[
  {
    "path": ".github/workflows/ci.yml",
    "content": "name: CI\n\non:\n  pull_request:\n      branches:\n        - main\n      paths:\n        - 'teams.md/**'\n\njobs:\n  build-teams-md:\n    name: Build teams.md\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout repository\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22'\n          cache: 'npm'\n\n      - name: Install dependencies (teams.md)\n        run: npm ci\n        working-directory: teams.md\n\n      - name: Clear build artifacts\n        working-directory: teams.md\n        run: npm run clear\n\n      - name: Build (teams.md)\n        env:\n          NODE_ENV: production\n        run: npm run build\n        working-directory: teams.md\n"
  },
  {
    "path": ".github/workflows/deploy-teams-docs.yml",
    "content": "name: Deploy-Teams-Docs\non:\n  workflow_dispatch:\n  push:\n    branches:\n      - main\n    paths:\n      - 'teams.md/**'\n      - .github/workflows/deploy-teams-docs.yml\n\njobs:\n  deploy:\n    environment:\n      name: github-pages\n    runs-on: ubuntu-latest\n    permissions:\n      contents: write # To push a branch\n      pages: write\n      id-token: write\n\n    steps:\n      - name: Checkout repo\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '22.x'\n\n      - name: Install dependencies\n        working-directory: teams.md\n        run: npm install\n\n      - name: Clear build artifacts\n        working-directory: teams.md\n        run: npm run clear\n\n      - name: Generate language docs\n        working-directory: teams.md\n        env:\n          NODE_ENV: production\n        run: npm run generate:docs\n\n      - name: Generate llms.txt files\n        working-directory: teams.md\n        run: npm run generate:llms\n\n      - name: Build static site\n        working-directory: teams.md\n        run: npm run build\n\n      - name: Setup GitHub Pages\n        uses: actions/configure-pages@v4\n\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: teams.md/build\n\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# Dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# Local env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# Testing\ncoverage\n\n# Turbo\n.turbo\n\n# Vercel\n.vercel\n\n# Build Outputs\n.next/\nout/\nbuild\ndist\n\n# Generated documentation files\nteams.md/docs/main/typescript/\nteams.md/docs/main/csharp/\nteams.md/docs/main/python/\n\n\n# Debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# Misc\n.DS_Store\n*.pem\n.specstory\n"
  },
  {
    "path": ".gitmodules",
    "content": ""
  },
  {
    "path": ".prettierignore",
    "content": "dist\nnode_modules\ncoverage\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n  \"recommendations\": [\"streetsidesoftware.code-spell-checker\"]\n}\n"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n  \"cSpell.words\": [\n    \"botframework\",\n    \"devtunnels\",\n    \"Entra\",\n    \"mdbook\"\n  ]\n}\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Instructions for Contributing Code\n\nTeams SDK is a mono-repo that hosts GitHub submodules to other repos that contain code by language. The submodules in this repository uses the latest commit from those submodules. To make changes to Teams SDK Typescript, C#, Python, please use the following repos:\n\n- [Teams.ts](https://github.com/microsoft/teams.ts)\n- [Teams.net](https://github.com/microsoft/teams.net)\n- [Teams.py](https://github.com/microsoft/teams.py)\n\nPlease note that as of August 2023, signed commits are required for all contributions to this project. For more information on how to set up, see the [GitHub Authentication verify commit signature](https://docs.github.com/en/authentication/managing-commit-signature-verification/about-commit-signature-verification) documentation.\n\n## Contributing bug fixes and features\n\nThis project welcomes contributions and suggestions. Most contributions require you to agree to a\nContributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us\nthe rights to use your contribution. For details, visit https://cla.opensource.microsoft.com.\n\nWhen you submit a pull request, a CLA bot will automatically determine whether you need to provide\na CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions\nprovided by the bot. You will only need to do this once across all repos using our CLA.\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\nFor more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or\ncontact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments.\n\nMicrosoft Teams is currently accepting contributions in the form of bug fixes and new\nfeatures. Any submission must have an issue tracking it in the issue tracker that has\nbeen approved by the Teams SDK team. Your pull request should include a link to\nthe bug that you are fixing. If you've submitted a PR for a bug, please post a\ncomment in the bug to avoid duplication of effort.\n\n## Legal\n\nIf your contribution is more than 15 lines of code, you will need to complete a Contributor\nLicense Agreement (CLA). Briefly, this agreement testifies that you are granting us permission\nto use the submitted change according to the terms of the project's license, and that the work\nbeing submitted is under appropriate copyright.\n\nPlease submit a Contributor License Agreement (CLA) before submitting a pull request.\nYou may visit https://cla.azure.com to sign digitally.\n\n## Contributing guide\n\n### Documentation\n\nPlease note that we place high importance on documentation, which we host as [Teams SDK github pages](https://microsoft.github.io/teams-sdk/).\n\n### Testing changes\n\nPlease use any of the agents in the `tests` directory of the repo you are writing in. These apps use the latest local changes and are intended to quickly set up and test feature work, bug fixes, etc.\n"
  },
  {
    "path": "LICENSE",
    "content": "Copyright (c) Microsoft Corporation.\n\nMIT License\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED *AS IS*, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE."
  },
  {
    "path": "README.md",
    "content": "# Welcome to the Teams SDK ([Docs](https://microsoft.github.io/teams-sdk/))\n\nTeams SDK represents a fundamental reimagining of how Teams apps and AI agents are built, while maintaining compatibility with existing botframework-based agents. This new version focuses on developer experience, simplified architecture, and enhanced AI capabilities.\n\nFor a detailed explanation of the motivations and architectural decisions behind v2, please see our [WHY.md](https://microsoft.github.io/teams-sdk/why) document.\n\n## We have a new name!\n\nWe are very excited to announce that \"Teams AI\" is now officially \"Teams SDK\"! This change reflects our commitment to providing a comprehensive development framework for building all types of Teams applications, including AI-powered agents.\n\n## Code repositories per SDK language\n\nThe SDK code for each language are in individual repos:\n\n- [Typescript](https://github.com/microsoft/teams.ts)\n- [C#](https://github.com/microsoft/teams.net)\n- [Python](https://github.com/microsoft/teams.py)\n\nFor language-specific bugs or issues, please use the Issues tab in the respective language repository.\n\n## Agent Accelerator Templates\n\nYou can find a set of open-source agent accelerator templates in the [Teams Agent Accelerators repository](https://github.com/microsoft/teams-agent-accelerator-templates). These templates provide a great starting point for building your own agents using the Teams SDK.\n\n### Quick start\n\nThe Teams SDK CLI makes it easy to bootstrap your first agent. First, install the CLI via NPM:\n\n```sh\nnpm install -g @microsoft/teams.cli\n```\n\nNext, use the CLI to create your agent:\n\n```sh\nnpx @microsoft/teams.cli new <typescript | csharp | python> quote-agent --template echo\n```\n\nFor more information, follow our quickstart guide: [C#](http://microsoft.github.io/teams-sdk/csharp/getting-started/quickstart), [Typescript](http://microsoft.github.io/teams-sdk/typescript/getting-started/quickstart), [Python](http://microsoft.github.io/teams-sdk/python/getting-started/quickstart)\n\n### SDK\n\nMicrosoft Teams has a robust developer ecosystem with a broad suite of capabilities, now unified via Teams SDK. Whether you are building AI-powered agents ([TS](https://microsoft.github.io/teams-sdk/typescript/in-depth-guides/ai/), [C#](https://microsoft.github.io/teams-sdk/csharp/in-depth-guides/ai/), [Python](https://microsoft.github.io/teams-sdk/python/in-depth-guides/ai/)), Message Extensions ([TS](https://microsoft.github.io/teams-sdk/typescript/in-depth-guides/message-extensions/), [C#](https://microsoft.github.io/teams-sdk/csharp/in-depth-guides/message-extensions/), [Python](https://microsoft.github.io/teams-sdk/python/in-depth-guides/message-extensions/)), embedded web applications, or Graph, Teams SDK has you covered.\n\nHere is a simple example, which responds to incoming messages with information retrieved from Graph.\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { DevtoolsPlugin } from '@microsoft/teams.dev';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App({\n  plugins: [new DevtoolsPlugin()],\n});\n\n// Listen for incoming messages\napp.on('message', async ({ userGraph, isSignedIn, send, signin }) => {\n  if (!isSignedIn) {\n    await signin(); // initiates Entra login flow\n    return;\n  }\n  const me = await userGraph.call(endpoints.me.get);\n  await send(`Hello, ${me.displayName} from Earth!`);\n});\n\n// Start your application\n(async () => {\n  await app.start();\n})();\n```\n\nFor language-specific bugs or issues, please use the Issues tab in the respective language repository.\n\n## Important: v1 to v2 Transition Notice\n\n**This repository has transitioned from v1 to v2 as the main branch.**\n\n- The `main` branch now contains v2 code, which was previously developed on the `v2-preview` branch.\n- The previous `main` branch (v1) has been moved to the [`release/v1`](https://github.com/microsoft/teams-sdk/tree/release/v1) branch. We will continue to provide critical bug fixes and security patches for v1 on this branch.\n\n## Documentation\n\nFor comprehensive documentation, API references, and examples, visit our [documentation site](https://microsoft.github.io/teams-sdk/).\n"
  },
  {
    "path": "SECURITY.md",
    "content": "<!-- BEGIN MICROSOFT SECURITY.MD V0.0.9 BLOCK -->\n\n## Security\n\nMicrosoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin).\n\nIf you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below.\n\n## Reporting Security Issues\n\n**Please do not report security vulnerabilities through public GitHub issues.**\n\nInstead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report).\n\nIf you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp).\n\nYou should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).\n\nPlease include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:\n\n- Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)\n- Full paths of source file(s) related to the manifestation of the issue\n- The location of the affected source code (tag/branch/commit or direct URL)\n- Any special configuration required to reproduce the issue\n- Step-by-step instructions to reproduce the issue\n- Proof-of-concept or exploit code (if possible)\n- Impact of the issue, including how an attacker might exploit the issue\n\nThis information will help us triage your report more quickly.\n\nIf you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs.\n\n## Preferred Languages\n\nWe prefer all communications to be in English.\n\n## Policy\n\nMicrosoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).\n\n<!-- END MICROSOFT SECURITY.MD BLOCK -->\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"teams-sdk\",\n  \"private\": true,\n  \"scripts\": {\n    \"clean\": \"npx turbo clean\",\n    \"build\": \"npx turbo build\",\n    \"build:packages\": \"npx turbo build --filter=./packages/*\",\n    \"dev\": \"npx turbo dev\",\n    \"lint\": \"npx turbo lint\",\n    \"lint:fix\": \"npx turbo lint:fix\",\n    \"test\": \"npx turbo test --concurrency=100%\",\n    \"fmt\": \"npx prettier --write \\\"**/*.{js,ts,tsx,md,json}\\\"\",\n    \"docs:build\": \"npm -w teams-md run build\",\n    \"docs:dev\": \"npm -w teams-md run start\",\n    \"docs:serve\": \"npm -w teams-md run serve\"\n  },\n  \"devDependencies\": {\n    \"prettier\": \"^3.5.3\",\n    \"turbo\": \"^2.5.0\",\n    \"typescript\": \"5.8.2\"\n  },\n  \"engines\": {\n    \"node\": \">=20\"\n  },\n  \"packageManager\": \"npm@10.9.2\",\n  \"workspaces\": [\n    \"packages/*\",\n    \"teams.md\"\n  ],\n  \"tsconfig\": {\n    \"exclude\": [\n      \"node_modules\"\n    ]\n  }\n}\n"
  },
  {
    "path": "packages/README.md",
    "content": "# This is a blank file\n\nThis is a blank folder currently. We will add packages that are common to all submodules here.\n"
  },
  {
    "path": "prettier.config.js",
    "content": "// prettier.config.js, .prettierrc.js, prettier.config.cjs, or .prettierrc.cjs\n\n/**\n * @see https://prettier.io/docs/en/configuration.html\n * @type {import(\"prettier\").Config}\n */\nconst config = {\n  trailingComma: 'es5',\n  semi: true,\n  singleQuote: true,\n  printWidth: 100,\n};\n\nmodule.exports = config;\n"
  },
  {
    "path": "teams.md/.gitattributes",
    "content": "# Auto detect text files and perform LF normalization\n* text=auto\n"
  },
  {
    "path": "teams.md/.gitignore",
    "content": "# Dependencies\n/node_modules\n\n# Production\n/build\n\n# Generated files\n.docusaurus\n.cache-loader\nscripts/generated/\n\n# Misc\n.DS_Store\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\nstatic/llms_docs/"
  },
  {
    "path": "teams.md/LANGUAGE-INCLUDE.md",
    "content": "# Language-Specific Documentation System\n\n## Table of Contents\n\n- [Quick Start](#quick-start)\n- [Architecture](#architecture)\n- [Core Concepts](#core-concepts)\n  - [Templates](#templates-srcpagestemplatesmdx)\n  - [Fragments](#fragments-srccomponentsincludelangincmd)\n- [Commands](#commands)\n- [Language Filtering](#language-filtering)\n- [Page Types](#page-types)\n- [Directory Structure](#directory-structure)\n- [Workflow](#workflow)\n- [Error Handling](#error-handling)\n  - [Development Error Messages](#development-error-messages)\n  - [Content Gaps Tracking](#content-gaps-tracking)\n- [Summary](#summary)\n- [Best Practices](#best-practices)\n- [Migration Guide](#migration-guide)\n- [Troubleshooting](#troubleshooting)\n\nThis documentation system maintains a single source of truth for content across multiple programming languages (TypeScript, C#, Python), with language-specific fragments embedded within shared templates.\n\n## Quick Start\n\n1. **Create template**: `src/pages/templates/my-guide.mdx` with `<LanguageInclude section=\"example\" />` tags\n2. **Create fragments**: `src/components/include/my-guide/{lang}.incl.md` with `<!-- example -->` sections\n3. **Generate**: `npm run generate:docs` or `npm start`\n\n## Architecture\n\n```\nsrc/pages/templates/    ← Templates with <LanguageInclude /> tags\n     ↓ (generate-language-docs.ts)\nsrc/components/include/ ← Language-specific fragments ({lang}.incl.md)\n     ↓ (Build-time processing)\ndocs/main/{lang}/      ← Auto-generated files (fully resolved)\n     ↓ (Docusaurus)\nFinal rendered page    ← Language-specific content only\n```\n\n## Core Concepts\n\n[⭐ Skip to Best Practices section](#best-practices)\n\n### Templates (`src/pages/templates/**/*.mdx`)\n\nYour source of truth - write common content once with `<LanguageInclude />` placeholders:\n\n```mdx\n---\ntitle: Getting Started\nlanguages: ['typescript', 'python'] # Optional: restrict languages to render documentation for\n---\n\n# Getting Started\n\nShared content for all languages.\n\n<LanguageInclude section=\"install\" />  <!-- Block-level: full Markdown from include files -->\n\nThe package name is <LanguageInclude section=\"package-name\" />. <!-- Inline: from include files -->\n\n<!-- NEW: Inline content without separate include files -->\nThe context type is <LanguageInclude content={{\"typescript\": \"`IContext`\", \"python\": \"`Context`\"}} />.\n```\n\n### Fragments (`src/components/include/**/{lang}.incl.md`)\n\nLanguage-specific content organized by HTML comment sections:\n\n````markdown\n<!-- install -->\n\n```bash\nnpm install @microsoft/teams.ts\n```\n\n<!-- package-name -->\n\n@microsoft/teams.ts\n\n<!-- advanced -->\n\nN/A\n````\n\n**Fragment Rules:**\n\n- `<!-- sectionName -->` marks section start\n- Content continues until next section or file end\n- Use `N/A` to intentionally skip sections (e.g., when a section is necessary for one or two languages, but not all three)\n- Full Markdown/MDX supported for block-level content\n\n**Directory Mapping:**\n\n- Category pages: `src/components/include/{category}/{lang}.incl.md`\n- Regular pages: `src/components/include/{category}/{filename}/{lang}.incl.md`\n\n### Inline Content\n\nFor simple, short language-specific text (like API names, method names, or simple phrases), you can use inline content directly in templates without creating separate include files:\n\n```mdx\n<LanguageInclude content={{\"typescript\": \"`send`\", \"csharp\": \"`SendAsync`\", \"python\": \"`send`\"}} />\n```\n\n**When to use inline content:**\n- Short text snippets (API names, method names, parameter names)\n- Simple differences between languages\n- Content that's easier to read inline than in separate files\n\n**When to use include files:**\n- Code examples\n- Complex or multi-line content\n- Content that benefits from syntax highlighting\n- Larger documentation sections\n\n## Language Filtering\n\nControl which languages generate pages using frontmatter:\n\n```mdx\n---\ntitle: TypeScript-only Feature\nlanguages: ['typescript'] # Restrict rendering to specific languages. If all languages are supported, omit this field.\nsuppressLanguageIncludeWarning: true # Suppress warnings for static content\n---\n```\n\n**Important**: When a template file has `languages` frontmatter that restricts to specific languages, you should only create include files for those supported languages. For example, if a template is restricted to `['typescript', 'python']`, you should not create a `csharp.incl.md` file - only create `typescript.incl.md` and `python.incl.md`.\n\nUseful for:\n\n_- Language-specific features\n_- Migration periods\n\n- Framework-specific documentation\n\n## Page Types\n\n- **Category pages**: `README.mdx` → `index.mdx`\n- **Regular pages**: `filename.mdx` → `filename.mdx`\n- **Hidden pages**: `_filename.mdx` (accessible but hidden from sidebar)\n\n## Directory Structure\n\n```\nsrc/pages/templates/\n├── getting-started/\n│   ├── README.mdx              # → index.mdx\n│   ├── quickstart.mdx\n│   └── _category_.json\n\nsrc/components/include/\n├── getting-started/            # For README.mdx\n│   ├── typescript.incl.md\n│   ├── csharp.incl.md\n│   └── python.incl.md\n└── getting-started/\n    └── quickstart/             # For quickstart.mdx\n        ├── typescript.incl.md\n        ├── csharp.incl.md\n        └── python.incl.md\n\ndocs/main/{lang}/               # Auto-generated (DO NOT EDIT)\n├── getting-started/\n│   ├── index.mdx\n│   └── quickstart.mdx\n```\n\n## Workflow\n\n1. **Create template**: `src/pages/templates/my-guide.mdx`\n2. **Create fragments**: `src/components/include/my-guide/{lang}.incl.md`\n3. **Generate**: `npm run generate:docs` and `npm start`\n\n## Error Handling\n\n- **Missing files/sections**: Console warnings (unless language-restricted)\n- **Empty sections**: Show development error messages\n- **N/A sections**: Use `N/A` or `not applicable` to intentionally skip sections\n- **Development mode**: Error messages rendered in browser with markdown formatting\n- **Production mode**: Clean files without error content\n- **Page availability**: Tracked in `static/missing-pages.json`\n- **Content gaps**: Tracked in `scripts/generated/content-gaps.json` and `content-gaps.md`\n\n### Development Error Messages\n\nWhen sections are missing or empty, you'll see helpful error messages:\n\n```markdown\n**[Dev] Section \"install\" not found in TypeScript documentation.**\nEither mark the section explicitly as N/A for intentionally ignored, or fill in documentation.\n```\n\n### Content Gaps Tracking\n\nThe system now generates development reports to help track missing documentation:\n\n- **JSON manifest**: `scripts/generated/content-gaps.json` - Machine-readable data\n- **Markdown report**: `scripts/generated/content-gaps.md` - Human-readable summary\n- **Automatic generation**: Created every time docs are generated\n- **Git ignored**: Reports are not committed to the repository\n\nExample content gaps report:\n\n```markdown\n# Content Gaps Report\n\n**8 template(s) have missing sections**\n\n## `in-depth-guides/ai/function-calling.mdx`\n\n- **`advanced-features`**: Missing in C#, Python\n\n## Summary\n\n- **8** templates with gaps\n- **27** total missing sections\n```\n\n## Best Practices\n\n1. **Never edit `docs/main/{lang}/` files** - they're auto-generated\n2. **Use `languages: ['lang1', 'lang2']` frontmatter** for language-specific pages\n3. **Add `suppressLanguageIncludeWarning: true`** for static content pages\n4. **Keep section names consistent** across all fragment files\n5. **Use `N/A` or `not applicable` explicitly** when a section doesn't apply to a language\n6. **Distinguish between missing and intentional**: Empty sections show errors, `N/A` sections are silently skipped\n7. **Test all supported languages** before committing\n8. **Use block-level tags for rich content**, inline tags for simple text\n9. **Add `title`** to frontmatter for proper capitalization and navigation rendering\n   - Use `sidebar_label` to customize sidebar text if needed\n10. **Prefix utility pages with underscore** to hide from sidebar\n11. **Use correct relative URLs** (no extensions, relative to generated location)\n12. **Remove file suffixes from markdown links** - use `[link](../page)` not `[link](../page.md)` or `[link](../README.md)`\n13. **Check `missing-pages.json`** after generation to verify restrictions\n14. **Review content gaps report** at `scripts/generated/content-gaps.md` to track missing documentation\n\n## Migration Guide\n\n1. **Analyze differences** between existing language versions\n2. **Extract common content** → template file\n3. **Extract language-specific content** → include files\n4. **Create template and includes** following directory structure\n5. **Generate and test** all languages\n6. **Remove old files** from `docs/{lang}/`\n\n## Troubleshooting\n\n- **Translation conflicts**: Regenerate with `npm run generate:docs`\n- **Missing sidebar labels**: Add `sidebar_label: \"Name\"` to frontmatter\n- **Language dropdown issues**: Ensure `missing-pages.json` exists\n- **Banner not dismissing**: Check browser console for errors\n- **Unexpected warnings**: Add `suppressLanguageIncludeWarning: true`\n- **Empty vs N/A sections**: Use `N/A` to intentionally skip; empty sections will show dev errors\n- **Content gaps tracking**: Check `scripts/generated/content-gaps.md` for missing documentation overview\n"
  },
  {
    "path": "teams.md/README.md",
    "content": "# Website\n\nThis website is built using [Docusaurus](https://docusaurus.io/), a modern static website generator.\n\n### Installation\n\n```\n$ npm install\n```\n\n### Local Development\n\n```\n$ npm run start\n```\n\nThis command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server.\n\n### Build\n\n```\n$ npm run build\n```\n\nThis command generates static content into the `build` directory and can be served using any static contents hosting service.\n\n### Generate LLM.txt files\n\n```\n$ npm run generate:llms\n```\n\nThis command generates `LLM.txt` files used for SEO purposes.\n\n### Generate docs\n\n```\n$ npm run generate:docs\n```\n\nThis command generates documentation files for the website using the custom LanguageInclude system. To see how to use LanguageInclude, check out the [Language Include documentation](./LANGUAGE-INCLUDE.md).\n\n### Watch docs generation\n\n```\n$ npm run generate:docs:watch\n```\n\nWatches for changes and regenerates documentation files automatically.\n\n### TypeScript type checking\n\n```\n$ npm run typecheck\n```\n\nRuns TypeScript type checking on the codebase.\n\n### Prebuild\n\n```\n$ npm run prebuild\n```\n\nRuns docs generation before building the site.\n\n### Scaffold folders/files\n\n```\n$ npm run scaffold -- <path>\n```\n\nScaffolds template/include folders and files. See `src/scripts/scaffold.js` for details.\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/README.md",
    "content": "---\nsidebar_position: 4\nsummary: Overview of developer tools in Teams SDK including the CLI for project management and DevTools for testing and debugging agents.\n---\n\n# Developer Tools\n\nOne of the main motivations for Teams SDK is to provide excellent tools that simplify and speed up building and testing agents. Because of this, we created the CLI for speedy agent initiation and project management, and DevTools as an accessible way to test your agent's behavior without jumping through deployment hoops. DevTools also provides crucial insight on activity payloads on the Activities page.\n\nLearn more about the developer tools that come with Teams SDK.\n\n1. [Teams CLI](./cli)\n2. [DevTools](./devtools)\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/_category_.json",
    "content": "{\n  \"position\": 5,\n  \"label\": \"Developer Tools\",\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/cli.md",
    "content": "---\nsidebar_position: 1\nsummary: Comprehensive guide to the Teams CLI tool for creating, managing, and deploying Teams SDK applications with simple command-line operations. Use this when you need to set up a new Teams SDK agent or manage existing ones.\n---\n\n# Teams CLI\n\nThe Teams CLI was created with the intent of supporting developers by making common actions simple to implement with just a command line. The CLI overarching features are:\n\n| Feature       | Description                                                                                          |\n| ------------- | ---------------------------------------------------------------------------------------------------- |\n| `new`         | Create a new Teams SDK agent by choosing a template that will be ready to run with one command line. |\n| `config`      | Add Microsoft 365 Agents Toolkit configuration files to your existing Teams SDK agent project.       |\n| `environment` | Manage multiple environments (e.g. dev, prod) and their keys for your agent.                         |\n\n:::tip\nWith the CLI, you can enter `npx @microsoft/teams.cli <token-arguments> --help` at any command level to access information about the command, tokens, or required arguments.\n:::\n\n## Create an agent with one command line\n\n```sh\nnpx @microsoft/teams.cli@latest new <typscript | csharp | python> <app-name> <optional>\n```\n\nThe `new` token will create a brand new agent with `app-name` applied as the directory name and project name.\n\n:::note\nThe name you choose may have case changes when applied; for example, \"My App\" would become \"my-app' due to the requirements for `package.json` files.\n:::\n\n:::warning\nOur Python SDK is currently in Public Preview. As a result, we have the CLI under a feature flag.\nPlease run the below command to enable this language.\n:::\n\n```sh\n$env:ENABLE_EXPERIMENTAL_PYTHON_OPTIONS = 1\n```\n\n### Optional parameters\n\n:::tip\nUse command line `npx @microsoft/teams.cli --help` to see the latest options for all optional params.\n:::\n\n| Parameter              | Description                                                                                                                                                                                                                                                 |\n| ---------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `--template`           | Ready-to-run templates that serve as a starting point depending on your scenario. Template examples include `ai`, `echo`, `graph`, and more.                                                                                                                |\n| `--start`              | Run the agent immediately upon finishing the creation of the project.                                                                                                                                                                                       |\n| `--toolkit` or `--atk` | Include the configuration files required to run the agent in the debugger via the [Microsoft 365 Agents Toolkit](https://github.com/OfficeDev/teams-toolkit) extension. Options include `basic`, `embed`, and `oauth`, and more may be added in the future. |\n| `--client-id`          | The app client id, if you already have deployed a resource. This will be added to the root `.env` file of the project.                                                                                                                                      |\n| `--client-secret`      | The app client secret, if you already have deployed a resource. This will be added to the root `.env` file of the project.                                                                                                                                  |\n\n## Add Microsoft 365 Agents Toolkit configuration to existing agent\n\nAn existing project may also have the appropriate Microsoft 365 Agents Toolkit configuration files added by configuration name.\n\n```bash\nnpx @microsoft/teams.cli config add <config-name>\n```\n\n| Configuration | Description                                      |\n| ------------- | ------------------------------------------------ |\n| `atk.basic`   | Basic Microsoft 365 Agents Toolkit configuration |\n| `atk.embed`   | Configuration for embedded Teams applications    |\n| `atk.oauth`   | Configuration for OAuth-enabled applications     |\n\nUsing this command will include\n\n- `env`: folders for managing multiple environments\n- `infra`: files for deployment and provisioning\n- `.yml` files for tasks, launch, deployment, etc.\n\n## Remove Agents Toolkit configuration files\n\n```bash\nnpx @microsoft/teams.cli config remove <config-name>\n```\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/devtools/README.md",
    "content": "---\nsidebar_position: 2\nsummary: Use DevTools to locally test Teams apps with chat, activity inspection, and card design features.\nllms: ignore\n---\n\n# 🛠️ DevTools\n\nThe developer tools can be used to locally interact with an app to streamline the testing/development process,\npreventing you from needing to deploy/register the app or expose a public endpoint.\n\n![Screenshot of DevTools showing user prompt 'hello!' and agent response 'you said hello!'.](/screenshots/devtools-echo-chat.png)\n\n## Basic features\n\n- **Chat**: Chat with your app the same way you would in Teams without the need for an app id or authentication. This is useful for testing and debugging your app.\n- **Inspect**: Inspect incoming and outgoing activities on DevTools' Activity page. All activity on your agent logged here, including messages, reactions, and more.\n- **Cards**: Use the Cards page to visually design and test your cards.\n\nContinue on to the next pages to learn more about the features available in DevTools.\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/devtools/cards.md",
    "content": "---\nsidebar_position: 3\nsummary: Design and test Adaptive Cards using the DevTools card designer with live preview and JSON editing.\nllms: ignore\n---\n\n# 🪪 Cards\n\nUse the Cards page to design and test your cards. Then, use the \"Attach card\" button to add that card as an attachment to your message. By default, the card will be attached in the new message compose box, but you can also attach a card when editing an existing message.\n\n![Card Designer Typescript](https://github.com/microsoft/teams.ts/blob/main/assets/screenshots/adaptive-cards-designer.png?raw=true)\n\n## Using the card designer from Chat\n\nAdd an attachment to your message by clicking the attachment (paperclip) icon in the compose box. Select \"Open card designer\" from the dropdown menu, and your card will be added as an attachment to the same message you are composing or editing after you click \"Attach card\".\n\n:::tip\nDevTools stores your card attachment so you can use it between page navigation (Chat to Cards and back). Only the last card you designed will be stored, and only temporarily, so if you want to save a card, make sure to save the payload to a file or copy it to your clipboard.\nAlso check out the **[Adaptive Cards Designer](https://adaptivecards.microsoft.com/designer)** and [documentation](https://adaptivecards.microsoft.com/designer).\n:::\n\n## Pasting Adaptive Card JSON\n\nYou can also use the \"Paste custom JSON\" menu option to paste an Adaptive Card JSON payload into the dialog that will renders. This adds the attachment to the message you are composing or editing.\n\nPlease keep an eye out for a big update coming soon!\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/devtools/chat.md",
    "content": "---\nsidebar_position: 1\nsummary: Test chat functionality with your Teams agent using the lightweight DevTools without sideloading into Teams.\nllms: ignore\n---\n\n# 💬 Devtools chat\n\nUse the lightweight DevTools app that allows you to test chat functionality with your agent without the need to sideload into Teams. This is useful for testing and debugging.\n\n![Empty DevTools chat](https://github.com/microsoft/teams.ts/blob/main/assets/screenshots/devtools_blank_chat.png?raw=true)\n\n:::note\nWe plan to add more features to DevTools for a wider variety of testing. Stay tuned!\n:::\n\n## Using DevTools\n\nUse the Teams SDK dev package as a plugin.\n\n### Installation\n\nAdd the dev package to your Teams app.\n\n```bash\n$: npm install @microsoft/teams.dev\n```\n\n### Usage\n\nIn your app's main file, ensure DevTools plugin is added to the app.\n\n:::warning\nDevTools is not secure and should not be used in production environments. Remove the plugin before deploying your app to production.\n:::\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { ConsoleLogger } from '@microsoft/teams.common/logging';\nimport { DevtoolsPlugin } from '@microsoft/teams.dev';\n//... Other imports here\nconst app = new App({\n  logger: new ConsoleLogger('@samples/echo', { level: 'debug' }),\n  plugins: [new DevtoolsPlugin()],\n});\n```\n\nWhen you run your app, for example `npm run dev`, devtools will be running on port 3979\n\n```bash\n[nodemon] watching extensions: ts\n[nodemon] starting `node -r ts-node/register -r dotenv/config ./src/index.ts`\n[INFO] @samples/echo/http listening on port 3978 🚀\n[INFO] @samples/echo/devtools available at http://localhost:3979/devtools\n```\n\n:::info\nIf you used the [CLI](../cli) to create an `atk` configuration for your app, DevTools will run on port 3979 when you launch the debugger.\n:::\n\nWhen you open the page, you will see a Teams-like chat window and you can immediately interact with your agent.\n\n![Screenshot of DevTools showing user prompt 'hello!' and agent response 'you said hello!'.](/screenshots/devtools-echo-chat.png)\n\n## Teams chat terminology\n\nBelow is a brief list of the terminology used in the chat window and in Teams:\n\n1. **Compose box**: The area where you type your message and attach files. A newly rendered Chat page will automatically focus on the compose box.\n2. **Message actions menu**: The menu that appears when you hover over or focus on a message, offering different actions depending on whether you sent or received the message.\n\n## Chat capabilities\n\nThe chat window emulates Teams features as closely as possible. Not all Teams features are available in DevTools, but we are working to add more features over time. The following capabilities are available:\n\n:::info\nAccessibility and keyboard navigation is not fully supported in DevTools. Full support for all users is important to us, and we will prioritize acessibility in future preview releases.\n:::\n\n### Send messages\n\nYou can send messages to your agent just like in Teams. In the compose box, type your message and press <kbd>Enter</kbd> to send it.\n\n### Attachments\n\nAttach up to 10 files to your message using the Attach (paperclip) button. DevTools supports pasting an Adaptive Card JSON or attaching a card from the card designer. See the [Cards page](./cards) for more.\n\n:::note\nFurther support for attachments is coming soon!\n:::\n\n### Connectivity\n\nCheck your app's connectivity in three ways:\n\n1. The DevTools banner shows a green badge or 'Connected' text when connected, and red or 'Disconnected' when not.\n2. Similarly, the agent's avatar shows a 'Connected' or 'Disconnected' badge.\n3. DevTools uses the `ConsoleLogger` that logs connectivity events to the console. Use the browser's console tool to see the logs.\n\n### Message reactions\n\nYou can react to messages by selecting an emoji in the message actions menu.\n\n![Devtools react to a message](https://github.com/microsoft/teams.ts/blob/main/assets/screenshots/devtools_message_reaction.gif?raw=true)\n\n### Edit your message\n\nEdit messages by selecting the Edit (pencil) icon from the message actions menu. Press Enter or the checkmark button to send the edited message, or the Dismiss (X) button to cancel.\n\n### Delete your message\n\nSoft delete messages by hovering over your message, pressing the More (ellipsis) button, then the Delete (trash) button. Click \"Undo\" to restore the message.\n\n### Streaming\n\nIf your agent is using streaming, DevTools will render messages as a stream with a rainbow border until the stream ends. See the full stream on the [Activities](inspect) page by clicking the Inspect (magnifying glass) button in the message actions menu of the message.\n\n### Feedback\n\nSend feedback to your app by clicking the Feedback (thumbs up/down) buttons in the message actions menu and completing the dialog form.\n\n:::info\nThe capabilities above will also populate activities to the Activities page, where you can inspect activity payloads and see the full activity history.\n:::\n\n### Developer message shortcut\n\nFor easier debugging, the compose box stores the last five messages sent to the app. Press the Up <kbd>↑</kbd> arrow key to cycle through your message history and resend messages.\n\n![Devtools Up Arrow Feature](https://github.com/microsoft/teams.ts/blob/main/assets/screenshots/devtools_uparrow_feature.gif?raw=true)\n"
  },
  {
    "path": "teams.md/docs/main/developer-tools/devtools/inspect.md",
    "content": "---\nsidebar_position: 2\nsummary: Inspect all incoming and outgoing activities with your agent using DevTools Activities page for debugging.\nllms: ignore\n---\n\n# 🔍 Inspect activities\n\nInspect incoming and outgoing activities on DevTools' Activities page. All interactions with your agent are logged here, including messages, reactions, and more.\n![Inspect Activities view](https://github.com/microsoft/teams.ts/blob/main/assets/screenshots/inspect_activity.png?raw=true)\n\n## View all activity\n\nThe Activities page displays all activities sent to and from your agent in a grid, showing:\n\n1. Activity type (message, reaction, etc.)\n2. Direction via down arrow (incoming) or up arrow (outgoing)\n3. Conversation type (for now, only personal chat is supported)\n4. Sender\n5. Timestamp.\n\n### Monitor activity while testing Teams in browser\n\nWhen testing your sideloaded app in the Teams web client, you can monitor activities in DevTools. Once your agent has launched, the agent server will indicate what port DevTools is running on). Open another browser tab and navigate to the DevTools Activities URL. Interact with your agent in the Teams web client and see the activities in DevTools. To learn more, review the [Agents Toolkit](../../teams/configuration/agents-toolkit) page.\n\nYou can filter activities by type using the filter icon in the Type column header.\n\n### View activity details\n\nSelecting an activity in the grid opens a detailed view in Preview mode, showing the full payload as a tree with expandable and collapsible sections.\n\n### View activity JSON\n\nToggle from \"Preview\" to \"JSON\" under the \"Activity details\" header to see the raw JSON payload.\n\n### Copy activity payload\n\nPress the Copy button in the top right corner of the Activity details view to copy the payload to your clipboard.\n\n### Inspect activities by ID\n\nWhen in [Chat](chat), you can inspect activities by ID by clicking the magnifying glass icon in the message actions menu. This opens the Activities page with the activity ID filtered in the list, which is useful for inspecting streamed messages, which have multiple activities.\n\nTo reset the filter, use the filter button in the Type column header and de-select the activity ID to show all activities again.\n"
  },
  {
    "path": "teams.md/docs/main/privacy.md",
    "content": "---\nsidebar_position: 5\nsummary: Privacy policy detailing data collection practices using Microsoft Clarity for website analytics and user behavior tracking.\nllms: ignore\n---\n\n# Privacy Policy\n\nWe partner with Microsoft Clarity to capture how you use and interact with our website through behavioral metrics, heatmaps, and session replay to improve and market our products/services. Website usage data is captured using first and third-party cookies and other tracking technologies to determine the popularity of products/services and online activity. Additionally, we use this information for site optimization and fraud/security purposes. For more information about how Microsoft collects and uses your data, visit the [Microsoft Privacy Statement](https://www.microsoft.com/en-us/privacy/privacystatement)."
  },
  {
    "path": "teams.md/docs/main/teams/README.md",
    "content": "---\nsidebar_position: 3\nsummary: Overview of Teams-specific features and SDK components that enable agent integration with the Microsoft Teams platform.\n---\n\n# Teams Integration\n\nThis section describes Teams-specific features and components of the SDK, helping you understand how your agent integrates with the Microsoft Teams platform.\n\n## Core Concepts\n\nWhen working with Teams, several key components come into play:\n\n- **DevTunnel**: Enables local development by creating secure public endpoints\n- **App Provisioning**: Handles registration and configuration in Teams\n- **Environment Setup**: Manages Teams-specific configuration files\n- **App Packaging**: Bundles your agent for Teams deployment\n\n## In This Section\n\n1. [Core Concepts](core-concepts) - Understanding the Teams deployment process and architecture\n2. [Teams Manifest](manifest) - Configuring your agent's Teams presence\n3. [Microsoft 365 Agents Toolkit](configuration/agents-toolkit) - Using the Agents Toolkit extension for sideloading, deployment, and provisioning\n4. [Enabling in M365 Copilot](enabling-in-copilot) - Make your Teams app available in M365 Copilot\n\nEach guide provides detailed information about specific aspects of Teams integration, from local development to production deployment.\n"
  },
  {
    "path": "teams.md/docs/main/teams/_category_.json",
    "content": "{\n  \"position\": 2,\n  \"label\": \"Teams Integration\",\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "teams.md/docs/main/teams/app-authentication/README.mdx",
    "content": "---\nsidebar_position: 4\nsummary: Set up authentication for your Teams bot using client secrets, user managed identities, or federated identity credentials\n---\n\nimport LangLink from '@site/src/components/LangLink';\n\n# App Authentication Setup\n\nYour Teams bot needs to authenticate with Azure to send messages. This involves configuring your Azure Bot Service and App Registration correctly.\n\n## Authentication Methods\n\nChoose one of the following authentication methods based on your security requirements:\n\n1. **[Client Secret](client-secret)** - Simple password-based authentication using a client secret\n2. **[User Managed Identity](user-managed-identity)** - Passwordless authentication using Azure managed identities\n3. **[Federated Identity Credentials](federated-identity-credentials)** - Advanced identity federation using managed identities assigned to App Registration\n\nEach method has different setup requirements in Azure Portal or Azure CLI.\n\n## After Setup\n\nOnce you've completed the Azure setup for your chosen authentication method, you'll need to configure your application code. See the <LangLink to=\"essentials/app-authentication\">App Authentication configuration guide</LangLink> for details on environment variables and code configuration.\n\n## Troubleshooting\n\nIf you encounter authentication errors, see the [Troubleshooting](troubleshooting) guide for common issues and solutions.\n"
  },
  {
    "path": "teams.md/docs/main/teams/app-authentication/_category_.json",
    "content": "{\n  \"label\": \"App Authentication Configuration\",\n  \"position\": 3,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/docs/main/teams/app-authentication/client-secret.md",
    "content": "---\nsidebar_position: 1\ntitle: Client Secret Setup\nsummary: Set up client secret authentication for your Teams bot in Azure Portal or Azure CLI\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Client Secret Authentication Setup\n\nClient Secret authentication is the simplest method, using a password-like secret to authenticate your bot. While easy to set up, secrets need to be rotated periodically and kept secure.\n\n## Prerequisites\n\nBefore you begin, ensure you have:\n- An Azure subscription\n- Permissions to create App Registrations and Azure Bot Services\n\n## Setup Steps\n\n### Step 1: Create Azure Bot with Single Tenant\n\nWhen creating your Azure Bot Service, you must select `Single Tenant` for the `Type of App`.\n\n![Single Tenant Bot Creation](/screenshots/single-tenant-bot.png)\n\n### Step 2: Create Client Secret\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n1. Navigate to your **App Registration** in the Azure Portal\n2. Go to **Certificates and Secrets**\n3. Click **New client secret**\n4. Add a description and select an expiration period\n5. Click **Add**\n6. **Important**: Copy the secret value immediately - it won't be shown again\n\n![Secret in Certificates and Secrets](/screenshots/client-secret.png)\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\n```bash\n# Create a new client secret\naz ad app credential reset --id $APP_ID --append\n```\n\nThe command will output the secret value. Save it securely.\n\n</TabItem>\n</Tabs>\n\n## Next Steps\n\nAfter completing the Azure setup, configure your application code with the appropriate environment variables. See the App Authentication Essentials Guide for details.\n"
  },
  {
    "path": "teams.md/docs/main/teams/app-authentication/federated-identity-credentials.md",
    "content": "---\nsidebar_position: 3\ntitle: Federated Identity Credentials Setup\nsummary: Set up Federated Identity Credentials authentication for your Teams bot in Azure Portal or Azure CLI\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Federated Identity Credentials Setup\n\nFederated Identity Credentials (FIC) allows you to assign managed identities directly to your App Registration instead of creating a separate User Managed Identity resource.\n\n## Prerequisites\n\nBefore you begin, ensure you have:\n- An Azure subscription\n- Permissions to create App Registrations, Azure Bot Services, and manage identities\n- A compute resource where your bot will be hosted (App Service, Container App, VM, etc.)\n- Either a User Managed Identity or the ability to use System Assigned Identity\n\n## Setup Steps\n\n### Step 1: Create Azure Bot with Single Tenant\n\nWhen creating your Azure Bot Service, select `Single Tenant` for the `Type of App`.\n\n![Single Tenant Bot Creation](/screenshots/single-tenant-bot.png)\n\n### Step 2: Configure Federated Credentials\n\nAssign managed identities to your App Registration using Federated Credentials.\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n1. Navigate to your **App Registration** in the Azure Portal\n2. Go to **Certificates and Secrets**\n3. Select the **Federated Credentials** tab\n4. Click **Add credential**\n5. Select the federated credential scenario (e.g., \"Customer managed keys\")\n6. Choose the User Managed Identity or configure for System Assigned Identity\n7. Complete the required fields and click **Add**\n\n![Federated Identity Creds](/screenshots/fic.png)\n\nThe identity you select here must also be assigned to the compute resource where your application is hosted.\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\n```bash\n# Add a federated credential for a user managed identity\naz ad app federated-credential create \\\n  --id $APP_ID \\\n  --parameters '{\n    \"name\": \"MyFederatedCredential\",\n    \"issuer\": \"https://login.microsoftonline.com/'$TENANT_ID'/v2.0\",\n    \"subject\": \"'$MANAGED_IDENTITY_CLIENT_ID'\",\n    \"audiences\": [\"api://AzureADTokenExchange\"]\n  }'\n```\n\n</TabItem>\n</Tabs>\n\n### Step 3: Assign the Managed Identity to Your Compute Resource\n\nThe managed identity configured in the federated credential must be assigned to your compute resource.\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n**For User Managed Identity:**\n\n1. Navigate to your compute resource in the Azure Portal\n2. Go to **Identity** section in the left menu\n3. Select the **User assigned** tab\n4. Click **Add**\n5. Select the User Managed Identity you configured in the federated credential\n6. Click **Add** to confirm\n\n**For System Assigned Identity:**\n\n1. Navigate to your compute resource in the Azure Portal\n2. Go to **Identity** section in the left menu\n3. Select the **System assigned** tab\n4. Set **Status** to **On**\n5. Click **Save**\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\n```bash\n# For user managed identity:\naz webapp identity assign \\\n  --name $APP_NAME \\\n  --resource-group $RESOURCE_GROUP \\\n  --identities $MANAGED_IDENTITY_RESOURCE_ID\n\n# For system assigned identity:\naz webapp identity assign \\\n  --name $APP_NAME \\\n  --resource-group $RESOURCE_GROUP\n```\n\n</TabItem>\n</Tabs>\n\n## Next Steps\n\nAfter completing the Azure setup, configure your application code with the appropriate environment variables. See the App Authentication Essentials Guide for details.\n"
  },
  {
    "path": "teams.md/docs/main/teams/app-authentication/troubleshooting.md",
    "content": "---\nsidebar_position: 4\ntitle: Troubleshooting\nsummary: Common authentication errors and how to resolve them\nllms: ignore\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Authentication Troubleshooting\n\nThis guide covers common authentication errors and their solutions.\n\n## Missing Service Principal in the Tenant\n\nThis error occurs when the application has a single-tenant Azure Bot Service (`msaAppType: 'SingleTenant'`) instance, but your app registration has not yet been linked to a Service Principal in the tenant.\n\n### Error Examples\n\n<Tabs>\n<TabItem value=\"typescript\" label=\"TypeScript\">\n\n```sh\n[ERROR] @teams/app Request failed with status code 401\n[ERROR] @teams/app /aaaabbbb-0000-cccc-1111-dddd2222eeee/oauth2/v2.0/token\n[ERROR] @teams/app {\n[ERROR] @teams/app   \"error\": \"invalid_client\",\n[ERROR] @teams/app   \"error_description\": \"AADSTS7000229: The client application 00001111-aaaa-2222-bbbb-3333cccc4444 is missing service principal in the tenant aaaabbbb-0000-cccc-1111-dddd2222eeee. See instructions here: https://go.microsoft.com/fwlink/?linkid=2225119 Trace ID: 0000aaaa-11bb-cccc-dd22-eeeeee333333 Correlation ID: aaaa0000-bb11-2222-33cc-444444dddddd Timestamp: 2025-09-18 01:17:37Z\",\n[ERROR] @teams/app   \"error_codes\": [\n[ERROR] @teams/app     7000229\n[ERROR] @teams/app   ],\n[ERROR] @teams/app   \"timestamp\": \"2025-09-18 01:17:37Z\",\n[ERROR] @teams/app   \"trace_id\": \"0000aaaa-11bb-cccc-dd22-eeeeee333333\",\n[ERROR] @teams/app   \"correlation_id\": \"aaaa0000-bb11-2222-33cc-444444dddddd\",\n[ERROR] @teams/app   \"error_uri\": \"https://login.microsoftonline.com/error?code=7000229\"\n[ERROR] @teams/app }\n```\n\n</TabItem>\n<TabItem value=\"python\" label=\"Python\">\n\n```sh\n[ERROR] @teams/app Failed to refresh bot token: Client error '401 Unauthorized' for url 'https://login.microsoftonline.com/aaaabbbb-0000-cccc-1111-dddd2222eeee/oauth2/v2.0/token'\n[ERROR] @teams/app For more information check: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status/401\n```\n\n</TabItem>\n<TabItem value=\"csharp\" label=\"C#\">\n\n```sh\n[ERROR] Echobot Failed to get bot token on app startup.\n[ERROR] Echobot {\n[ERROR] Echobot   \"error\": \"invalid_client\",\n[ERROR] Echobot   \"error_description\": \"AADSTS7000229: The client application 00001111-aaaa-2222-bbbb-3333cccc4444 is missing service principal in the tenant aaaabbbb-0000-cccc-1111-dddd2222eeee. See instructions here: https://go.microsoft.com/fwlink/?linkid=2225119 Trace ID: 0000aaaa-11bb-cccc-dd22-eeeeee333333 Correlation ID: aaaa0000-bb11-2222-33cc-444444dddddd Timestamp: 2025-09-18 02:26:20Z\",\n[ERROR] Echobot   \"error_codes\": [\n[ERROR] Echobot     7000229\n[ERROR] Echobot   ],\n[ERROR] Echobot   \"timestamp\": \"2025-09-18 02:26:20Z\",\n[ERROR] Echobot   \"trace_id\": \"0000aaaa-11bb-cccc-dd22-eeeeee333333\",\n[ERROR] Echobot   \"correlation_id\": \"aaaa0000-bb11-2222-33cc-444444dddddd\",\n[ERROR] Echobot   \"error_uri\": \"https://login.microsoftonline.com/error?code=7000229\"\n[ERROR] Echobot }\n```\n\n</TabItem>\n</Tabs>\n\n### Solution\n\n1. **Sign in to Azure Portal**\n   Go to [https://portal.azure.com](https://portal.azure.com) and log in with your Azure account.\n\n2. **Navigate to App Registrations**\n   In the top search bar, search for **App registrations** and select it.\n\n3. **Search for your application**\n   Use the **BOT_ID** from your environment file:\n   - Local development → `env/.env.local`\n   - Azure deployment → `env/.env.dev`\n\n4. **Check if a Service Principal exists**\n   Open the app registration and verify if a Service Principal is created. If it exists already, you should see an entry for a **Managed Application in your local directory**.\n\n   ![Screenshot of App Registrations pane in Azure Portal showing value of 'Graphlocal' under the 'Managed application in local directory' field.](/screenshots/existing-service-principal.png)\n\n5. **Create a Service Principal if missing**\n   If it doesn't exist, click **Create Service Principal**. Wait for the page to finish loading.\n\n   ![Screenshot of App Registrations pane in Azure Portal showing value of 'Create Service Principal' under the 'Managed application in local directory' field.](/screenshots/create-service-principal.png)\n\n6. **Restart your app**\n   Once the Service Principal is created, restart your application.\n"
  },
  {
    "path": "teams.md/docs/main/teams/app-authentication/user-managed-identity.md",
    "content": "---\nsidebar_position: 2\ntitle: User Managed Identity Setup\nsummary: Set up User Managed Identity authentication for your Teams bot in Azure Portal or Azure CLI\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# User Managed Identity Authentication Setup\n\nUser Managed Identity authentication eliminates the need for secrets or passwords. A managed identity is created alongside your bot and assigned to your compute resource (App Service, Container App, VM, etc.).\n\n## Prerequisites\n\nBefore you begin, ensure you have:\n- An Azure subscription\n- Permissions to create App Registrations, Azure Bot Services, and manage identities\n- A compute resource where your bot will be hosted (App Service, Container App, VM, etc.)\n\n## Setup Steps\n\n### Step 1: Create Azure Bot with User Managed Identity\n\nWhen creating your Azure Bot Service, select `User Managed Identity` for the `Type of App`.\n\n![User Managed Identity](/screenshots/umi-auth.png)\n\nThis will automatically create a User Managed Identity resource alongside your bot.\n\n### Step 2: Assign the Managed Identity to Your Compute Resource\n\nThe User Managed Identity created with your bot must be assigned to the service running your application.\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n1. Navigate to your compute resource (App Service, Container App, VM, etc.) in the Azure Portal\n2. Go to **Identity** section in the left menu\n3. Select the **User assigned** tab\n4. Click **Add**\n5. Select the User Managed Identity that was created with your Azure Bot\n6. Click **Add** to confirm\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\n```bash\n# Assign user managed identity to your compute resource\n# Example for App Service:\naz webapp identity assign \\\n  --name $APP_NAME \\\n  --resource-group $RESOURCE_GROUP \\\n  --identities $MANAGED_IDENTITY_RESOURCE_ID\n\n# Example for Container App:\naz containerapp identity assign \\\n  --name $APP_NAME \\\n  --resource-group $RESOURCE_GROUP \\\n  --user-assigned $MANAGED_IDENTITY_RESOURCE_ID\n```\n\n</TabItem>\n</Tabs>\n\n## Next Steps\n\nAfter completing the Azure setup, configure your application code with the appropriate environment variables. See the App Authentication Essentials Guide for details.\n"
  },
  {
    "path": "teams.md/docs/main/teams/configuration/README.md",
    "content": "# Basic Configuration\n\nAfter learning about the [core concepts](./../core-concepts) on how to enable an application in Teams, this guide will help you configure the underlying resources correctly in Azure. There are two main ways of doing this integration.\n\n1. Using [M365 Agents Toolkit](./agents-toolkit.md) which uses a CLI/VSCode extension to help automate that work for you.\n2. [Manually Configuration](./manual-configuration.mdx) if you have a setup that deviates from something simple\n"
  },
  {
    "path": "teams.md/docs/main/teams/configuration/_category_.json",
    "content": "{\n  \"position\": 2,\n  \"label\": \"Basic Configuration\",\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/docs/main/teams/configuration/agents-toolkit.md",
    "content": "---\nsidebar_position: 1\nsummary: Automate Teams app development with Microsoft 365 Agents Toolkit for manifest management, sideloading, and deployment.\n---\n\n# Microsoft 365 Agents Toolkit\n\nMicrosoft 365 Agents Toolkit provides a powerful VS Code extension and CLI tool that helps automate important tasks like provisioning and deployment. The project is maintained separately in this repository - [Agents Toolkit GitHub repository](https://github.com/OfficeDev/microsoft-365-agents-toolkit).\n\n## Installing Agents Toolkit\n\nAgents Toolkit can be installed as an extension and CLI. Please see the documentation linked below.\n\n- [Installing Agents Toolkit extension](https://learn.microsoft.com/microsoftteams/platform/toolkit/install-teams-toolkit)\n- [Installing Agents Toolkit CLI](https://learn.microsoft.com/microsoftteams/platform/toolkit/microsoft-365-agents-toolkit-cli)\n\n:::note\n\n- [Teams SDK CLI](../../developer-tools/cli) - helper for Teams SDK. It scaffolds agents, wires in deep Teams features (Adaptive Cards, Conversation History, Memory...etc), and adds all the config files you need while you're coding.\n- Agents Toolkit CLI - app deployment helper. It sideloads, provisions Azure resources, handles manfiest/tenant plumbing, and keeps your dev, test, and prod environments in sync.\n  :::\n\n## Official documentation\n\nRefer to the official [Microsoft 365 Agents Toolkit documentation](https://learn.microsoft.com/microsoft-365/developer/overview-m365-agents-toolkit?toc=%2Fmicrosoftteams%2Fplatform%2Ftoc.json&bc=%2Fmicrosoftteams%2Fplatform%2Fbreadcrumb%2Ftoc.json) on Microsoft Learn.\n\n## Deployment and provisioning\n\nGenerally, you can use the toolkit to add required resources to Azure based on your app manifest setup. For more, see [Add cloud resources and API connection](https://learn.microsoft.com/microsoftteams/platform/toolkit/add-resource).\n\n## Resources\n\n- [Microsoft 365 Agents Toolkit](https://learn.microsoft.com/microsoftteams/platform/toolkit/teams-toolkit-fundamentals): Extensive documentation covering usage and supported scenarios of Agents Toolkit.\n- [Teams SDK CLI documentation](../../developer-tools/cli): Instructions on adding Agents Toolkit configurations to your Teams SDK agent.\n"
  },
  {
    "path": "teams.md/docs/main/teams/configuration/manual-configuration.mdx",
    "content": "---\nsidebar_position: 2\nsummary: Describe how to deploy the Azure Bot Service resource required for Teams bot apps\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Manual Configuration\n\nIf you prefer manually configuring the resources on Azure, and do not want to follow the automated process, you can follow the following guide.\nAs described in the [Core Concepts](../core-concepts) article, the main things required are an App Registration and an Azure Bot.\n\n## Requirements\n\n1. An Azure subscription\n2. Permissions to create Entra ID App registrations. (If you don't have permissions in your tenant, ask your admin to create the App Registration and share the `Application Id`)\n3. Permissions to create Azure Bot Service resources\n4. (Optional) The [Azure CLI](https://aka.ms/azcli) installed and authenticated to your Azure subscription\n\n### Create the Entra App Registration\n\nAfter a successful App Registration you should have the `TenantId`, `ClientId` and `ClientSecret` values, that you will need later.\n\n:::tip\nWe are using Client Secrets authentication here, but it is possible to use other types of authentication. See the [App Authentication](../app-authentication) setup guide for other methods.\n:::\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n1. Navigate to the [Entra Id App Registrations](https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps)\n2. Select `New App Registration` and provide a name. Take note of the assigned `Application Id` (also known as `ClientId`) and `TenantId`\n3. Navigate to `Certificates & secrets` and create `New client secret`\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\n```bash\nbotName=\"My App\"\nappId=$(az ad app create --display-name $botName --sign-in-audience \"AzureADMyOrg\" --query appId -o tsv)\naz ad sp create --id $appId\nappCred=$(az ad app credential reset --id $appId)\ntenantId=$(echo $appCred | jq -r '.tenant')\nclientSecret=$(echo $appCred | jq -r '.password')\n```\n\n</TabItem>\n</Tabs>\n\n### Create the Azure Bot Service resource\n\n:::tip\nYou can create the Azure Bot Service resource and the Entra App Registration from this screen, and then you will have to create a new client secret.\n:::\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n1. Create or select the resource group where you want to create the Azure Bot Resource\n2. In the selected resource group, click Create and search for `bot`.\n3. Select the option `Azure Bot`, and click `Create`\n4. Provide the Bot handle, eg. `MyBot`, select Data Residency and Pricing tier\n   1. Under Microsoft App ID, select `Single Tenant`\n   2. In creation type select `Use existing app registration` and provide the `Application Id` obtained in the previous step\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\nTo run this script, make sure you initialize the variables `resourceGroup`, `tenantId` and `appId` from the previous steps.\n\n```bash\naz bot create \\\n   --name $botName \\\n   --app-type SingleTenant \\\n   --appid $appId \\\n   --tenant-id $tenantId \\\n   --resource-group $resourceGroup\n```\n\n</TabItem>\n</Tabs>\n\n### Configure the Azure Bot Service resource\n\nOnce the Azure Bot Service resource has been created you can configure it. You will need to have set up a public facing endpoint so that messages from your. You can use [DevTunnels](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/overview) if you wish to expose your local servers to public.\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\n\n1. Under `Settings/Configuration` provide the Message endpoint URL, typically it will look like: `https://myapp.mydomain.com/api/messages`\n   1. When using DevTunnels for local development, use the devtunnels hosting URL with the relative path `/api/messages`\n   2. When deploying to a compute instance, such as App Services, Container Apps, or in other Cloud, use the public hostname with the relative path `/api/messages`\n2. In `Settings/Channels` enable the `Microsoft Teams` channel.\n\n</TabItem>\n<TabItem value=\"cli\" label=\"Azure CLI\">\n\n```bash\nendpointUrl=<your-devtunnels-public-url>\naz bot update \\\n   --name $botName \\\n   --resource-group $resourceGroup \\\n   --endpoint $endpointUrl\n\naz bot msteams create \\\n    --name $botName \\\n    --resource-group $resourceGroup\n```\n\n</TabItem>\n</Tabs>\n\n## Save the credentials to use as configuration\n\n```bash\necho \"TENANT_ID=$tenantId\" > \"$botName.env\"\necho \"CLIENT_ID=$appId\" >> \"$botName.env\"\necho \"CLIENT_SECRET=$clientSecret\" >> \"$botName.env\"\n```\n\n## Resources\n\n- [Teams App Publishing overview](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-publish-overview)\n"
  },
  {
    "path": "teams.md/docs/main/teams/core-concepts.md",
    "content": "---\nsidebar_position: 1\nsummary: Understand Teams app architecture including app registration, Azure Bot Service, DevTunnel, and sideloading processes.\n---\n\n# Teams Core Concepts\n\nWhen you run your agent on Teams using Microsoft 365 Agents Toolkit, several Teams-specific processes happen behind the scenes. Understanding these components will help you better debug and deploy your agents. Obviously, all these processes can be done manually, but Agents Toolkit automates them for you.\n\n## Basic Flow\n\n```mermaid\nflowchart LR\n    %% Main actors\n    User([User])\n\n    %% Teams section\n    subgraph Teams [\"Teams\"]\n        TeamsClient[\"Teams Client\"]\n        TeamsBackend[\"Teams Backend\"]\n    end\n\n    %% Azure section\n    subgraph Azure [\"Azure\"]\n        AppReg[\"App Registration\"]\n        AzureBot[\"Azure Bot\"]\n    end\n\n    %% Local Server section\n    subgraph LocalServer [\"Local Server\"]\n        DevTunnel[\"DevTunnel\"]\n        LocalApp[\"Your local application\"]\n    end\n\n    %% Deployed Server section\n    subgraph DeployedServer [\"Deployed Server\"]\n        DeployedApp[\"Your deployed application\"]\n    end\n\n    %% Define connections\n    User <--> TeamsClient\n    TeamsClient <--> TeamsBackend\n    TeamsBackend <--> AppReg\n    AppReg <--> AzureBot\n    AzureBot --> LocalServer\n    AzureBot --> DeployedServer\n```\n\n**Teams**\n\n- Teams Client: User-facing agent that interacts with the user.\n- Teams Backend: Part of your app package; includes a manifest with your app’s client ID.\n\n**Azure**\n\n- App Registration: Contains a unique client ID and secret for your app.\n- Azure Bot: Connects your app to Teams; contains a pointer to your HTTPS URL.\n\n**Local Server**\n\n- Dev Tunnel: Public-facing HTTPS tunnel to expose your local machine.\n- Local App: Your application running locally; handles events from Teams and sends responses.\n\n**Deployed Server**\n\n- Deployed App: Your app deployed to the cloud with a public HTTPS endpoint; also interacts with Teams.\n\n## Core Concepts\n\nWhen working with Teams, these are the key concepts. Keep in mind, this is a simplified view of the architecture.\n\n- Teams Client: This is the Teams application where users interact with your agent. This can be the desktop app, web app, or mobile app.\n- Teams Backend: This service handles all the Teams-related operations, including keeping a record of your manifest, and routing messages from your agent to the Azure bot service.\n- App Registration: This is the registration of your agent in Azure. This Application Registration issues a unique client ID for your application and a client secret. This is used to authenticate your agent application with the Teams backend and other Azure services (including Graph if you are using it).\n- Azure Bot Service: This is the service that handles all the bot-related operations, including routing messages from Teams to your agent and vice versa. This holds the URL to your agent application.\n- DevTunnel: This is a service that creates a public facing URL to your locally running application. Azure Bot Service requires that you have a public facing https URL to your agent application.\n- Local Agent Application: This is your agent application running on your local machine.\n- Deployed Agent Application: This is your deployed agent which probably has a public facing URL.\n\n## DevTunnel\n\n[DevTunnel](https://learn.microsoft.com/en-us/azure/developer/dev-tunnels/overview) is a critical component that makes your locally running agent accessible to Teams. When you set up a DevTunnel, it:\n\n- Creates a secure public HTTPS endpoint that forwards to your local server\n- Manages SSL certificates automatically\n- Routes Teams messages and events to your local agent\n\n:::info\nDevTunnel is only one way of exposing your locally running service to the internet. Other tools like ngrok can also accomplish the same thing.\n:::\n\n## Teams App Provisioning\n\nBefore your agent can interact with Teams, it needs to be properly registered and configured. This step handles creating or updating the App Registration and creating or registering the Azure Bot instance in Azure.\n\n### App Registration\n\n- Creates an App ID (i.e. Client ID) in the Teams platform\n- Sets up a bot registration with the Bot Framework\n- Creates a client secret that your agent can use to authenticate to be able to send and receive messages. Agents Toolkit will automatically get this value and store it in the `.env` file for you.\n\n### Azure Bot\n\n- Creates an Azure Bot resource\n- Associates the bot with your App Registration\n- Configures the messaging endpoint to point to your DevTunnel (or public URL if deployed)\n\n## Sideloading Process\n\nSideloading is the process of installing your agent in Teams. You are able to pass in the manifest and icons (zipped up) to the Teams client. Sideloading an application automatically makes that application available to you. You are also able to sideload the application in a Team or a Group chat. In this case, the application will be available to all members of that Team or Group chat.\n\n:::warning\nSideloading needs to be enabled in your tenant. If this is not the case, then you will need to contact your Teams administrator to enable it.\n:::\n\n## Provisioning and Deployment\n\nTo test your app in Teams, you will at minimum need to have a provisioned Azure bot. You are likely to have other provisionied resources such as storage. Please see the Microsoft Learn [Provision cloud resources](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/provision) documentation for provisioning and deployment using Visual Studio Code and to a container service."
  },
  {
    "path": "teams.md/docs/main/teams/enabling-in-copilot.md",
    "content": "---\nsidebar_position: 6\nsummary: Learn how to enable your Teams app to work in M365 Copilot by updating the app manifest.\n---\n\n# Enabling in M365 Copilot\n\nIf you've built a Teams app or agent and want to make it available in M365 Copilot, you can easily do so by updating your app manifest. This allows users to interact with your agent through Copilot's interface.\n\n## Prerequisites\n\nBefore enabling your app in Copilot, ensure you have:\n\n1. A working Teams app or agent\n2. Completed the app registration process (see \"Running in Teams\" for your language)\n3. Your `BOT_ID` from the app registration\n\n## Updating the Manifest\n\nTo enable your app in Copilot, add the following configuration to your `manifest.json` file:\n\n```json\n\"copilotAgents\": {\n  \"customEngineAgents\": [\n    {\n      \"type\": \"bot\",\n      \"id\": \"${{BOT_ID}}\"\n    }\n  ]\n}\n```\n\nThe `BOT_ID` is assigned to your agent after you have registered your application. If you followed the \"Running in Teams\" guide for your language, this ID should already be available in your environment configuration.\n\n## Location of the Manifest\n\nThe manifest file is typically located in the `appPackage` folder of your project. If you used the Teams CLI to set up your project, this folder was automatically created for you.\n\n### Example Manifest Structure\n\nHere's how the `copilotAgents` section fits into the overall manifest structure:\n\n```json\n{\n  \"$schema\": \"https://developer.microsoft.com/en-us/json-schemas/teams/v1.19/MicrosoftTeams.schema.json\",\n  \"manifestVersion\": \"1.19\",\n  \"version\": \"1.0.0\",\n  \"id\": \"${{APP_ID}}\",\n  \"developer\": {\n    \"name\": \"Your Company\",\n    \"websiteUrl\": \"https://www.example.com\",\n    \"privacyUrl\": \"https://www.example.com/privacy\",\n    \"termsOfUseUrl\": \"https://www.example.com/terms\"\n  },\n  \"name\": {\n    \"short\": \"Your App Name\",\n    \"full\": \"Your Full App Name\"\n  },\n  \"description\": {\n    \"short\": \"Short description\",\n    \"full\": \"Full description of your app\"\n  },\n  \"icons\": {\n    \"outline\": \"outline.png\",\n    \"color\": \"color.png\"\n  },\n  \"accentColor\": \"#FFFFFF\",\n  \"bots\": [\n    {\n      \"botId\": \"${{BOT_ID}}\",\n      \"scopes\": [\n        \"personal\",\n        \"team\",\n        \"groupchat\"\n      ],\n      \"supportsFiles\": false,\n      \"isNotificationOnly\": false\n    }\n  ],\n  \"copilotAgents\": {\n    \"customEngineAgents\": [\n      {\n        \"type\": \"bot\",\n        \"id\": \"${{BOT_ID}}\"\n      }\n    ]\n  }\n}\n```\n\n## Regenerating the App Package\n\nAfter updating the manifest, you need to zip the manifest and icon files into an app package:\n\n### Using Microsoft 365 Agents Toolkit\n\nIf you're using the Microsoft 365 Agents Toolkit, the app package is automatically regenerated when you build or debug your app. The toolkit will:\n\n1. Read your updated manifest\n2. Replace variables like `${{BOT_ID}}` with actual values from your environment\n3. Package the manifest and icons into a zip file\n4. Deploy the updated package to Teams\n\n### Manual Packaging\n\nIf you're manually packaging your app:\n\n1. Ensure the `manifest.json` file is updated with the `copilotAgents` section\n2. Create a zip file containing:\n   - `manifest.json`\n   - `color.png` (192x192 icon)\n   - `outline.png` (32x32 icon)\n3. Upload the zip file to Teams\n\n## Testing in Copilot\n\nOnce you've updated and redeployed your app:\n\n1. Open M365 Copilot in Teams or at [m365.cloud.microsoft](https://m365.cloud.microsoft/)\n2. Your app should now be available as an agent option\n3. Interact with your agent through the Copilot interface\n\n## Resources\n\n- [Convert Your Declarative Agent for Microsoft 365 Copilot to a Custom Engine Agent](https://learn.microsoft.com/en-us/microsoft-365-copilot/extensibility/convert-declarative-agent)\n- [Teams app manifest reference](https://learn.microsoft.com/microsoftteams/platform/resources/schema/manifest-schema)\n"
  },
  {
    "path": "teams.md/docs/main/teams/manifest.md",
    "content": "---\nsidebar_position: 5\nsummary: Learn about Teams app manifest requirements, permissions, and sideloading process for app installation.\n---\n\n# Teams Manifest\n\nEvery app or agent installed on Teams requires an app manifest json file, which provides important information and permissions to that app. When sideloading the app, you are required to provide the app manifest via zip which also includes the icons for the app.\n\n## Manifest\n\nThere are many permissions and details that an app manifest may have added to the `manifest.json`, including the app ID, url, and much more. Please review the comprehensive documentation on the [manifest schema](https://learn.microsoft.com/microsoft-365/extensibility/schema/).\n\n## Sideloading\n\nSideloading is the ability to install and test your app before it is published to your organization's app catalog. For more on sideloading, see [Upload your apps to Teams](https://learn.microsoft.com/microsoftteams/platform/concepts/deploy-and-publish/apps-upload).\n\nTo sideload, ensure the manifest includes all required information (such as the app ID, tenant details, and permissions). Place the manifest and icons at the root of a zip file.\n\nFor convenient assistance with managing your manifest and automating important functionality like sideloading, deployment, and provisioning, we recommend the [Microsoft 365 Agents Toolkit extension](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/install-teams-toolkit)) and [CLI](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/microsoft-365-agents-toolkit-cli). Please continue to the [Toolkit documentation](./configuration/agents-toolkit) to learn more.\n"
  },
  {
    "path": "teams.md/docs/main/teams/user-authentication/README.md",
    "content": "---\nsidebar_position: 4\nsummary: Overview of user authentication in Teams SDK applications, including OAuth, SSO, and secure resource access.\n---\n\n# User Authentication\n\nAt times, agents must access secured online resources on behalf of the user, such as checking email, checking flight status, or placing an order. To enable this, the user must authenticate their identity and grant consent for the application to access these resources. This process results in the application receiving a token, which the application can then use to access the permitted resources on the user's behalf.\n\n## How Auth Works\n\nWhen building Teams applications, choosing the right authentication method is crucial for both security and user experience. Teams supports two primary authentication approaches: OAuth and Single Sign-On (SSO). While both methods serve the same fundamental purpose of validating user identity, they differ significantly in their implementation, supported identity providers, and user experience. Understanding these differences is essential for making the right choice for your application.\n\nThe following table provides a clear comparison between OAuth and SSO authentication methods, highlighting their key differences in terms of identity providers, authentication flows, and user experience.\n\n### Single Sign-On (SSO)\n\nSingle Sign-On (SSO) in Teams provides a seamless authentication experience by leveraging a user's existing Teams identity. Once a user is logged into Teams, they can access your app without needing to sign in again. The only requirement is a one-time consent from the user, after which your app can securely retrieve their access details from Microsoft Entra ID. This consent is device-agnostic - once granted, users can access your app from any device without additional authentication steps.\n\nWhen an access token expires, the app automatically initiates a token exchange flow. In this process:\n\n1. The Teams client sends an OAuth ID token containing the user's information\n2. Your app exchanges this ID token for a new access token with the previously consented scopes\n3. This exchange happens silently without requiring user interaction\n\n:::tip\nAlways use SSO if you're authenticating the user with Microsoft Entra ID.\n:::\n\n#### The SSO Signin Flow\n\nThe SSO signin flow involves several components working together. Here's how it works:\n\n1. User interacts with your bot or message extension app\n2. App initiates the signin process\n3. If it's the first time:\n   - User is shown a consent form for the requested scopes\n   - Upon consent, Microsoft Entra ID issues an access token (in simple terms)\n4. For subsequent interactions:\n   - If token is valid, app uses it directly\n   - If token expires, app silently signs the user in using the token exchange flow\n\nThis is what the SSO consent form looks like in Teams:\n\n![SSO Consent Form](/screenshots/auth-consent-popup.png)\n\n### OAuth\n\nYou can use a third-party OAuth Identity Provider (IdP) to authenticate your app users. The app user is registered with the identity provider, which has a trust relationship with your app. When the user attempts to log in, the identity provider validates the app user and provides them with access to your app. Microsoft Entra ID is one such third party OAuth provider. You can use other providers, such as Google, Facebook, GitHub, or any other provider.\n\n#### The OAuth Signin Flow\n\nThe OAuth signin flow involves several components working together. Here's how it works:\n\n1. User interacts with your bot or message extension app\n2. App sends a sign-in card with a link to the OAuth provider\n3. User clicks the link and is redirected to the provider's authentication page\n4. User authenticates and grants consent for requested scopes\n5. Provider issues an access token to your app (in simple terms)\n6. App uses the token to access services on user's behalf\n\nWhen an access token expires, the user will need to go through the sign-in process again. Unlike SSO, there is no automatic token exchange - the user must explicitly authenticate each time their token expires.\n\n#### The OAuth Card\n\nThis is what the OAuth card looks like in Teams:\n\n![OAuthCard](/screenshots/auth-explicit-signin.png)\n\n## OAuth vs SSO - Head-to-Head Comparison\n\nThe following table provides a clear comparison between OAuth and SSO authentication methods, highlighting their key differences in terms of identity providers, authentication flows, and user experience.\n\n| Feature                                                | OAuth                                                                              | SSO                                                                                                                                                                      |\n| ------------------------------------------------------ | ---------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| Identity Provider                                      | Works with any OAuth provider (Microsoft Entra ID, Google, Facebook, GitHub, etc.) | Only works with Microsoft Entra ID                                                                                                                                       |\n| Authentication Flow                                    | User is sent a card with a sign-in link                                            | If the user has already consented to the requested scopes in the past they will \"silently\" login through the token exchange flow. Otherwise user is shown a consent form |\n| User Experience                                        | Requires explicit signin, and consent to scopes                                    | Re-use existing Teams credential. Only requires consent to scopes                                                                                                        |\n| Conversation scopes (`personal`, `groupChat`, `teams`) | `personal` scope only                                                              | `personal` scope only                                                                                                                                                    |\n| Azure Configuration differences                        | Same configuration except `Token Exchange URL` is blank                            | Same configuration except `Token Exchange URL` is set                                                                                                                    |\n"
  },
  {
    "path": "teams.md/docs/main/teams/user-authentication/_category_.json",
    "content": "{\n  \"label\": \"User Authentication Configuration\",\n    \"collapsed\": true,\n    \"position\": 4,\n}\n"
  },
  {
    "path": "teams.md/docs/main/teams/user-authentication/sso-setup.mdx",
    "content": "---\nsidebar_position: 1\nsummary: Describes how to configure SSO in Teams\n---\n\n# SSO Setup\n\nThis section describes how to configure the Azure Bot Service (ABS), the Entra App Registration and the Teams manifest to enable Single-Sign-On (SSO) for your Teams app.\n\n## Create the Entra App Registration\n\nYou need an Entra ID App Registration to configure the OAuth Connection in Azure Bot Service. This can be the same EntraId app registration you used to configure your ABS resource or a new App created just for this purpose. You can create the App Registration from the Azure portal, or the CLI, the next list summarizes the creation steps from the portal.\n\n1. Use the existing App registration, or Create a new App registration from the [Entra Id](https://portal.azure.com/#view/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/~/RegisteredApps) section in the Azure portal. Now you have an Application Id (also known as Client ID) and a Tenant Id.\n2. Provide a name for the app registration, select SingleTenant, and for the Redirect URI select the platform Web and add the value `https://token.botframework.com/.auth/web/redirect`\n\n![Entra new app](/screenshots/entra-new-app.png)\n\n3. Add a new client secret. From `Certificates & secrets`, select `Client secrets` and add `New client secret`. Take note of the secret as you will need the value later on this guide.\n\n![Entra client secret](/screenshots/entra-client-secret.png)\n\n4. Configure the API. From `Expose an API`,  Click `Add` to Application ID URI and accept the default value that will look like `api://<Your-Application-Id>`. Add the scope `access_as_user` and select who can _consent_.\n\n![Entra oauth scopes](/screenshots/entra-oauth-scopes.png)\n\n5. Authorize the client applications for SSO. To enable the Teams clients, desktop and web, to perform the SSO flow you must add the next client applications to the scope defined before: Teams Desktop `1fec8e78-bce4-4aaf-ab1b-5451cc387264` and Teams Web `5e3ce6c0-2b1f-4285-8d4b-75ee78787346`\n\n![Entra oauth authorize client app](/screenshots/entra-authorize-clientapp.png)\n\n### Configure the Entra App Registration with the CLI\n\n```bash\n#!/bin/bash\n\naz ad app update --id $appId --web-redirect-uris \"https://token.botframework.com/.auth/web/redirect\"\naz ad app update --id $appId --identifier-uris \"api://$appId\"\n# TODO: add oauthpermission settings and client applications.\n```\n\n## Create the OAuth connection in Azure Bot Service\n\nYou need to add a new OAuth connection to your Azure Bot Service resource.\n\n1. From the Bot service resource in the Azure Portal, navigate to `Settings/Configuration` and `Add OAuth Connection settings`.\n2. Provide a name for your connection e.g. `graph`, and select the _Service Provider_ `Azure Active Directory v2`\n3. Populate the `TenantId`/`ClientId`/`ClientSecret` from the values obtained in the previous section steps 2 and 3. Configure the Token Exchange URL with the Application ID URI configured in step 4, and add the Scopes you need e.g. `User.Read`\n\n![ABS OAuth connection](/screenshots/abs-oauth-connection.png)\n\n### Create the OAuth connection using the Azure CLI\n\n```bash\n#!/bin/bash\n\naz bot authsetting create \\\n  --resource-group $resourceGroup \\\n  --name $botName \\\n  -c \"graph\" \\\n  --client-id $appId \\\n  --client-secret $clientSecret \\\n  --provider-scope-string \"User.Read\" \\\n  --service \"Aadv2\" \\\n  --parameters \"clientId=$appId\" \"clientSecret=$clientSecret\" \"tenantId=$tenantId\" \"tokenExchangeUrl=api://$appId\"\n```\n\n\n## Configure the App Manifest\n\nThe Teams application manifest needs to be updated to reflect the settings configure above, with the `Application Id` and `Application ID URI`, if not using `devtunnels`, replace the valid domain with the domain hosting your application. \n\n```json\n\"validDomains\": [\n    \"*.devtunnels.ms\",\n    \"*.botframework.com\",\n ],\n \"webApplicationInfo\": {\n    \"id\": \"<Your-Application-Id>\",\n    \"resource\": \"api://<Your-Application-Id>\"\n  }\n```\n\n## Troubleshooting\n\nIf you encounter SSO errors, see the [Troubleshooting](troubleshooting-sso) guide for common issues and solutions.\n\n"
  },
  {
    "path": "teams.md/docs/main/teams/user-authentication/troubleshooting-sso.mdx",
    "content": "---\nsidebar_position: 2\ntitle: Troubleshooting\nsummary: Common SSO errors and how to resolve them\n---\n\nimport LangLink from '@site/src/components/LangLink';\n\n# SSO Troubleshooting\n\nWhen SSO fails, Teams sends a `signin/failure` invoke activity to your bot with a `code` and `message` describing the error. The SDK's default handler logs a warning with these details.\n\n## Failure codes\n\n| Code | Silent | Description |\n|------|--------|-------------|\n| `installappfailed` | No | Failed to install the app in the user's personal scope (group chat SSO flow). |\n| `authrequestfailed` | No | The SSO auth request failed after app installation. |\n| `installedappnotfound` | Yes | The bot app is not installed for the user or group chat. *(most common)* |\n| `invokeerror` | Yes | A generic error occurred during the SSO invoke flow. |\n| `resourcematchfailed` | Yes | The token exchange resource URI on the OAuthCard does not match the Application ID URI in the Entra app registration's \"Expose an API\" section. *(common)* |\n| `oauthcardnotvalid` | Yes | The bot's OAuthCard could not be parsed. |\n| `tokenmissing` | Yes | AAD token acquisition failed. |\n\n\"Silent\" failures produce no user-facing feedback in the Teams client — the user sees nothing and sign-in simply doesn't complete. \"Non-silent\" failures occur during the group chat SSO flow where the user is shown an install/auth card.\n\n:::note\nThe `userconsentrequired` and `interactionrequired` codes are handled by the Teams client via the OAuth card fallback flow and do not typically reach the bot.\n:::\n\n## `resourcematchfailed`\n\nIf you see a warning in your app logs like:\n\n> Sign-in failed for user \"...\" in conversation \"...\": resourcematchfailed -- Resource match failed\n\nThis means Teams attempted the SSO token exchange but failed because the token exchange resource URI does not match your Entra app registration. To fix this:\n\n1. **Verify \"Expose an API\"** in your Entra app registration: the Application ID URI must be set (typically `api://<Your-Application-Id>`)\n2. **Verify the `access_as_user` scope** is defined under \"Expose an API\"\n3. **Verify pre-authorized client applications** include the Teams Desktop (`1fec8e78-bce4-4aaf-ab1b-5451cc387264`) and Teams Web (`5e3ce6c0-2b1f-4285-8d4b-75ee78787346`) client IDs\n4. **Verify the Token Exchange URL** in your Azure Bot OAuth connection matches the Application ID URI exactly\n5. **Verify the `webApplicationInfo.resource`** in your Teams app manifest matches the Application ID URI\n\n:::tip\nIf you don't need SSO and only want standard OAuth (sign-in button), leave the **Token Exchange URL** blank in your OAuth connection settings.\n:::\n\nTo handle `signin/failure` programmatically in your app, see <LangLink to=\"in-depth-guides/user-authentication#handling-sign-in-failures\">Handling Sign-In Failures</LangLink> in the User Authentication guide.\n"
  },
  {
    "path": "teams.md/docs/main/welcome.mdx",
    "content": "---\nsidebar_position: 0\nsummary: Welcome guide to Teams SDK, covering the quickstart process and how to build agents and applications for Microsoft Teams.\nllms: ignore\n---\n\nimport LangLink from '@site/src/components/LangLink';\n\n# 👋 Welcome\n\nTeams SDK is a suite of packages used to develop on Microsoft Teams. Rebuilt from the ground up with improved developer experience in mind, it's never been easier to build powerful agents and applications for the hundreds of millions Microsoft Teams users.\n\n## Quickstart\n\nThe Teams CLI makes it easy to bootstrap your first agent. Using `npx @microsoft/teams.cli` you can get started using these commands:\n\n**TypeScript:**\n\n```bash\nnpx @microsoft/teams.cli@latest new typescript quote-agent --template echo\n```\n\n**C#:**\n\n```bash\nnpx @microsoft/teams.cli@latest new csharp quote-agent --template echo\n```\n\n**Python:**\n\n```bash\nnpx @microsoft/teams.cli@latest new python quote-agent --template echo\n```\n\n## Overview\n\nMicrosoft Teams has a robust developer ecosystem with a broad suite of capabilities, now unified via Teams SDK. Whether you are building <LangLink to=\"in-depth-guides/ai\">AI-powered agents</LangLink>, <LangLink to=\"in-depth-guides/message-extensions\">message extensions</LangLink>, embedded web applications, or Graph, Teams SDK has you covered.\n\n## ⭐ What's new?\n\n### Streamlined Developer Experience\n\nWe’ve refined the developer experience so you can concentrate on building your app’s logic — not wrestling with integrations.\n\n### Effortless Integration\n\nWe’ve simplified complex integration workflows to help you deliver a richer, more seamless user experience.\n\n### Accelerate Your Workflow\n\nGet your application up and running in under 30 seconds with our lightning-fast CLI—so you can spend more time on what really matters.\n\n## 🔎 Navigation Tips\n\nWe encourage you to use the left sidebar to navigate to your desired section.\n\nCan't find what you're searching for? Use the search button above.\n"
  },
  {
    "path": "teams.md/docs/main/why.md",
    "content": "---\nsidebar_position: 1\nsummary: Explanation of why an SDK is beneficial for building Teams agent applications, covering event handling and proactive messaging patterns.\nllms: ignore\n---\n\n# Why An SDK?\n\nBefore getting into the basics, it's important to understand how an SDK can be helpful when building an agent application. For this, it's a good exercise to understand the basic messaging and event flow of a Teams agent application.\n\n---\n\nAn agent application is mainly able to do two things:\n\n- Listen to events and respond to them\n- Proactively send messages to the user\n\n```mermaid\nflowchart LR\n    Teams([Teams])\n    InputEvents[\"Input Events\"]\n    OutputEvents[\"Output Events\"]\n    Application([\"Your application\"])\n\n    Teams --> InputEvents\n    OutputEvents --> Teams\n    InputEvents --> Application\n    Application --> OutputEvents\n\n    %% Styling for dark/light compatibility\n    style Teams fill:#2E86AB,stroke:#1B4F72,stroke-width:2px,color:#ffffff\n    style Application fill:#28B463,stroke:#1D8348,stroke-width:2px,color:#ffffff\n```\n\nTo do this, we already need a few components:\n\n1. A public facing URL to our agent application - This is so that the Teams backend knows where to send messages to when an interesting event happens.\n2. A unique identifier for our agent application - Teams doesn't like to pass around this URL everywhere. Instead it hides this information behind a unique ID. This way, if your URL changes, all you need to do is update the URL and keep the ID the same.\n3. A way to authenticate to and from the Teams backend - This public facing URL may get hit in many different ways. We need some protections to make sure that the only messages that reach our main application are authenticated.\n\n```mermaid\nflowchart LR\n    Start(\"Start\")\n    Teams([Teams])\n    PublicFacingUrl[\"Public Facing Endpoint\"]\n    TeamsURL[\"Teams Endpoint\"]\n    AuthInput[\"Authentication\"]\n    AuthOutput[\"Authentication\"]\n    InputEvents[\"Input Events\"]\n    OutputEvents[\"Output Events\"]\n    Application([\"Your application logic\"])\n\n    Start --> Teams\n    subgraph TeamsEcosystem[\"Teams Ecosystem\"]\n        Teams --> InputEvents\n    end\n    subgraph App[\"Application with Unique Id\"]\n        InputEvents --> PublicFacingUrl\n        PublicFacingUrl --> AuthInput --> Application\n        Application -.-> AuthOutput\n        AuthOutput -.-> OutputEvents\n    end\n    subgraph TeamsEcosystem[\"Teams Ecosystem\"]\n        OutputEvents --> TeamsURL --> Teams\n    end\n\n    %% Styling for dark/light compatibility\n    style Teams fill:#2E86AB,stroke:#1B4F72,stroke-width:2px,color:#ffffff\n    style Application fill:#28B463,stroke:#1D8348,stroke-width:2px,color:#ffffff\n```\n\nNext, once a request is successfully authenticated, there is a _slew_ of possible types of events that can be sent to your agent application. User messages, user reactions, installation events, Adaptive Card actions, dialog actions, and more. All of these get to your application through a single public door - the public facing URL. Not only this, but different types of events may expect a particular type of response back. For example, a message event may expect a text response or an Adaptive Card response, while a reaction event may not expect a response at all.\n\nNow, it's possible for your own application to handle all the nuances with these events, but that would be a lot of work, and a lot of boilerplate code. Think, a gigantic switch statement at the very least.\n\n```mermaid\nblock-beta\n    columns 5\n    block:InputEventsGroup:2\n        columns 1\n        InputEvents(\"Input Events\"):1\n        block:group2:1\n            columns 2\n            UserMessaged\n            UserReacted\n            FormSubmitted\n            Etc1[\"...\"]\n        end\n    end\n    space:1\n    block:AppHandlersGroup:2\n        columns 1\n        Handlers(\"Application Handlers\"):1\n        block:group4:1\n            columns 2\n            UserMessageH[\"User Messaged<br/>handler\"]\n            UserReactedH[\"User Reacted<br/>handler\"]\n            FormSubmittedH[\"Form Submitted<br/>handler\"]\n            Etc2[\"...\"]\n        end\n    end\n    InputEventsGroup --> AppHandlersGroup\n```\n\nNext, if you wanted to send messages to the user, you would need to make sure each call to the Teams backend is authenticated appropriately for your application.\n\nIf your application wanted additional data from [Microsoft Graph](https://learn.microsoft.com/en-us/graph/overview), you would need to authenticate to that as well. Additionally, if you wanted the _user_ to authenticate and query Graph on their behalf, you would have to set up a solution to do the OAuth flow as well. For these complex flows, Teams offers a solution, but you must adhere to specific protocols and patterns to facilitate them.\n\n```mermaid\nblock-beta\n    columns 4\n    space:2\n    block:userAuth:2\n        columns 4\n        Graph:1\n        Github:1\n        Google:1\n        Etc[\"...\"]:1\n        space:4\n        UserAuth[\"User OAuth/SSO\"]:4\n        UserAuth-->Graph\n        UserAuth-->Github\n        UserAuth-->Google\n        UserAuth-->Etc\n    end\n    block:events:2\n        columns 2\n        blockArrowInput<[\"InputEvents\"]>(right)\n        InputAuthentication[\"Input Auth\"]\n        blockArrowOutput<[\"OutputEvents\"]>(left)\n        OutputAuthentication[\"Output Auth\"]\n    end\n\n    Application:2\n\n    style Application fill:#28B463,stroke:#1D8348,stroke-width:2px,color:#ffffff\n```\n\nAs you can see, there are a lot of moving parts to building a Teams agent application. This is where the SDK comes in. The SDK abstracts away all of the boilerplate code and provides you with a simple interface to work with. It handles all the authentication, routing, and event handling for you, so you can focus on building your application.\n"
  },
  {
    "path": "teams.md/docusaurus.config.ts",
    "content": "import type * as Preset from '@docusaurus/preset-classic';\nimport type { Config } from '@docusaurus/types';\nimport path from 'node:path';\nimport { themes as prismThemes } from 'prism-react-renderer';\n\n// This runs in Node.js - Don't use client-side code here (browser APIs, JSX...)\nconst baseUrl = '/teams-sdk/';\nconst config: Config = {\n    title: 'Teams SDK',\n    favicon: 'img/msft-logo-48x48.png',\n\n    // Set the production url of your site here\n    url: 'https://microsoft.github.io/',\n    // Set the /<baseUrl>/ pathname under which your site is served\n    // For GitHub pages deployment, it is often '/<projectName>/'\n    baseUrl,\n\n    // GitHub pages deployment config.\n    // If you aren't using GitHub pages, you don't need these.\n    organizationName: 'microsoft', // Usually your GitHub org/user name.\n    projectName: baseUrl, // Usually your repo name.\n\n    onBrokenLinks: 'throw',\n\n    // Even if you don't use internationalization, you can use this field to set\n    // useful metadata like html lang. For example, if your site is Chinese, you\n    // may want to replace \"en\" with \"zh-Hans\".\n    i18n: {\n        defaultLocale: 'en',\n        locales: ['en'],\n    },\n\n    markdown: {\n        mermaid: true,\n        hooks: {\n            onBrokenMarkdownLinks: 'throw',\n        },\n    },\n    headTags: [\n        {\n            tagName: 'link',\n            attributes: {\n                rel: 'llms.txt',\n                href: 'https://microsoft.github.io/teams-sdk/llms_docs/llms.txt'\n            }\n        }\n    ],\n    scripts: [path.join(baseUrl, '/scripts/clarity.js')],\n\n    presets: [\n        [\n            'classic',\n            {\n                blog: false,\n                docs: {\n                    routeBasePath: '/',\n                    path: 'docs/main',\n                    sidebarPath: './sidebars.ts',\n                    sidebarCollapsed: false,\n                    editUrl: 'https://github.com/microsoft/teams-sdk/tree/main/teams.md/',\n                    // Temporary exclude until generate-LLMs script is fully tested\n                    exclude: ['**/LLMs.md'],\n                },\n                pages: {\n                    exclude: ['**/templates/**'],\n                },\n                theme: {\n                    customCss: ['./src/css/custom.css', './src/css/code-blocks.css'],\n                },\n            } satisfies Preset.Options,\n        ],\n    ],\n\n    themes: [\n        '@docusaurus/theme-mermaid',\n        [\n            require.resolve('@easyops-cn/docusaurus-search-local'),\n            /** @type {import(\"@easyops-cn/docusaurus-search-local\").PluginOptions} */\n            {\n                hashed: true,\n                language: ['en'],\n                docsRouteBasePath: ['/', '/typescript', '/csharp', '/python'],\n                indexDocs: true,\n                indexPages: true,\n                highlightSearchTermsOnTargetPage: true,\n            },\n        ],\n    ],\n    themeConfig: {\n        colorMode: {\n            respectPrefersColorScheme: true,\n        },\n        navbar: {\n            title: 'Teams SDK',\n            hideOnScroll: true,\n            logo: {\n                alt: 'Teams SDK',\n                src: 'img/teams.png',\n            },\n            items: [\n                {\n                    href: 'https://github.com/microsoft/teams-sdk/tree/main',\n                    position: 'right',\n                    className: 'header-github-link',\n                },\n            ],\n        },\n        footer: {\n            style: 'dark',\n            links: [\n                {\n                    title: 'Docs',\n                    items: [\n                        {\n                            label: 'Getting Started',\n                            to: '/',\n                        },\n                        {\n                            label: 'TypeScript',\n                            to: '/typescript/getting-started',\n                        },\n                        {\n                            label: 'C#',\n                            to: '/csharp/getting-started',\n                        },\n                        {\n                            label: 'Python',\n                            to: '/python/getting-started',\n                        },\n                        {\n                            label: 'Privacy policy',\n                            to: '/privacy',\n                        },\n                    ],\n                },\n                {\n                    title: 'More',\n                    items: [\n                        {\n                            label: 'GitHub',\n                            href: 'https://github.com/microsoft/teams-sdk/tree/main',\n                        },\n                        {\n                            label: 'Contributing',\n                            href: 'https://github.com/microsoft/teams-sdk/blob/main/CONTRIBUTING.md',\n                        },\n                        {\n                            label: 'Blog',\n                            href: 'https://devblogs.microsoft.com/microsoft365dev/announcing-the-updated-teams-ai-library-and-mcp-support/',\n                        },\n                        {\n                            label: 'Teams agent accelerator templates',\n                            href: 'https://microsoft.github.io/teams-agent-accelerator-templates/',\n                        },\n                    ],\n                },\n            ],\n            copyright: `Copyright © ${new Date().getFullYear()} Microsoft Corporation. All rights reserved.`,\n        },\n        prism: {\n            theme: prismThemes.dracula,\n            darkTheme: prismThemes.vsDark,\n            magicComments: [\n                {\n                    className: 'theme-code-block-highlighted-line',\n                    line: 'highlight-next-line',\n                    block: {\n                        start: 'highlight-start',\n                        end: 'highlight-end',\n                    },\n                },\n                {\n                    className: 'code-block-error-line',\n                    line: 'highlight-error-line',\n                    block: {\n                        start: 'highlight-error-start',\n                        end: 'highlight-error-end',\n                    },\n                },\n                {\n                    className: 'code-block-success-line',\n                    line: 'highlight-success-line',\n                    block: {\n                        start: 'highlight-success-start',\n                        end: 'highlight-success-end',\n                    },\n                },\n            ],\n            additionalLanguages: [\n                'typescript',\n                'javascript',\n                'csharp',\n                'python',\n                'bash',\n                'markdown',\n                'json',\n            ],\n        },\n        announcementBar: {\n            id: 'teams-sdk-rename',\n            content: 'We have been renamed to Teams SDK! 🎉 🥳',\n            isCloseable: true,\n            backgroundColor: '#515cc6',\n            textColor: '#fff'\n        },\n    } satisfies Preset.ThemeConfig,\n};\n\nexport default config;\n"
  },
  {
    "path": "teams.md/i18n/en/code.json",
    "content": "{\n  \"theme.ErrorPageContent.title\": {\n    \"message\": \"This page crashed.\",\n    \"description\": \"The title of the fallback page when the page crashed\"\n  },\n  \"theme.BackToTopButton.buttonAriaLabel\": {\n    \"message\": \"Scroll back to top\",\n    \"description\": \"The ARIA label for the back to top button\"\n  },\n  \"theme.blog.archive.title\": {\n    \"message\": \"Archive\",\n    \"description\": \"The page & hero title of the blog archive page\"\n  },\n  \"theme.blog.archive.description\": {\n    \"message\": \"Archive\",\n    \"description\": \"The page & hero description of the blog archive page\"\n  },\n  \"theme.blog.paginator.navAriaLabel\": {\n    \"message\": \"Blog list page navigation\",\n    \"description\": \"The ARIA label for the blog pagination\"\n  },\n  \"theme.blog.paginator.newerEntries\": {\n    \"message\": \"Newer entries\",\n    \"description\": \"The label used to navigate to the newer blog posts page (previous page)\"\n  },\n  \"theme.blog.paginator.olderEntries\": {\n    \"message\": \"Older entries\",\n    \"description\": \"The label used to navigate to the older blog posts page (next page)\"\n  },\n  \"theme.blog.post.paginator.navAriaLabel\": {\n    \"message\": \"Blog post page navigation\",\n    \"description\": \"The ARIA label for the blog posts pagination\"\n  },\n  \"theme.blog.post.paginator.newerPost\": {\n    \"message\": \"Newer post\",\n    \"description\": \"The blog post button label to navigate to the newer/previous post\"\n  },\n  \"theme.blog.post.paginator.olderPost\": {\n    \"message\": \"Older post\",\n    \"description\": \"The blog post button label to navigate to the older/next post\"\n  },\n  \"theme.tags.tagsPageLink\": {\n    \"message\": \"View all tags\",\n    \"description\": \"The label of the link targeting the tag list page\"\n  },\n  \"theme.colorToggle.ariaLabel\": {\n    \"message\": \"Switch between dark and light mode (currently {mode})\",\n    \"description\": \"The ARIA label for the navbar color mode toggle\"\n  },\n  \"theme.colorToggle.ariaLabel.mode.dark\": {\n    \"message\": \"dark mode\",\n    \"description\": \"The name for the dark color mode\"\n  },\n  \"theme.colorToggle.ariaLabel.mode.light\": {\n    \"message\": \"light mode\",\n    \"description\": \"The name for the light color mode\"\n  },\n  \"theme.docs.breadcrumbs.navAriaLabel\": {\n    \"message\": \"Breadcrumbs\",\n    \"description\": \"The ARIA label for the breadcrumbs\"\n  },\n  \"theme.docs.DocCard.categoryDescription.plurals\": {\n    \"message\": \"1 item|{count} items\",\n    \"description\": \"The default description for a category card in the generated index about how many items this category includes\"\n  },\n  \"theme.docs.paginator.navAriaLabel\": {\n    \"message\": \"Docs pages\",\n    \"description\": \"The ARIA label for the docs pagination\"\n  },\n  \"theme.docs.paginator.previous\": {\n    \"message\": \"Previous\",\n    \"description\": \"The label used to navigate to the previous doc\"\n  },\n  \"theme.docs.paginator.next\": {\n    \"message\": \"Next\",\n    \"description\": \"The label used to navigate to the next doc\"\n  },\n  \"theme.docs.tagDocListPageTitle.nDocsTagged\": {\n    \"message\": \"One doc tagged|{count} docs tagged\",\n    \"description\": \"Pluralized label for \\\"{count} docs tagged\\\". Use as much plural forms (separated by \\\"|\\\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)\"\n  },\n  \"theme.docs.tagDocListPageTitle\": {\n    \"message\": \"{nDocsTagged} with \\\"{tagName}\\\"\",\n    \"description\": \"The title of the page for a docs tag\"\n  },\n  \"theme.docs.versionBadge.label\": {\n    \"message\": \"Version: {versionLabel}\"\n  },\n  \"theme.docs.versions.unreleasedVersionLabel\": {\n    \"message\": \"This is unreleased documentation for {siteTitle} {versionLabel} version.\",\n    \"description\": \"The label used to tell the user that he's browsing an unreleased doc version\"\n  },\n  \"theme.docs.versions.unmaintainedVersionLabel\": {\n    \"message\": \"This is documentation for {siteTitle} {versionLabel}, which is no longer actively maintained.\",\n    \"description\": \"The label used to tell the user that he's browsing an unmaintained doc version\"\n  },\n  \"theme.docs.versions.latestVersionSuggestionLabel\": {\n    \"message\": \"For up-to-date documentation, see the {latestVersionLink} ({versionLabel}).\",\n    \"description\": \"The label used to tell the user to check the latest version\"\n  },\n  \"theme.docs.versions.latestVersionLinkLabel\": {\n    \"message\": \"latest version\",\n    \"description\": \"The label used for the latest version suggestion link label\"\n  },\n  \"theme.common.editThisPage\": {\n    \"message\": \"Edit this page\",\n    \"description\": \"The link label to edit the current page\"\n  },\n  \"theme.common.headingLinkTitle\": {\n    \"message\": \"Direct link to {heading}\",\n    \"description\": \"Title for link to heading\"\n  },\n  \"theme.lastUpdated.atDate\": {\n    \"message\": \" on {date}\",\n    \"description\": \"The words used to describe on which date a page has been last updated\"\n  },\n  \"theme.lastUpdated.byUser\": {\n    \"message\": \" by {user}\",\n    \"description\": \"The words used to describe by who the page has been last updated\"\n  },\n  \"theme.lastUpdated.lastUpdatedAtBy\": {\n    \"message\": \"Last updated{atDate}{byUser}\",\n    \"description\": \"The sentence used to display when a page has been last updated, and by who\"\n  },\n  \"theme.navbar.mobileVersionsDropdown.label\": {\n    \"message\": \"Versions\",\n    \"description\": \"The label for the navbar versions dropdown on mobile view\"\n  },\n  \"theme.NotFound.title\": {\n    \"message\": \"Page Not Found\",\n    \"description\": \"The title of the 404 page\"\n  },\n  \"theme.admonition.caution\": {\n    \"message\": \"caution\",\n    \"description\": \"The default label used for the Caution admonition (:::caution)\"\n  },\n  \"theme.admonition.danger\": {\n    \"message\": \"danger\",\n    \"description\": \"The default label used for the Danger admonition (:::danger)\"\n  },\n  \"theme.admonition.info\": {\n    \"message\": \"info\",\n    \"description\": \"The default label used for the Info admonition (:::info)\"\n  },\n  \"theme.admonition.note\": {\n    \"message\": \"note\",\n    \"description\": \"The default label used for the Note admonition (:::note)\"\n  },\n  \"theme.admonition.tip\": {\n    \"message\": \"tip\",\n    \"description\": \"The default label used for the Tip admonition (:::tip)\"\n  },\n  \"theme.admonition.warning\": {\n    \"message\": \"warning\",\n    \"description\": \"The default label used for the Warning admonition (:::warning)\"\n  },\n  \"theme.tags.tagsListLabel\": {\n    \"message\": \"Tags:\",\n    \"description\": \"The label alongside a tag list\"\n  },\n  \"theme.AnnouncementBar.closeButtonAriaLabel\": {\n    \"message\": \"Close\",\n    \"description\": \"The ARIA label for close button of announcement bar\"\n  },\n  \"theme.blog.sidebar.navAriaLabel\": {\n    \"message\": \"Blog recent posts navigation\",\n    \"description\": \"The ARIA label for recent posts in the blog sidebar\"\n  },\n  \"theme.CodeBlock.copied\": {\n    \"message\": \"Copied\",\n    \"description\": \"The copied button label on code blocks\"\n  },\n  \"theme.CodeBlock.copyButtonAriaLabel\": {\n    \"message\": \"Copy code to clipboard\",\n    \"description\": \"The ARIA label for copy code blocks button\"\n  },\n  \"theme.CodeBlock.copy\": {\n    \"message\": \"Copy\",\n    \"description\": \"The copy button label on code blocks\"\n  },\n  \"theme.CodeBlock.wordWrapToggle\": {\n    \"message\": \"Toggle word wrap\",\n    \"description\": \"The title attribute for toggle word wrapping button of code block lines\"\n  },\n  \"theme.DocSidebarItem.expandCategoryAriaLabel\": {\n    \"message\": \"Expand sidebar category '{label}'\",\n    \"description\": \"The ARIA label to expand the sidebar category\"\n  },\n  \"theme.DocSidebarItem.collapseCategoryAriaLabel\": {\n    \"message\": \"Collapse sidebar category '{label}'\",\n    \"description\": \"The ARIA label to collapse the sidebar category\"\n  },\n  \"theme.NavBar.navAriaLabel\": {\n    \"message\": \"Main\",\n    \"description\": \"The ARIA label for the main navigation\"\n  },\n  \"theme.navbar.mobileLanguageDropdown.label\": {\n    \"message\": \"Languages\",\n    \"description\": \"The label for the mobile language switcher dropdown\"\n  },\n  \"theme.TOCCollapsible.toggleButtonLabel\": {\n    \"message\": \"On this page\",\n    \"description\": \"The label used by the button on the collapsible TOC component\"\n  },\n  \"theme.NotFound.p1\": {\n    \"message\": \"We could not find what you were looking for.\",\n    \"description\": \"The first paragraph of the 404 page\"\n  },\n  \"theme.NotFound.p2\": {\n    \"message\": \"Please contact the owner of the site that linked you to the original URL and let them know their link is broken.\",\n    \"description\": \"The 2nd paragraph of the 404 page\"\n  },\n  \"theme.blog.post.readMore\": {\n    \"message\": \"Read more\",\n    \"description\": \"The label used in blog post item excerpts to link to full blog posts\"\n  },\n  \"theme.blog.post.readMoreLabel\": {\n    \"message\": \"Read more about {title}\",\n    \"description\": \"The ARIA label for the link to full blog posts from excerpts\"\n  },\n  \"theme.blog.post.readingTime.plurals\": {\n    \"message\": \"One min read|{readingTime} min read\",\n    \"description\": \"Pluralized label for \\\"{readingTime} min read\\\". Use as much plural forms (separated by \\\"|\\\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)\"\n  },\n  \"theme.docs.breadcrumbs.home\": {\n    \"message\": \"Home page\",\n    \"description\": \"The ARIA label for the home page in the breadcrumbs\"\n  },\n  \"theme.docs.sidebar.collapseButtonTitle\": {\n    \"message\": \"Collapse sidebar\",\n    \"description\": \"The title attribute for collapse button of doc sidebar\"\n  },\n  \"theme.docs.sidebar.collapseButtonAriaLabel\": {\n    \"message\": \"Collapse sidebar\",\n    \"description\": \"The title attribute for collapse button of doc sidebar\"\n  },\n  \"theme.docs.sidebar.navAriaLabel\": {\n    \"message\": \"Docs sidebar\",\n    \"description\": \"The ARIA label for the sidebar navigation\"\n  },\n  \"theme.docs.sidebar.closeSidebarButtonAriaLabel\": {\n    \"message\": \"Close navigation bar\",\n    \"description\": \"The ARIA label for close button of mobile sidebar\"\n  },\n  \"theme.navbar.mobileSidebarSecondaryMenu.backButtonLabel\": {\n    \"message\": \"← Back to main menu\",\n    \"description\": \"The label of the back button to return to main menu, inside the mobile navbar sidebar secondary menu (notably used to display the docs sidebar)\"\n  },\n  \"theme.docs.sidebar.toggleSidebarButtonAriaLabel\": {\n    \"message\": \"Toggle navigation bar\",\n    \"description\": \"The ARIA label for hamburger menu button of mobile navigation\"\n  },\n  \"theme.docs.sidebar.expandButtonTitle\": {\n    \"message\": \"Expand sidebar\",\n    \"description\": \"The ARIA label and title attribute for expand button of doc sidebar\"\n  },\n  \"theme.docs.sidebar.expandButtonAriaLabel\": {\n    \"message\": \"Expand sidebar\",\n    \"description\": \"The ARIA label and title attribute for expand button of doc sidebar\"\n  },\n  \"theme.blog.post.plurals\": {\n    \"message\": \"One post|{count} posts\",\n    \"description\": \"Pluralized label for \\\"{count} posts\\\". Use as much plural forms (separated by \\\"|\\\") as your language support (see https://www.unicode.org/cldr/cldr-aux/charts/34/supplemental/language_plural_rules.html)\"\n  },\n  \"theme.blog.tagTitle\": {\n    \"message\": \"{nPosts} tagged with \\\"{tagName}\\\"\",\n    \"description\": \"The title of the page for a blog tag\"\n  },\n  \"theme.blog.author.pageTitle\": {\n    \"message\": \"{authorName} - {nPosts}\",\n    \"description\": \"The title of the page for a blog author\"\n  },\n  \"theme.blog.authorsList.pageTitle\": {\n    \"message\": \"Authors\",\n    \"description\": \"The title of the authors page\"\n  },\n  \"theme.blog.authorsList.viewAll\": {\n    \"message\": \"View all authors\",\n    \"description\": \"The label of the link targeting the blog authors page\"\n  },\n  \"theme.blog.author.noPosts\": {\n    \"message\": \"This author has not written any posts yet.\",\n    \"description\": \"The text for authors with 0 blog post\"\n  },\n  \"theme.contentVisibility.unlistedBanner.title\": {\n    \"message\": \"Unlisted page\",\n    \"description\": \"The unlisted content banner title\"\n  },\n  \"theme.contentVisibility.unlistedBanner.message\": {\n    \"message\": \"This page is unlisted. Search engines will not index it, and only users having a direct link can access it.\",\n    \"description\": \"The unlisted content banner message\"\n  },\n  \"theme.contentVisibility.draftBanner.title\": {\n    \"message\": \"Draft page\",\n    \"description\": \"The draft content banner title\"\n  },\n  \"theme.contentVisibility.draftBanner.message\": {\n    \"message\": \"This page is a draft. It will only be visible in dev and be excluded from the production build.\",\n    \"description\": \"The draft content banner message\"\n  },\n  \"theme.ErrorPageContent.tryAgain\": {\n    \"message\": \"Try again\",\n    \"description\": \"The label of the button to try again rendering when the React error boundary captures an error\"\n  },\n  \"theme.common.skipToMainContent\": {\n    \"message\": \"Skip to main content\",\n    \"description\": \"The skip to content label used for accessibility, allowing to rapidly navigate to main content with keyboard tab/enter navigation\"\n  },\n  \"theme.tags.tagsPageTitle\": {\n    \"message\": \"Tags\",\n    \"description\": \"The title of the tag list page\"\n  }\n}\n"
  },
  {
    "path": "teams.md/i18n/en/docusaurus-plugin-content-docs/current.json",
    "content": "{\n  \"version.label\": {\n    \"message\": \"Next\",\n    \"description\": \"The label for version current\"\n  }\n}\n"
  },
  {
    "path": "teams.md/i18n/en/docusaurus-plugin-content-docs-csharp/current.json",
    "content": "{\n  \"version.label\": {\n    \"message\": \"Next\",\n    \"description\": \"The label for version current\"\n  }\n}\n"
  },
  {
    "path": "teams.md/i18n/en/docusaurus-plugin-content-docs-typescript/current.json",
    "content": "{\n  \"version.label\": {\n    \"message\": \"Next\",\n    \"description\": \"The label for version current\"\n  },\n  \"sidebar.default.category.In Depth Guides\": {\n    \"message\": \"In Depth Guides\",\n    \"description\": \"The label for category In Depth Guides in sidebar default\"\n  },\n  \"sidebar.typescript.category.In Depth Guides\": {\n    \"message\": \"In Depth Guides\",\n    \"description\": \"The label for category In Depth Guides in sidebar typescript\"\n  },\n  \"sidebar.csharp.category.In Depth Guides\": {\n    \"message\": \"In Depth Guides\",\n    \"description\": \"The label for category In Depth Guides in sidebar csharp\"\n  },\n  \"sidebar.python.category.In Depth Guides\": {\n    \"message\": \"In Depth Guides\",\n    \"description\": \"The label for category In Depth Guides in sidebar python\"\n  }\n}\n"
  },
  {
    "path": "teams.md/i18n/en/docusaurus-theme-classic/footer.json",
    "content": "{\n  \"link.title.Docs\": {\n    \"message\": \"Docs\",\n    \"description\": \"The title of the footer links column with title=Docs in the footer\"\n  },\n  \"link.title.More\": {\n    \"message\": \"More\",\n    \"description\": \"The title of the footer links column with title=More in the footer\"\n  },\n  \"link.item.label.Getting Started\": {\n    \"message\": \"Getting Started\",\n    \"description\": \"The label of footer link with label=Getting Started linking to /\"\n  },\n  \"link.item.label.Blog\": {\n    \"message\": \"Blog\",\n    \"description\": \"The label of footer link with label=Blog linking to https://devblogs.microsoft.com/microsoft365dev/announcing-the-updated-teams-ai-library-and-mcp-support/\"\n  },\n  \"link.item.label.GitHub\": {\n    \"message\": \"GitHub\",\n    \"description\": \"The label of footer link with label=GitHub linking to https://github.com/microsoft/teams-sdk/tree/main\"\n  },\n  \"copyright\": {\n    \"message\": \"Copyright © 2025 Microsoft Corporation. All rights reserved.\",\n    \"description\": \"The footer copyright\"\n  }\n}\n"
  },
  {
    "path": "teams.md/i18n/en/docusaurus-theme-classic/navbar.json",
    "content": "{\n  \"title\": {\n    \"message\": \"Teams SDK\",\n    \"description\": \"The title in the navbar\"\n  },\n  \"logo.alt\": {\n    \"message\": \"Teams SDK\",\n    \"description\": \"The alt text of navbar logo\"\n  },\n  \"item.label.Typescript\": {\n    \"message\": \"Typescript\",\n    \"description\": \"Navbar item with label Typescript\"\n  },\n  \"item.label.C#\": {\n    \"message\": \"C#\",\n    \"description\": \"Navbar item with label C#\"\n  },\n  \"item.label.Python\": {\n    \"message\": \"Python\",\n    \"description\": \"Navbar item with label Python\"\n  }\n}\n"
  },
  {
    "path": "teams.md/package.json",
    "content": "{\n  \"name\": \"teams-md\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"docusaurus\": \"docusaurus\",\n    \"start\": \"concurrently --kill-others \\\"npm run generate:docs:watch\\\" \\\"docusaurus start\\\"\",\n    \"start:simple\": \"docusaurus start\",\n    \"prebuild\": \"npm run generate:docs\",\n    \"build\": \"docusaurus build\",\n    \"swizzle\": \"docusaurus swizzle\",\n    \"deploy\": \"docusaurus deploy\",\n    \"clear\": \"docusaurus clear\",\n    \"serve\": \"docusaurus serve\",\n    \"write-heading-ids\": \"docusaurus write-heading-ids\",\n    \"typecheck\": \"tsc\",\n    \"generate:docs\": \"tsx scripts/generate-language-docs.ts\",\n    \"generate:docs:watch\": \"tsx scripts/generate-language-docs.ts --watch\",\n    \"generate:llms\": \"tsx scripts/generate-llms-txt.ts\",\n    \"scaffold\": \"node src/scripts/scaffold.js\"\n  },\n  \"dependencies\": {\n    \"@docusaurus/core\": \"^3.9.2\",\n    \"@docusaurus/preset-classic\": \"^3.9.2\",\n    \"@docusaurus/theme-mermaid\": \"^3.9.2\",\n    \"@easyops-cn/docusaurus-search-local\": \"^0.49.2\",\n    \"@mdx-js/react\": \"^3.0.0\",\n    \"clsx\": \"^2.0.0\",\n    \"prism-react-renderer\": \"^2.3.0\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\",\n    \"react-json-view-lite\": \"^2.4.1\"\n  },\n  \"devDependencies\": {\n    \"@docusaurus/module-type-aliases\": \"^3.9.2\",\n    \"@docusaurus/tsconfig\": \"^3.9.2\",\n    \"@docusaurus/types\": \"^3.9.2\",\n    \"@types/chokidar\": \"^1.7.5\",\n    \"@types/js-yaml\": \"^4.0.9\",\n    \"chokidar\": \"^3.6.0\",\n    \"concurrently\": \"^9.2.1\",\n    \"js-yaml\": \"^4.1.1\",\n    \"json-stable-stringify\": \"^1.3.0\",\n    \"tsx\": \"^4.20.6\",\n    \"typescript\": \"~5.6.2\"\n  },\n  \"browserslist\": {\n    \"production\": [\n      \">0.5%\",\n      \"not dead\",\n      \"not op_mini all\"\n    ],\n    \"development\": [\n      \"last 3 chrome version\",\n      \"last 3 firefox version\",\n      \"last 5 safari version\"\n    ]\n  },\n  \"engines\": {\n    \"node\": \">=20.0\"\n  }\n}\n"
  },
  {
    "path": "teams.md/scripts/generate-language-docs.ts",
    "content": "#!/usr/bin/env node\n\nimport * as fs from 'fs';\nimport * as path from 'path';\nimport * as chokidar from 'chokidar';\nimport * as yaml from 'js-yaml';\nimport stringify from 'json-stable-stringify';\n\nimport { FrontmatterParser, FRONTMATTER_REGEX } from './lib/frontmatter-parser';\nimport {\n  LANGUAGES,\n  LANGUAGE_NAMES,\n  type Language,\n  type LanguageAvailabilityMap,\n} from '../src/constants/languages';\nimport normalizePath from '../src/utils/normalizePath';\nimport readFileUtf8Normalized from '../src/utils/readFileUtf8Normalized';\n\nconst missingPagesManifest: LanguageAvailabilityMap = {};\nconst contentGapsManifest: { [templatePath: string]: { [sectionName: string]: Language[] } } = {};\n\nconst TEMPLATES_DIR = path.join(__dirname, '..', 'src', 'pages', 'templates');\nconst FRAGMENTS_DIR = path.join(__dirname, '..', 'src', 'components', 'include');\n\nconst DOCS_BASE = path.join(__dirname, '..', 'docs', 'main');\n\nconst isProduction = process.env.NODE_ENV === 'production';\n\n// Track all include files that are actually referenced by templates\n// Languages excluded from a template via frontmatter will not be processed\nconst processedIncludeFiles = new Set<string>();\n\n// For sections in an *.mdx file that is applicable to one or two languages, but not all three.\n// This is an intentional way of differentiating from missing sections in documentation that haven't been written yet.\nconst NOT_APPLICABLE_REGEX = /^(not applicable|n\\/a)\\s*$/i;\n\n// Notation in *.incl.md files for sections to be added into the *.mdx\nconst SECTION_REGEX = (sectionName: string) =>\n  new RegExp(`<!--\\\\s*${sectionName}\\\\s*-->\\\\s*([\\\\s\\\\S]*?)(?=<!--\\\\s*[\\\\w-]+\\\\s*-->|$)`, 'i');\n\n// Regex to find LanguageInclude tags (supports both section and content props)\nconst LANGUAGE_INCLUDE_REGEX = /<LanguageInclude\\s+(?:section=\"([^\"]+)\"|content=\\{(\\{[^}]+\\})\\})\\s*\\/>/g;\n\nconst languagePattern = LANGUAGES.join('|');\nconst LANGUAGE_INCL_FILENAME_REGEX = new RegExp(`^(${languagePattern})\\\\.incl\\\\.md$`);\n\n/**\n * Extract a section from markdown content using HTML comment markers\n * - null: section not found (no matching HTML comment)\n * - '': section found but marked as N/A (intentionally empty)\n * - 'EMPTY_SECTION': section found but has no content (should show error in dev)\n * - string: section content\n */\nfunction extractSection(markdown: string, sectionName: string): string | null {\n  if (!markdown) {\n    return 'EMPTY_SECTION';\n  }\n\n  const match = markdown.match(SECTION_REGEX(sectionName));\n\n  // Section not found\n  if (!match) {\n    return null;\n  }\n\n  const content = match[1].trim();\n\n  // Content not applicable\n  if (NOT_APPLICABLE_REGEX.test(content)) {\n    return '';\n  }\n\n  // Section exists but has no content\n  if (content === '') {\n    return 'EMPTY_SECTION';\n  }\n\n  return content;\n}\n\n/**\n * Given a template path and language, return the include file path for that language.\n */\nfunction getIncludeFilePath(templatePath: string, language: Language): string {\n  const relativePath = path.relative(TEMPLATES_DIR, templatePath);\n  const fileName = path.basename(templatePath, '.mdx');\n  const dirPath = path.dirname(relativePath);\n\n  // Category files can be either index or README\n  if (fileName === 'index' || fileName === 'README') {\n    return path.join(FRAGMENTS_DIR, dirPath, `${language}.incl.md`);\n  } else {\n    return path.join(FRAGMENTS_DIR, dirPath, fileName, `${language}.incl.md`);\n  }\n}\n\n/**\n * Given an include file path, return the expected template path.\n */\nfunction getTemplatePathFromInclude(includePath: string): string {\n  const rel = path.relative(FRAGMENTS_DIR, includePath);\n  const parts = rel.split(path.sep);\n\n  // foo/bar/lang.incl.md => foo/bar.mdx\n  if (parts.length >= 2) {\n    return path.join(TEMPLATES_DIR, ...parts.slice(0, -1)) + '.mdx';\n  } else {\n    return '(unknown)';\n  }\n}\n\n/**\n * Process LanguageInclude tags in template content and replace with Language components or raw content\n * - Production mode + target language: generates clean files with only raw content for that language\n * - Development mode: generates Language components with helpful error messages for missing content\n */\nfunction processLanguageIncludeTags(\n  templateContent: string,\n  templatePath: string,\n  targetLanguage?: Language\n): string {\n  let processedContent = templateContent;\n  let hasLanguageInclude = false;\n\n  // Replace all LanguageInclude tags\n  processedContent = processedContent.replace(\n    LANGUAGE_INCLUDE_REGEX,\n    (match, sectionName, inlineContent, offset) => {\n      hasLanguageInclude = true;\n\n      // Determine if this is inline or block based on context\n      const beforeMatch = templateContent.substring(0, offset);\n\n      // Check if the tag is at the start of a line or after only whitespace (block)\n      // vs. if it's within text content (inline)\n      const lineStart = beforeMatch.lastIndexOf('\\n');\n      const textBeforeOnLine = beforeMatch.substring(lineStart + 1);\n      const isBlock = /^\\s*$/.test(textBeforeOnLine);\n\n      // Handle inline content (content={{...}})\n      if (inlineContent) {\n        try {\n          // Parse the inline content object\n          const contentObj = JSON.parse(inlineContent);\n          \n          // Production mode with target language\n          if (isProduction && targetLanguage) {\n            const content = contentObj[targetLanguage];\n            return content || '';\n          }\n          \n          // Development mode: generate Language components for all languages\n          const languageComponents: string[] = [];\n          for (const lang of LANGUAGES) {\n            const content = contentObj[lang];\n            if (content) {\n              if (isBlock) {\n                languageComponents.push(\n                  `<Language language=\"${lang}\">\\n\\n${content}\\n\\n</Language>`\n                );\n              } else {\n                languageComponents.push(`<Language language=\"${lang}\">${content}</Language>`);\n              }\n            }\n          }\n          return languageComponents.join('\\n');\n        } catch (error) {\n          console.warn(`generate-language-docs warning: Error parsing inline content: ${error}`);\n          return match; // Return original tag on error\n        }\n      }\n\n      // Handle section reference (section=\"...\")\n      // Production mode with target language: only generate for that language\n      if (isProduction && targetLanguage) {\n        const inclPath = getIncludeFilePath(templatePath, targetLanguage);\n        if (!fs.existsSync(inclPath)) {\n          // Skip missing content (prod)\n          return '';\n        }\n\n        try {\n          const fileContent = readFileUtf8Normalized(inclPath);\n          const sectionContent = extractSection(fileContent, sectionName);\n\n          if (sectionContent === null || sectionContent === '' || sectionContent === 'EMPTY_SECTION') {\n            // Skip missing sections (null), intentional N/A content (empty string), or empty sections\n            return '';\n          }\n\n          return isBlock ? `${sectionContent}` : sectionContent;\n        } catch (error) {\n          console.warn(`generate-language-docs warning: Error reading ${inclPath}: ${error}`);\n          return '';\n        }\n      }\n\n      // Development mode or no target language: generate Language components for all languages\n      // This allows dev-time rendering of messages indicating what include sections are missing.\n      const languageComponents: string[] = [];\n      const languagesToProcess = targetLanguage ? [targetLanguage] : LANGUAGES;\n\n      for (const lang of languagesToProcess) {\n        const inclPath = getIncludeFilePath(templatePath, lang);\n        let sectionContent: string | null = null;\n\n        const isLanguageRestricted = shouldGenerateForLanguage(templateContent, lang);\n\n        if (!isLanguageRestricted) {\n          // Template doesn't target this language; skip\n          continue;\n        }\n\n        // Only mark as incl file path as used if this template is supposed to generate for this language\n        processedIncludeFiles.add(path.resolve(inclPath));\n\n        if (!fs.existsSync(inclPath)) {\n          // File missing for a language the template should support - show error in development\n          if (!isProduction) {\n            const errorMsg = `[DevMode] Documentation file for ${LANGUAGE_NAMES[lang]} not found: ${path.relative(process.cwd(), inclPath)}`;\n            if (isBlock) {\n              languageComponents.push(\n                `<Language language=\"${lang}\">\\n\\n${errorMsg}\\n\\n</Language>`\n              );\n            } else {\n              languageComponents.push(`<Language language=\"${lang}\">${errorMsg}</Language>`);\n            }\n          }\n          continue;\n        }\n\n        try {\n          const fileContent = readFileUtf8Normalized(inclPath);\n          sectionContent = extractSection(fileContent, sectionName);\n\n          if (sectionContent === null || sectionContent === 'EMPTY_SECTION') {\n            // Section not found (null) or section exists but has no content (EMPTY_SECTION)\n            // Track this gap in the manifest\n            const gapKey = normalizePath(path.relative(TEMPLATES_DIR, templatePath));\n            if (!contentGapsManifest[gapKey]) {\n              contentGapsManifest[gapKey] = {};\n            }\n            if (!contentGapsManifest[gapKey][sectionName]) {\n              contentGapsManifest[gapKey][sectionName] = [];\n            }\n            contentGapsManifest[gapKey][sectionName].push(lang);\n\n            // Both cases show the same error in development, skip in production\n            if (!isProduction) {\n              const errorMsg = `**[Dev] Section \"${sectionName}\" not found in ${LANGUAGE_NAMES[lang]} documentation.** Either mark the section explicitly as N/A for intentionally ignored, or fill in documentation.`;\n              if (isBlock) {\n                languageComponents.push(\n                  `<Language language=\"${lang}\">\\n\\n${errorMsg}\\n\\n</Language>`\n                );\n              } else {\n                languageComponents.push(`<Language language=\"${lang}\">${errorMsg}</Language>`);\n              }\n            }\n            continue;\n          }\n\n          if (sectionContent === '') {\n            // N/A section - intentionally skip any rendering\n            continue;\n          }\n\n          // Valid content found\n          if (isBlock) {\n            // Block-level: full Language component with markdown processing\n            languageComponents.push(\n              `<Language language=\"${lang}\">\\n\\n${sectionContent}\\n\\n</Language>`\n            );\n          } else {\n            // Inline: single Language component with just the text content\n            languageComponents.push(`<Language language=\"${lang}\">${sectionContent}</Language>`);\n          }\n        } catch (error) {\n          console.warn(`    Warning: Error reading ${inclPath}: ${error}`);\n          // Generate error component in development\n          if (!isProduction) {\n            const errorMsg = `[Dev] Error reading file: ${error}`;\n            if (isBlock) {\n              languageComponents.push(\n                `<Language language=\"${lang}\">\\n\\n${errorMsg}\\n\\n</Language>`\n              );\n            } else {\n              languageComponents.push(`<Language language=\"${lang}\">${errorMsg}</Language>`);\n            }\n          }\n        }\n      }\n\n      // Return joined Language components (or empty string if no components added)\n      if (isBlock) {\n        // Block: Each language on separate lines\n        return languageComponents.join('\\n\\n');\n      } else {\n        // Inline: Return all language components without line breaks to preserve list structure\n        return languageComponents.join('');\n      }\n    }\n  );\n\n  // Add Language component import if we processed any LanguageInclude tags and need Language components\n  const needsLanguageImport =\n    hasLanguageInclude &&\n    (!isProduction || !targetLanguage) &&\n    !processedContent.includes(\"import Language from '@site/src/components/Language'\");\n\n  if (needsLanguageImport) {\n    // Find where to insert the import (after frontmatter if it exists)\n    const frontmatterMatch = processedContent.match(FRONTMATTER_REGEX);\n    const insertPosition = frontmatterMatch ? frontmatterMatch[0].length : 0;\n\n    const importStatement = \"import Language from '@site/src/components/Language';\\n\\n\";\n    processedContent =\n      processedContent.slice(0, insertPosition) +\n      importStatement +\n      processedContent.slice(insertPosition);\n  }\n\n  return processedContent;\n}\n\nfunction deleteInDirectory(dir: string, deletedCount: { count: number }): void {\n  const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n  for (const entry of entries) {\n    const fullPath = path.join(dir, entry.name);\n\n    if (entry.isDirectory()) {\n      deleteInDirectory(fullPath, deletedCount);\n      // Remove empty directories\n      try {\n        fs.rmdirSync(fullPath);\n      } catch (e) {\n        // Directory not empty and will be cleaned up later\n      }\n    } else if (entry.isFile() && entry.name.endsWith('.mdx')) {\n      fs.unlinkSync(fullPath);\n      deletedCount.count++;\n    } else if (entry.isFile() && entry.name === '_category_.json') {\n      // Delete all category files - they will be regenerated from templates\n      fs.unlinkSync(fullPath);\n      deletedCount.count++;\n    }\n  }\n}\n\n/**\n * Clean up stale generated files before regeneration\n * Removes all .mdx files from docs/main/{lang}/ directories\n */\nfunction cleanGeneratedFiles(): void {\n  console.log('Cleaning up stale generated files...');\n\n  let deletedCount = { count: 0 };\n\n  for (const lang of LANGUAGES) {\n    const langDir = path.join(DOCS_BASE, lang);\n\n    if (!fs.existsSync(langDir)) {\n      continue;\n    }\n\n    // Also remove root-level category file for this language\n    const rootCategoryPath = path.join(langDir, '_category_.json');\n\n    if (fs.existsSync(rootCategoryPath)) {\n      fs.unlinkSync(rootCategoryPath);\n      deletedCount.count++;\n    }\n\n    // Recursively find and delete all .mdx and _category_.json files\n    deleteInDirectory(langDir, deletedCount);\n  }\n\n  if (deletedCount.count === 0) {\n    console.log('No files to clean');\n  } else {\n    console.log(`  Cleaned up ${deletedCount.count} file(s)\\n`);\n  }\n}\n\n/**\n * Clean up generated files for a specific template\n * Removes the generated .mdx files for all languages\n */\nfunction cleanGeneratedFilesForTemplate(templatePath: string): void {\n  const relativePath = path.relative(TEMPLATES_DIR, templatePath);\n  const templateName = path.basename(templatePath);\n\n  for (const lang of LANGUAGES) {\n    const outputDir = path.join(DOCS_BASE, lang, path.dirname(relativePath));\n    // Handle both README.mdx -> index.mdx conversion and regular files\n    // Note: README.mdx category templates are converted to index.mdx for generated output\n    const outputFileName = templateName === 'README.mdx' ? 'index.mdx' : templateName;\n    const outputPath = path.join(outputDir, outputFileName);\n\n    if (fs.existsSync(outputPath)) {\n      fs.unlinkSync(outputPath);\n      console.log(`  Removed: docs/main/${lang}/${path.dirname(relativePath)}/${outputFileName}`);\n    }\n  }\n}\n\n/**\n * Check if a template should be generated for a specific language based on frontmatter\n */\nfunction shouldGenerateForLanguage(templateContent: string, language: Language): boolean {\n  const { frontmatter } = FrontmatterParser.extract(templateContent);\n\n  // Check if languages array is specified\n  if (frontmatter.languages && Array.isArray(frontmatter.languages)) {\n    return frontmatter.languages.includes(language);\n  }\n\n  // No language restriction = generate for all languages\n  return true;\n}\n\n/**\n * Generate language-specific doc files from templates\n * Templates in src/components/ (with nested dirs) are copied to docs/main/{lang}/\n * Preserves directory structure and processes LanguageInclude tags\n */\nfunction generateDocsForTemplate(templatePath: string): void {\n  // Calculate relative path from TEMPLATES_DIR to preserve nested structure\n  const relativePath = normalizePath(path.relative(TEMPLATES_DIR, templatePath));\n  const templateName = path.basename(templatePath);\n\n  // Read template content (normalize CRLF > LF)\n  const templateContent = readFileUtf8Normalized(templatePath);\n\n  // Check frontmatter for warning suppression\n  const { frontmatter } = FrontmatterParser.extract(templateContent);\n  const suppressLanguageIncludeWarning = frontmatter.suppressLanguageIncludeWarning === true;\n\n  // Validate template contained LanguageInclude tags (unless suppressed)\n  if (!suppressLanguageIncludeWarning && !templateContent.includes('<LanguageInclude')) {\n    console.warn(\n      `\\nWarning: Template \"${relativePath}\" does not contain <LanguageInclude /> tags.`\n    );\n    console.warn(\n      `  If the file is intended to be identical for all languages, ignore this warning.\\n  Suppress this warning by adding suppressLanguageIncludeWarning: true to the file's fronmatter`\n    );\n  }\n\n  // Track which languages this page is missing from\n  const pagePath =\n    relativePath\n      .replace(/\\.mdx?$/, '')\n      .replace(/README$/, '')\n      .replace(/\\/$/, '') || '/';\n  const missingLanguages: Language[] = [];\n\n  for (const lang of LANGUAGES) {\n    // Check if this template should be generated for this language\n    if (!shouldGenerateForLanguage(templateContent, lang)) {\n      missingLanguages.push(lang);\n      continue;\n    }\n\n    const processedContent = processLanguageIncludeTags(templateContent, templatePath, lang);\n\n    // Extract frontmatter if exists\n    const { frontmatter, hasFrontmatter, content: contentWithoutFrontmatter } = FrontmatterParser.extract(processedContent);\n    let content = contentWithoutFrontmatter;\n    let frontmatterRaw = '';\n\n    if (hasFrontmatter && frontmatter && Object.keys(frontmatter).length > 0) {\n      frontmatterRaw = `---\\n${yaml.dump(frontmatter)}---\\n`\n    }\n\n    const outputDir = path.join(DOCS_BASE, lang, path.dirname(relativePath));\n    // Convert README.mdx to index.mdx so it becomes the category page content\n    const outputFileName = templateName === 'README.mdx' ? 'index.mdx' : templateName;\n    const outputPath = path.join(outputDir, outputFileName);\n\n    // Ensure directory exists\n    if (!fs.existsSync(outputDir)) {\n      fs.mkdirSync(outputDir, { recursive: true });\n    }\n\n    // Build output content\n    let output = '';\n\n    // Add frontmatter first if it exists (must be at the very top)\n    if (frontmatter) {\n      output += `${frontmatterRaw}\\n`;\n    }\n\n    // Add auto-generation warning after frontmatter\n    output += `<!--\\n`;\n    output += `  AUTO-GENERATED FILE - DO NOT EDIT\\n`;\n    output += `  This file is generated from: src/pages/templates/${relativePath}\\n`;\n    output += `  To make changes, edit the template file, then run: npm run generate:docs\\n`;\n    output += `-->\\n\\n`;\n\n    // Add processed content (optimized for production, with Language components for development)\n    output += content;\n\n    // Write file\n    fs.writeFileSync(outputPath, output, 'utf8');\n  }\n\n  // Only add to manifest if some languages are missing\n  if (missingLanguages.length > 0) {\n    missingPagesManifest[pagePath] = missingLanguages;\n  }\n}\n\n/**\n * Copy category configuration files to all language directories\n * Copies _category_.json files from template directories to docs/main/{lang}/\n * Preserves directory structure\n */\nfunction copyCategoryFiles(): void {\n  function copyDirectory(sourceDir: string, relativePath: string = ''): void {\n    const entries = fs.readdirSync(sourceDir, { withFileTypes: true });\n\n    for (const entry of entries) {\n      const sourcePath = path.join(sourceDir, entry.name);\n      const currentRelativePath = path.join(relativePath, entry.name);\n\n      if (entry.isDirectory()) {\n        copyDirectory(sourcePath, currentRelativePath);\n      } else if (entry.isFile() && entry.name === '_category_.json') {\n        // Copy category file to all language directories with unique keys\n        const categoryContent = JSON.parse(readFileUtf8Normalized(sourcePath));\n\n        for (const lang of LANGUAGES) {\n          const targetDir = path.join(DOCS_BASE, lang, relativePath);\n          const targetPath = path.join(targetDir, '_category_.json');\n\n          // Ensure target directory exists\n          if (!fs.existsSync(targetDir)) {\n            fs.mkdirSync(targetDir, { recursive: true });\n          }\n\n          // Add unique key based on language and relative path\n          const modifiedContent = {\n            ...categoryContent,\n            key: `${lang}-${relativePath.replace(/[/\\\\]/g, '-') || 'root'}`,\n          };\n\n          fs.writeFileSync(targetPath, JSON.stringify(modifiedContent, null, 2) + '\\n');\n        }\n      }\n    }\n  }\n\n  copyDirectory(TEMPLATES_DIR);\n}\n\n/**\n * Find all template files in src/components recursively\n */\nfunction findTemplateFiles(): string[] {\n  if (!fs.existsSync(TEMPLATES_DIR)) {\n    console.error(`Templates directory not found: ${TEMPLATES_DIR}`);\n    return [];\n  }\n\n  const templates: string[] = [];\n\n  function searchDirectory(dir: string): void {\n    const entries = fs.readdirSync(dir, { withFileTypes: true });\n\n    for (const entry of entries) {\n      const fullPath = path.join(dir, entry.name);\n\n      if (entry.isDirectory()) {\n        searchDirectory(fullPath);\n      } else if (entry.isFile() && entry.name.endsWith('.mdx')) {\n        // Skip generation of underscore-prefixed files (utility/unlisted templates)\n        if (entry.name.startsWith('_')) {\n          continue;\n        }\n        templates.push(fullPath);\n      }\n    }\n  }\n\n  searchDirectory(TEMPLATES_DIR);\n  // Deterministic ordering across platforms\n  templates.sort((a, b) => normalizePath(a).localeCompare(normalizePath(b)));\n  return templates;\n}\n\n/**\n * Create root-level category files for each language directory\n * Creates _category_.json files in docs/main/{lang}/ with proper positioning\n */\nfunction createLanguageRootCategories(): void {\n  // Position values for each language for correct sidebar ordering\n  const languagePositions = {\n    typescript: 2.0,\n    csharp: 2.1,\n    python: 2.2,\n  };\n\n  for (const lang of LANGUAGES) {\n    const langDir = path.join(DOCS_BASE, lang);\n    const categoryPath = path.join(langDir, '_category_.json');\n\n    // Ensure directory exists\n    if (!fs.existsSync(langDir)) {\n      fs.mkdirSync(langDir, { recursive: true });\n    }\n\n    // Create category configuration\n    const categoryConfig = {\n      label: `${LANGUAGE_NAMES[lang]} Guide`,\n      position: languagePositions[lang],\n      collapsible: true,\n      collapsed: false,\n    };\n\n    // Write the category file\n    fs.writeFileSync(categoryPath, JSON.stringify(categoryConfig, null, 2), 'utf8');\n  }\n}\n\n// After all templates are processed, warn about orphaned include files\nfunction warnOrphanedIncludeFiles() {\n  const orphanedFiles: Array<{ lang: string; fullPath: string; relTemplate: string }> = [];\n  function scanDir(dir: string) {\n    const entries = fs.readdirSync(dir, { withFileTypes: true });\n    for (const entry of entries) {\n      const fullPath = path.join(dir, entry.name);\n      if (entry.isDirectory()) {\n        scanDir(fullPath);\n      } else if (entry.isFile() && entry.name.endsWith('.incl.md')) {\n        if (!processedIncludeFiles.has(path.resolve(fullPath))) {\n          // Extract language from filename\n          const match = entry.name.match(LANGUAGE_INCL_FILENAME_REGEX);\n          const lang = match ? match[1] : 'unknown';\n          const templatePath = getTemplatePathFromInclude(fullPath);\n          const relTemplate = normalizePath(path.relative(process.cwd(), templatePath));\n          orphanedFiles.push({ lang, fullPath, relTemplate });\n        }\n      }\n    }\n  }\n  scanDir(FRAGMENTS_DIR);\n  if (orphanedFiles.length > 0) {\n    console.warn(`\\n[DevMode] Orphaned include files were found. These files are not referenced by any template (possibly due to 'language' frontmatter restrictions):`);\n    orphanedFiles.forEach(({ lang, fullPath, relTemplate }) => {\n      console.warn(`  - [${lang}] ${fullPath}\\n      Template: ${relTemplate}`);\n    });\n  }\n}\n\n/**\n * Write the missing pages manifest to static directory\n */\nfunction writePageManifest(): void {\n  const manifestPath = normalizePath(path.join(__dirname, '..', 'static', 'missing-pages.json'));\n\n  // Ensure static directory exists\n  const staticDir = path.dirname(manifestPath);\n  if (!fs.existsSync(staticDir)) {\n    fs.mkdirSync(staticDir, { recursive: true });\n  }\n\n  // Write compact JSON with only pages that are missing from some languages\n  // Ensure deterministic sorting across platforms using json-stable-stringify\n  fs.writeFileSync(manifestPath, stringify(missingPagesManifest, { space: 2 }) + '\\n', 'utf8');\n  console.log(\n    `\\nWrote missing pages manifest to ${path.relative(process.cwd(), manifestPath)} (${Object.keys(missingPagesManifest).length} entries)`\n  );\n}\n\n/**\n * Write the content gaps manifest for tracking missing sections\n */\nfunction writeContentGapsManifest(): void {\n  const generatedDir = normalizePath(path.join(__dirname, 'generated'));\n  const manifestPath = normalizePath(path.join(generatedDir, 'content-gaps.json'));\n  const readmePath = normalizePath(path.join(generatedDir, 'content-gaps.md'));\n\n  // Ensure generated directory exists\n  if (!fs.existsSync(generatedDir)) {\n    fs.mkdirSync(generatedDir, { recursive: true });\n  }\n\n  // Directly stringify manifest (order not important; stable stringify ensures deterministic output anyway)\n  fs.writeFileSync(manifestPath, stringify(contentGapsManifest, { space: 2 }) + '\\n', 'utf8');\n\n  // Generate human-readable markdown report\n  let markdownContent = `# Content Gaps Report\\n\\n`;\n  markdownContent += `Generated: ${new Date().toISOString()}\\n\\n`;\n  markdownContent += `This report tracks missing sections in language-specific documentation.\\n\\n`;\n\n  const totalGaps = Object.keys(contentGapsManifest).length;\n\n  markdownContent += `**${totalGaps} template(s) have missing sections**\\n\\n`;\n\n  if (totalGaps > 0) {\n    for (const [templatePath, sections] of Object.entries(contentGapsManifest)) {\n      markdownContent += `## \\`${templatePath}\\`\\n\\n`;\n      for (const [sectionName, languages] of Object.entries(sections)) {\n        const langNames = languages.map(lang => LANGUAGE_NAMES[lang]).join(', ');\n        markdownContent += `- **\\`${sectionName}\\`**: Missing in ${langNames}\\n`;\n      }\n      markdownContent += `\\n`;\n    }\n  }\n\n  markdownContent += `## Summary\\n\\n`;\n  const totalSectionGaps = Object.values(contentGapsManifest)\n    .flatMap(sections => Object.values(sections))\n    .reduce((total, langs: Language[]) => total + langs.length, 0);\n  markdownContent += `- **${totalGaps}** templates with gaps\\n`;\n  markdownContent += `- **${totalSectionGaps}** total missing sections\\n`;\n\n  fs.writeFileSync(readmePath, markdownContent, 'utf8');\n\n  console.log(\n    `\\nWrote content gaps manifest to ${path.relative(process.cwd(), manifestPath)} (${totalGaps} templates with gaps)`\n  );\n  console.log(`Generated readable report: ${path.relative(process.cwd(), readmePath)}`);\n}\n\ninterface GenerationResult {\n  templatesGenerated: number;\n  contentGapsFound: number;\n}\n\n/**\n * Generate all docs\n * @returns Generation result with template count and content gaps found\n */\nfunction generateAll(): GenerationResult {\n  console.log('generate-language-docs.ts: Generating language-specific documentation...\\n');\n\n  // Clean up stale files first\n  cleanGeneratedFiles();\n\n  // Reset manifests\n  Object.keys(missingPagesManifest).forEach((key) => delete missingPagesManifest[key]);\n  Object.keys(contentGapsManifest).forEach((key) => delete contentGapsManifest[key]);\n\n  const templates = findTemplateFiles();\n\n  if (templates.length === 0) {\n    console.log('No template files found in src/pages/templates/');\n    return { templatesGenerated: 0, contentGapsFound: 0 };\n  }\n\n  templates.forEach(generateDocsForTemplate);\n\n  // Copy category configuration files\n  copyCategoryFiles();\n\n  // Create root-level category files for language directories\n  createLanguageRootCategories();\n\n  // Warn about orphaned include files\n  if (!isProduction) {\n    warnOrphanedIncludeFiles();\n  }\n\n  // Write the page manifest\n  writePageManifest();\n\n  // Write the content gaps manifest\n  writeContentGapsManifest();\n\n  console.log(`\\nGenerated ${templates.length} template(s) for ${LANGUAGES.length} languages\\n`);\n\n  return {\n    templatesGenerated: templates.length,\n    contentGapsFound: Object.keys(contentGapsManifest).length,\n  };\n}\n\n/**\n * Watch mode for development\n */\nfunction watch(): void {\n  console.log('\\nWatching for template and fragment changes...\\n');\n\n  // Watch templates\n  const templateWatcher = chokidar.watch(path.join(TEMPLATES_DIR, '**/*.mdx'), {\n    persistent: true,\n    ignoreInitial: true,\n  });\n\n  templateWatcher.on('add', (filePath: string) => {\n    generateDocsForTemplate(filePath);\n  });\n\n  templateWatcher.on('change', (filePath: string) => {\n    generateDocsForTemplate(filePath);\n  });\n\n  templateWatcher.on('unlink', (filePath: string) => {\n    cleanGeneratedFilesForTemplate(filePath);\n  });\n\n  // Watch Language Include/fragment files (*.incl.md)\n  const inclWatcher = chokidar.watch(path.join(FRAGMENTS_DIR, '**/*.incl.md'), {\n    persistent: true,\n    ignoreInitial: true,\n  });\n\n  /**\n   * Handle changes to include files and regenerate corresponding templates\n   * Maps include file paths to their corresponding template files:\n   * - Category pages: src/components/include/{path}/{lang}.incl.md → src/pages/templates/{path}/README.mdx\n   * - Regular pages: src/components/include/{path}/{page}/{lang}.incl.md → src/pages/templates/{path}/{page}.mdx\n   */\n  const handleInclChange = (filePath: string): void => {\n    const relativePath = path.relative(FRAGMENTS_DIR, filePath);\n    const parts = relativePath.split(path.sep);\n\n    const langFile = parts.pop();\n\n    let templatePath: string;\n\n    if (langFile && LANGUAGE_INCL_FILENAME_REGEX.test(langFile)) {\n      templatePath = path.join(TEMPLATES_DIR, ...parts, 'README.mdx');\n\n      if (!fs.existsSync(templatePath)) {\n        templatePath = path.join(TEMPLATES_DIR, ...parts) + '.mdx';\n      }\n    } else {\n      templatePath = path.join(TEMPLATES_DIR, ...parts) + '.mdx';\n    }\n\n    if (fs.existsSync(templatePath)) {\n      console.log(`\\nFragment changed: ${relativePath}`);\n      console.log(`Regenerating template: ${path.relative(TEMPLATES_DIR, templatePath)}`);\n      generateDocsForTemplate(templatePath);\n    } else {\n      console.warn(`\\nFragment changed but template not found: ${templatePath}`);\n      console.warn('This might be an orphaned fragment file.');\n    }\n  };\n\n  inclWatcher.on('change', handleInclChange);\n  inclWatcher.on('add', handleInclChange);\n\n  // Watch category configuration files in templates\n  const categoryWatcher = chokidar.watch(path.join(TEMPLATES_DIR, '**/_category_.json'), {\n    persistent: true,\n    ignoreInitial: true,\n  });\n\n  categoryWatcher.on('change', () => {\n    copyCategoryFiles();\n  });\n\n  categoryWatcher.on('add', () => {\n    copyCategoryFiles();\n  });\n}\n\n// Main execution\nif (require.main === module) {\n  const args = process.argv.slice(2);\n  const watchMode = args.includes('--watch') || args.includes('-w');\n\n  if (watchMode) {\n    generateAll();\n    watch();\n  } else {\n    const result = generateAll();\n\n    // In production mode, fail the build if there are content gaps\n    if (isProduction && result.contentGapsFound > 0) {\n      console.error(`\\n❌ Build failed: Found ${result.contentGapsFound} template(s) with content gaps in production mode.`);\n      console.error('Please fill in all missing sections or mark them as N/A before deploying.\\n');\n      process.exit(1);\n    }\n  }\n}\n\nexport { generateAll, generateDocsForTemplate };\n"
  },
  {
    "path": "teams.md/scripts/generate-llms-txt.ts",
    "content": "#!/usr/bin/env node\n\nimport * as fs from 'fs';\nimport * as path from 'path';\n\nimport { collectFiles, getHierarchicalFiles } from './lib/file-collector';\nimport { processContent } from './lib/content-processor';\nimport { FrontmatterParser } from './lib/frontmatter-parser';\nimport { LANGUAGES, Language, LANGUAGE_NAMES } from '../src/constants/languages';\nimport readFileUtf8Normalized from '../src/utils/readFileUtf8Normalized';\n\nconst LANGUAGE_SPECIFIC_TIPS: Record<Language, string[]> = {\n    typescript: [\n        \"It's a good idea to build the application using `npm run build` and fix compile time errors to help ensure the app works as expected.\",\n        \"The SDK uses typescript to help you make the right decisions when using the APIs. You may check type definitions and type checkers to make sure your code is correct.\"\n    ],\n    python: [\n        \"It's a good idea to run `pyright` to make sure the code is correctly typed and fix any type errors.\",\n    ],\n    csharp: [\n        \"It's a good idea to build the application and fix compile time errors to help ensure the app works as expected.\",\n        \"It is helpful to inspect NuGet packages folder to get exact types for a given namesapce\"\n    ]\n};\n\nconst COMMON_OVERALL_SUMMARY = (language: Language) => {\n    const langName = LANGUAGE_NAMES[language];\n    const tips = LANGUAGE_SPECIFIC_TIPS[language];\n    const formattedTips = tips.map(tip => `- ${tip}`).join('\\n');\n\n    return `> Microsoft Teams SDK - A comprehensive framework for building AI-powered Teams applications using ${langName}. Using this SDK, you can easily build and integrate a variety of features in Microsoft Teams by building Agents or Tools. The documentation here helps by giving background information and code samples on how best to do this.\n\nIMPORTANT THINGS TO REMEMBER:\n- This SDK is NOT based off of BotFramework (which the _previous_ version of the Teams SDK was based on). This SDK is a completely new framework. Use this guide to find snippets to drive your decisions.\n- When scaffolding new applications, using the CLI is a lot simpler and preferred than doing it all by yourself. See the Quickstart guide for that.\n${formattedTips}\n\nYOU MUST FOLLOW THE ABOVE GUIDANCE.`;\n};\n\ninterface DocusaurusConfig {\n    url: string;\n    baseUrl: string;\n}\n\ninterface ProcessedFile {\n    title: string;\n    content: string;\n    frontmatter: { [key: string]: any };\n    filePath: string;\n    sidebarPosition: number;\n    relativeUrl: string;\n}\n\ninterface FileInfo {\n    name: string;\n    title: string;\n    path: string;\n    order: number;\n}\n\ninterface FolderStructure {\n    title: string;\n    order: number;\n    path: string;\n    files: FileInfo[];\n    children: { [key: string]: FolderStructure };\n}\n\n/**\n * Reads Docusaurus config to get base URL\n * @param baseDir - Base directory path\n * @returns Config object with url and baseUrl\n */\nfunction getDocusaurusConfig(baseDir: string): DocusaurusConfig {\n    try {\n    // Read the docusaurus.config.ts file\n    const configPath = path.join(baseDir, 'docusaurus.config.ts');\n    const configContent = readFileUtf8Normalized(configPath);\n\n        // Extract URL and baseUrl using regex (simple approach)\n        const urlMatch = configContent.match(/url:\\s*['\"]([^'\"]+)['\"]/);\n        const baseUrlMatch =\n            configContent.match(/baseUrl\\s*=\\s*['\"]([^'\"]+)['\"]/) ||\n            configContent.match(/baseUrl:\\s*['\"]([^'\"]+)['\"]/);\n\n        const url = urlMatch ? urlMatch[1] : 'https://microsoft.github.io';\n        const baseUrl = baseUrlMatch ? baseUrlMatch[1] : '/teams-sdk/';\n\n        return { url, baseUrl };\n    } catch (error) {\n        console.warn('⚠️ Could not read Docusaurus config, using defaults');\n        return { url: 'https://microsoft.github.io', baseUrl: '/teams-sdk/' };\n    }\n}\n\n/**\n * Generates llms.txt files for Teams SDK documentation\n * Creates both small and full versions for TypeScript and C# docs\n */\nasync function generateLlmsTxt(): Promise<void> {\n    console.log('🚀 Starting llms.txt generation...');\n\n    const baseDir = path.join(__dirname, '..');\n    const outputDir = path.join(baseDir, 'static', 'llms_docs');\n\n    // Get Docusaurus configuration\n    const config = getDocusaurusConfig(baseDir);\n    const cleanUrl = config.url.replace(/\\/$/, '');\n    const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl;\n    console.log(`📍 Using base URL: ${cleanUrl}${cleanBaseUrl}`);\n\n    // Ensure output directory exists\n    if (!fs.existsSync(outputDir)) {\n        fs.mkdirSync(outputDir, { recursive: true });\n    }\n\n    try {\n        // Generate llms.txt files for all languages\n        for (const language of LANGUAGES) {\n            const langName = LANGUAGE_NAMES[language];\n            console.log(`📝 Generating ${langName} llms.txt files...`);\n            await generateLanguageFiles(language, baseDir, outputDir, config);\n        }\n\n        console.log('✅ Successfully generated all llms.txt files!');\n    } catch (error) {\n        console.error('❌ Error generating llms.txt files:', error);\n        process.exit(1);\n    }\n}\n\n/**\n * Generates llms.txt files for a specific language\n * @param language - 'typescript', 'python', or 'csharp'\n * @param baseDir - Base directory path\n * @param outputDir - Output directory path\n * @param config - Docusaurus config object\n */\nasync function generateLanguageFiles(language: Language, baseDir: string, outputDir: string, config: DocusaurusConfig): Promise<void> {\n    // Collect all relevant files\n    const mainFiles: string[] = [];\n    const langFiles = collectFiles(path.join(baseDir, 'docs', 'main', language));\n\n    // Process all files to get metadata and file mapping\n    const { processedFiles, fileMapping } = await processAllFiles(\n        [...mainFiles, ...langFiles],\n        baseDir,\n        language\n    );\n\n    // Generate individual TXT files for each doc\n    await generateIndividualTxtFiles(\n        processedFiles,\n        outputDir,\n        language,\n        baseDir,\n        config,\n        fileMapping\n    );\n\n    // Process content for small version (navigation index)\n    const smallContent = await generateSmallVersionHierarchical(\n        language,\n        baseDir,\n        config,\n        fileMapping\n    );\n\n    // Process content for full version (all documentation)\n    const fullContent = await generateFullVersion(language, processedFiles, baseDir);\n\n    // Write main llms.txt files\n    const smallPath = path.join(outputDir, `llms_${language}.txt`);\n    const fullPath = path.join(outputDir, `llms_${language}_full.txt`);\n\n    fs.writeFileSync(smallPath, smallContent, 'utf8');\n    fs.writeFileSync(fullPath, fullContent, 'utf8');\n\n    console.log(`  ✓ Generated ${path.basename(smallPath)} (${formatBytes(smallContent.length)})`);\n    console.log(`  ✓ Generated ${path.basename(fullPath)} (${formatBytes(fullContent.length)})`);\n    console.log(`  ✓ Generated ${processedFiles.length} individual .txt files`);\n}\n\n/**\n * Processes all files and returns structured data\n * @param allFiles - All file paths to process\n * @param baseDir - Base directory path\n * @param language - Language identifier for filtering language-specific files\n * @returns Object with processedFiles array and fileMapping Map\n */\nasync function processAllFiles(allFiles: string[], baseDir: string, language: Language): Promise<{ processedFiles: ProcessedFile[]; fileMapping: Map<string, string> }> {\n    const processedFiles: ProcessedFile[] = [];\n\n    // First pass: build file mapping\n    const fileMapping = new Map<string, string>();\n    for (const file of allFiles) {\n        // Generate the same filename logic as used in generateIndividualTxtFiles\n        const tempProcessed = await processContent(file, baseDir, false, null, null, language); // Quick pass for title with language filtering\n        if (tempProcessed) {\n            // Only process files that aren't marked to ignore\n            let fileName: string;\n            if (path.basename(file) === 'README.md') {\n                const parentDir = path.basename(path.dirname(file));\n                fileName = generateSafeFileName(parentDir);\n            } else {\n                fileName = generateSafeFileName(tempProcessed.title || file);\n            }\n            fileMapping.set(file, fileName);\n        }\n    }\n\n    // Second pass: process files with mapping for link resolution\n    for (const file of allFiles) {\n        const processed = await processContent(file, baseDir, true, fileMapping, null, language);\n        if (processed && (processed.title || processed.content)) {\n            processedFiles.push(processed);\n        }\n    }\n\n    // Sort by sidebar position, then by title\n    const sortedFiles = processedFiles.sort((a, b) => {\n        const posA = a.sidebarPosition || 999;\n        const posB = b.sidebarPosition || 999;\n\n        if (posA !== posB) {\n            return posA - posB;\n        }\n\n        return (a.title || '').localeCompare(b.title || '');\n    });\n\n    return { processedFiles: sortedFiles, fileMapping };\n}\n\n/**\n * Generates individual .txt files for each documentation file\n * @param processedFiles - Array of processed file objects\n * @param outputDir - Output directory path\n * @param language - Language identifier\n * @param baseDir - Base directory path\n * @param config - Docusaurus config object\n * @param fileMapping - File mapping for link resolution\n */\nasync function generateIndividualTxtFiles(\n    processedFiles: ProcessedFile[],\n    outputDir: string,\n    language: Language,\n    baseDir: string,\n    config: DocusaurusConfig,\n    fileMapping: Map<string, string>\n): Promise<void> {\n    const docsDir = path.join(outputDir, `docs_${language}`);\n\n    // Clean and recreate docs directory to remove old files\n    if (fs.existsSync(docsDir)) {\n        fs.rmSync(docsDir, { recursive: true });\n    }\n    fs.mkdirSync(docsDir, { recursive: true });\n\n    for (const file of processedFiles) {\n        if (!file.content) continue;\n\n        // Re-process the file with full URL generation for individual files\n        const reprocessed = await processContent(\n            file.filePath,\n            baseDir,\n            true,\n            fileMapping,\n            config,\n            language\n        );\n\n        if (!reprocessed) continue;\n\n        // Generate safe filename - use folder name for README.md files\n        let fileName: string;\n        if (path.basename(file.filePath) === 'README.md') {\n            // Use parent folder name for README files\n            const parentDir = path.basename(path.dirname(file.filePath));\n            fileName = generateSafeFileName(parentDir);\n        } else {\n            fileName = generateSafeFileName(file.title || file.filePath);\n        }\n\n        const outputPath = path.join(docsDir, `${fileName}.txt`);\n\n        // Use the reprocessed content directly without adding metadata header\n        let txtContent = reprocessed.content || file.content; // Use reprocessed content with full URLs\n\n        fs.writeFileSync(outputPath, txtContent, 'utf8');\n    }\n}\n\n/**\n * Generates the small version of llms.txt (navigation index)\n * @param language - Language identifier\n * @param baseDir - Base directory path\n * @param config - Docusaurus config object\n * @param fileMapping - Mapping of source files to generated filenames\n * @returns Generated navigation content\n */\nasync function generateSmallVersionHierarchical(language: Language, baseDir: string, config: DocusaurusConfig, fileMapping: Map<string, string>): Promise<string> {\n    const langName = LANGUAGE_NAMES[language];\n    // Remove trailing slash from URL and ensure baseUrl starts with slash\n    const cleanUrl = config.url.replace(/\\/$/, '');\n    const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl;\n    const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`;\n\n    let content = `# Teams SDK - ${langName} Documentation\\n\\n`;\n    content += COMMON_OVERALL_SUMMARY(language) + '\\n\\n';\n\n    // Get hierarchical structure\n    const hierarchical = getHierarchicalFiles(baseDir, `main/${language}`);\n\n    // Add Language-specific Documentation\n    content += renderHierarchicalStructure(hierarchical.language, fullBaseUrl, language, fileMapping, 0);\n\n    return content;\n}\n\n/**\n * Renders hierarchical structure with proper indentation\n * @param structure - Hierarchical structure object\n * @param baseUrl - Base URL for links\n * @param language - Language identifier\n * @param fileMapping - Mapping of source files to generated filenames\n * @param indentLevel - Current indentation level (0 = section headers, 1+ = bullet points)\n * @returns Rendered content with proper hierarchy\n */\nfunction renderHierarchicalStructure(structure: { [key: string]: FolderStructure }, baseUrl: string, language: Language, fileMapping: Map<string, string>, indentLevel: number = 0): string {\n    let content = '';\n\n    // Helper function for folder name formatting\n    function formatFolderName(name: string): string {\n        return name.replace(/[-_]/g, ' ').replace(/\\b\\w/g, (l) => l.toUpperCase());\n    }\n\n    // Convert structure to sorted array\n    const folders = Object.entries(structure)\n        .map(([key, value]) => ({ key, ...value }))\n        .sort((a, b) => {\n            const orderA = a.order || 999;\n            const orderB = b.order || 999;\n            if (orderA !== orderB) return orderA - orderB;\n            return a.key.localeCompare(b.key);\n        });\n\n    for (const folder of folders) {\n        // Check if this folder has any content (files or children)\n        const hasFiles = folder.files && folder.files.length > 0;\n        const hasChildren = folder.children && Object.keys(folder.children).length > 0;\n        const hasContent = hasFiles || hasChildren;\n\n        if (hasContent) {\n            // Check if this folder has a README file to make the header clickable\n            const readmeFile = hasFiles\n                ? folder.files.find((f) => path.basename(f.path) === 'README.md')\n                : null;\n            const displayTitle =\n                folder.title && folder.title !== folder.key ? folder.title : formatFolderName(folder.key);\n\n            // Generate indent for nested folders (use spaces, 2 per level)\n            const indent = '  '.repeat(indentLevel);\n\n            if (readmeFile) {\n                // Make folder header clickable by linking to the README\n                let folderFileName: string;\n                if (fileMapping && fileMapping.has(readmeFile.path)) {\n                    folderFileName = fileMapping.get(readmeFile.path)!;\n                } else {\n                    folderFileName = generateSafeFileName(folder.key);\n                }\n\n                // Use ### header for top-level sections (indentLevel 0), bullet points for nested\n                if (indentLevel === 0) {\n                    content += `### [${displayTitle}](${baseUrl}llms_docs/docs_${language}/${folderFileName}.txt)\\n\\n`;\n                } else {\n                    content += `${indent}- [${displayTitle}](${baseUrl}llms_docs/docs_${language}/${folderFileName}.txt)`;\n                }\n\n                // Add summary from README if available\n                try {\n                    const readmeContent = readFileUtf8Normalized(readmeFile.path);\n                    const { frontmatter } = FrontmatterParser.extract(readmeContent);\n                    const summary = frontmatter.summary;\n                    if (summary) {\n                        if (indentLevel === 0) {\n                            content += `${summary}\\n\\n`;\n                        } else {\n                            content += `: ${summary}`;\n                        }\n                    }\n                } catch (error) {\n                    // Ignore errors reading README summary\n                }\n\n                if (indentLevel > 0) {\n                    content += '\\n';\n                }\n            } else {\n                // No README\n                if (indentLevel === 0) {\n                    content += `### ${displayTitle}\\n\\n`;\n                } else {\n                    content += `${indent}- ${displayTitle}\\n`;\n                }\n            }\n\n            // Add files in this folder (sorted by order), excluding README\n            if (hasFiles) {\n                const sortedFiles = [...folder.files]\n                    .filter((f) => path.basename(f.path) !== 'README.md') // Exclude README since it's now the header link\n                    .sort((a, b) => {\n                        const orderA = a.order || 999;\n                        const orderB = b.order || 999;\n                        if (orderA !== orderB) return orderA - orderB;\n                        return a.name.localeCompare(b.name);\n                    });\n\n                for (const file of sortedFiles) {\n                    // Use file mapping to get the correct generated filename\n                    let fileName: string;\n                    if (fileMapping && fileMapping.has(file.path)) {\n                        fileName = fileMapping.get(file.path)!;\n                    } else {\n                        fileName = generateSafeFileName(file.title || file.name);\n                    }\n\n                    const summary = extractSummaryFromFile(file.path);\n\n                    // Files are always indented one level deeper than their parent folder\n                    const fileIndent = '  '.repeat(indentLevel + 1);\n                    content += `${fileIndent}- [${file.title}](${baseUrl}llms_docs/docs_${language}/${fileName}.txt)`;\n                    if (summary) {\n                        content += `: ${summary}`;\n                    }\n                    content += '\\n';\n                }\n            }\n\n            // Recursively render children with increased indent\n            if (hasChildren) {\n                content += renderHierarchicalStructure(folder.children, baseUrl, language, fileMapping, indentLevel + 1);\n            }\n\n            // Add spacing after top-level sections\n            if (indentLevel === 0) {\n                content += '\\n';\n            }\n        }\n    }\n\n    return content;\n}\n\n/**\n * Extracts summary from a file (cached approach)\n * @param filePath - Path to the file\n * @returns File summary or empty string\n */\nfunction extractSummaryFromFile(filePath: string): string {\n    try {\n        const fileContent = readFileUtf8Normalized(filePath);\n\n        // First check for summary in frontmatter\n        const { frontmatter, content } = FrontmatterParser.extract(fileContent);\n        const summary = frontmatter.summary;\n        if (summary && typeof summary === 'string') {\n            return summary;\n        }\n\n        // Remove HTML comments before extracting summary\n        // Generated .mdx files contain AUTO-GENERATED warnings that shouldn't appear in summaries\n        const cleanContent = content.replace(/<!--[\\s\\S]*?-->/g, '');\n\n        // Fallback to extracting first meaningful paragraph if no summary in frontmatter\n        const paragraphs = cleanContent.split('\\n\\n');\n        for (const paragraph of paragraphs) {\n            const clean = paragraph\n                .replace(/#+\\s*/g, '') // Remove headers\n                .replace(/\\*\\*(.+?)\\*\\*/g, '$1') // Remove bold\n                .replace(/\\*(.+?)\\*/g, '$1') // Remove italic\n                .replace(/`(.+?)`/g, '$1') // Remove inline code\n                .replace(/\\[(.+?)\\]\\(.+?\\)/g, '$1') // Remove links, keep text\n                .trim();\n\n            if (clean.length > 20 && !clean.startsWith('```') && !clean.startsWith('import')) {\n                return clean.length > 100 ? clean.substring(0, 100) + '...' : clean;\n            }\n        }\n    } catch (error) {\n        // Ignore file read errors\n    }\n\n    return '';\n}\n\n/**\n * Generates the full version of llms.txt (complete documentation)\n * @param language - Language identifier\n * @param processedFiles - Array of processed file objects\n * @param baseDir - Base directory path\n * @returns Generated content\n */\nasync function generateFullVersion(language: Language, processedFiles: ProcessedFile[], baseDir: string): Promise<string> {\n    const langName = LANGUAGE_NAMES[language]\n    let content = `# Teams SDK - ${langName} Documentation (Complete)\\n\\n`;\n    content += COMMON_OVERALL_SUMMARY(language) + '\\n\\n';\n\n    // Group files by section\n    const sections = groupFilesBySection(processedFiles, baseDir);\n\n    // Process all sections\n    for (const [sectionName, files] of Object.entries(sections)) {\n        if (!files || files.length === 0) continue;\n\n        content += `## ${formatSectionName(sectionName)}\\n\\n`;\n\n        for (const file of files) {\n            if (file.content) {\n                content += `### ${file.title}\\n\\n${file.content}\\n\\n---\\n\\n`;\n            }\n        }\n    }\n\n    return content;\n}\n\n/**\n * Groups files by their section based on file path\n * @param processedFiles - Array of processed file objects\n * @param baseDir - Base directory path\n * @returns Grouped files by section\n */\nfunction groupFilesBySection(processedFiles: ProcessedFile[], baseDir: string): { [key: string]: ProcessedFile[] } {\n    const sections: { [key: string]: ProcessedFile[] } = {\n        main: [],\n        gettingStarted: [],\n        essentials: [],\n        inDepthGuides: [],\n        migrations: [],\n    };\n\n    for (const file of processedFiles) {\n        const relativePath = path.relative(path.join(baseDir, 'docs'), file.filePath);\n\n        if (relativePath.startsWith('main/')) {\n            sections.main.push(file);\n        } else if (relativePath.includes('getting-started/')) {\n            sections.gettingStarted.push(file);\n        } else if (relativePath.includes('essentials/')) {\n            sections.essentials.push(file);\n        } else if (relativePath.includes('in-depth-guides/')) {\n            sections.inDepthGuides.push(file);\n        } else if (relativePath.includes('migrations/')) {\n            sections.migrations.push(file);\n        } else {\n            // Create dynamic section based on directory\n            const parts = relativePath.split('/');\n            if (parts.length > 1) {\n                const sectionKey = parts[1].replace(/[^a-zA-Z0-9]/g, '');\n                if (!sections[sectionKey]) {\n                    sections[sectionKey] = [];\n                }\n                sections[sectionKey].push(file);\n            }\n        }\n    }\n\n    // Sort files within each section by sidebar position\n    for (const sectionFiles of Object.values(sections)) {\n        sectionFiles.sort((a, b) => {\n            const posA = a.sidebarPosition || 999;\n            const posB = b.sidebarPosition || 999;\n\n            if (posA !== posB) {\n                return posA - posB;\n            }\n\n            return (a.title || '').localeCompare(b.title || '');\n        });\n    }\n\n    return sections;\n}\n\n/**\n * Generates a safe filename from a title\n * @param title - Title to convert to filename\n * @returns Safe filename\n */\nfunction generateSafeFileName(title: string): string {\n    return (\n        title\n            .toLowerCase()\n            .replace(/[^a-z0-9\\s-]/g, '') // Remove special characters\n            .replace(/\\s+/g, '-') // Replace spaces with hyphens\n            .replace(/-+/g, '-') // Replace multiple hyphens with single\n            .replace(/^-|-$/g, '') // Remove leading/trailing hyphens\n            .substring(0, 50) || // Limit length\n        'untitled'\n    );\n}\n\n/**\n * Formats a section name for display\n * @param sectionName - Section name to format\n * @returns Formatted section name\n */\nfunction formatSectionName(sectionName: string): string {\n    const nameMap: { [key: string]: string } = {\n        main: 'Main Documentation',\n        gettingStarted: 'Getting Started',\n        essentials: 'Essentials',\n        inDepthGuides: 'In-Depth Guides',\n        migrations: 'Migrations',\n    };\n\n    return (\n        nameMap[sectionName] ||\n        sectionName\n            .replace(/([A-Z])/g, ' $1') // Add spaces before capitals\n            .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter\n            .trim()\n    );\n}\n\n/**\n * Formats bytes into human-readable format\n * @param bytes - Number of bytes\n * @returns Formatted string\n */\nfunction formatBytes(bytes: number): string {\n    if (bytes === 0) return '0 B';\n    const k = 1024;\n    const sizes = ['B', 'KB', 'MB'];\n    const i = Math.floor(Math.log(bytes) / Math.log(k));\n    return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];\n}\n\n// Run the generator if this file is executed directly\nif (require.main === module) {\n    generateLlmsTxt();\n}\n\nexport { generateLlmsTxt };\n"
  },
  {
    "path": "teams.md/scripts/lib/content-processor.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { FrontmatterParser } from './frontmatter-parser';\nimport readFileUtf8Normalized from '../../src/utils/readFileUtf8Normalized';\n\ninterface ProcessedContent {\n    title: string;\n    content: string;\n    frontmatter: { [key: string]: any };\n    filePath: string;\n    sidebarPosition: number;\n    relativeUrl: string;\n}\n\ninterface ParsedMarkdown {\n    title: string;\n    content: string;\n    frontmatter: { [key: string]: any };\n}\n\ninterface DocusaurusConfig {\n    url: string;\n    baseUrl: string;\n}\n\n/**\n * Checks if a file should be ignored based on section-wide README filtering\n * @param filePath - Path to the file to check\n * @returns True if file should be ignored due to section filtering\n */\nexport function shouldIgnoreFileBySection(filePath: string): boolean {\n    // Get the directory path\n    let currentDir = path.dirname(filePath);\n\n    // Walk up the directory tree looking for README.md or index.mdx files\n    while (currentDir && currentDir !== path.dirname(currentDir)) {\n        const readmePath = path.join(currentDir, 'README.md');\n        const indexPath = path.join(currentDir, 'index.mdx');\n\n        // Check README.md first, then index.mdx\n        const indexFilePath = fs.existsSync(readmePath) ? readmePath : (fs.existsSync(indexPath) ? indexPath : null);\n\n        if (indexFilePath) {\n            try {\n                const indexContent = readFileUtf8Normalized(indexFilePath);\n                const indexFrontmatter = FrontmatterParser.extract(indexContent).frontmatter;\n                // Only ignore entire section if index file has 'llms: ignore' (not 'ignore-file')\n                if (indexFrontmatter.llms === 'ignore' || indexFrontmatter.llms === false) {\n                    return true;\n                }\n            } catch (error) {\n                // Ignore errors reading index file\n            }\n        }\n\n        // Move up one directory\n        currentDir = path.dirname(currentDir);\n    }\n\n    return false;\n}\n\n/**\n * Processes a markdown/MDX file and extracts its content\n * @param filePath - Path to the file to process\n * @param baseDir - Base directory for resolving relative paths\n * @param includeCodeBlocks - Whether to include FileCodeBlock content\n * @param fileMapping - Optional mapping of source files to generated filenames\n * @param config - Optional Docusaurus config for full URL generation\n * @param language - Optional language identifier for URL generation\n * @returns Processed content with title, content, and metadata\n */\nexport async function processContent(\n    filePath: string,\n    baseDir: string,\n    includeCodeBlocks: boolean = false,\n    fileMapping: Map<string, string> | null = null,\n    config: DocusaurusConfig | null = null,\n    language: string | null = null\n): Promise<ProcessedContent | null> {\n    try {\n        if (!fs.existsSync(filePath)) {\n            console.warn(`⚠️ File not found: ${filePath}`);\n            return { title: '', content: '', frontmatter: {}, filePath, sidebarPosition: 999, relativeUrl: '' };\n        }\n\n    const rawContent = readFileUtf8Normalized(filePath);\n\n        // Check if this file should be excluded from LLM output\n        if (FrontmatterParser.shouldIgnore(rawContent)) {\n            return null; // Return null to indicate this file should be skipped\n        }\n\n        // Check if language filtering is enabled and if this file is for a different language\n        const { frontmatter: earlyFrontmatter } = FrontmatterParser.extract(rawContent);\n        if (language && earlyFrontmatter.languages) {\n            const fileLanguages = Array.isArray(earlyFrontmatter.languages)\n                ? earlyFrontmatter.languages\n                : [earlyFrontmatter.languages];\n            if (!fileLanguages.includes(language)) {\n                return null; // Skip this file as it's not for the current language\n            }\n        }\n\n        const { title, content, frontmatter } = await parseMarkdownContent(rawContent, baseDir, includeCodeBlocks, filePath, fileMapping, config, language);\n\n        // Check if this file should be ignored due to section-wide filtering\n        if (shouldIgnoreFileBySection(filePath)) {\n            return null; // Return null to indicate this file should be skipped\n        }\n\n        return {\n            title: title,\n            content: content || '',\n            frontmatter: frontmatter || {},\n            filePath,\n            sidebarPosition: (frontmatter.sidebar_position as number) || 999,\n            relativeUrl: generateRelativeUrl(filePath, baseDir)\n        };\n    } catch (error) {\n        console.error(`❌ Error processing file ${filePath}:`, (error as Error).message);\n        throw error; // Re-throw to fail the build\n    }\n}\n\n/**\n * Parses markdown/MDX content and extracts title and content\n * @param rawContent - Raw file content\n * @param baseDir - Base directory for resolving paths\n * @param includeCodeBlocks - Whether to process FileCodeBlocks\n * @param filePath - Current file path for resolving relative links\n * @param fileMapping - Optional mapping of source files to generated filenames\n * @param config - Optional Docusaurus config for full URL generation\n * @param language - Optional language identifier for URL generation\n * @returns Parsed title, content, and frontmatter\n */\nasync function parseMarkdownContent(\n    rawContent: string,\n    baseDir: string,\n    includeCodeBlocks: boolean,\n    filePath: string,\n    fileMapping: Map<string, string> | null,\n    config: DocusaurusConfig | null,\n    language: string | null\n): Promise<ParsedMarkdown> {\n    // Extract and remove frontmatter using enhanced parser with js-yaml\n    const { frontmatter, content: contentWithoutFrontmatter } = FrontmatterParser.extract(rawContent);\n    let content = contentWithoutFrontmatter;\n\n    // Extract title from first H1 (always required)\n    const h1Match = content.match(/^#\\s+(.+)$/m);\n    if (!h1Match) {\n        throw new Error(`No # header found in file: ${filePath}`);\n    }\n    const title = h1Match[1].trim();\n\n    // Remove import statements\n    content = content.replace(/^import\\s+.*$/gm, '');\n\n    // Process FileCodeBlock components if requested\n    if (includeCodeBlocks) {\n        content = await processFileCodeBlocks(content, baseDir);\n    } else {\n        // Remove FileCodeBlock components for small version\n        content = content.replace(/<FileCodeBlock[\\s\\S]*?\\/>/g, '[Code example removed for brevity]');\n    }\n\n    // Clean up MDX-specific syntax while preserving markdown\n    content = cleanMdxSyntax(content);\n\n    // Fix internal relative links\n    content = fixInternalLinks(content, filePath, fileMapping, config, language);\n\n    // Remove excessive whitespace\n    content = content.replace(/\\n{3,}/g, '\\n\\n').trim();\n\n    return { title, content, frontmatter };\n}\n\n/**\n * Processes FileCodeBlock components and includes the referenced code\n * @param content - Content containing FileCodeBlock components\n * @param baseDir - Base directory for resolving paths\n * @returns Content with FileCodeBlocks replaced by actual code\n */\nasync function processFileCodeBlocks(content: string, baseDir: string): Promise<string> {\n    const fileCodeBlockRegex = /<FileCodeBlock\\s+([^>]+)\\/>/g;\n    let processedContent = content;\n    let match;\n\n    while ((match = fileCodeBlockRegex.exec(content)) !== null) {\n        const attributes = parseAttributes(match[1]);\n        const { src, lang } = attributes;\n\n        if (src) {\n            try {\n                const codeContent = await loadCodeFile(src, baseDir);\n                const codeBlock = `\\`\\`\\`${lang || 'typescript'}\\n${codeContent}\\n\\`\\`\\``;\n                processedContent = processedContent.replace(match[0], codeBlock);\n            } catch (error) {\n                console.warn(`⚠️ Could not load code file ${src}:`, (error as Error).message);\n                processedContent = processedContent.replace(match[0], `[Code file not found: ${src}]`);\n            }\n        }\n    }\n\n    return processedContent;\n}\n\n/**\n * Loads code content from a file referenced by FileCodeBlock\n * @param src - Source path from FileCodeBlock (e.g., \"/generated-snippets/ts/example.ts\")\n * @param baseDir - Base directory\n * @returns Code content\n */\nasync function loadCodeFile(src: string, baseDir: string): Promise<string> {\n    // Convert src path to local file path\n    let filePath: string;\n    if (src.startsWith('/')) {\n        filePath = path.join(baseDir, 'static', src.substring(1));\n    } else {\n        filePath = path.join(baseDir, 'static', src);\n    }\n\n    if (fs.existsSync(filePath)) {\n        return readFileUtf8Normalized(filePath).trim();\n    } else {\n        throw new Error(`File not found: ${filePath}`);\n    }\n}\n\n/**\n * Parse JSX-style attributes from attribute string\n * @param attributeString - String containing attributes\n * @returns Parsed attributes\n */\nfunction parseAttributes(attributeString: string): { [key: string]: string } {\n    const attributes: { [key: string]: string } = {};\n    const regex = /(\\w+)=[\"']([^\"']+)[\"']/g;\n    let match;\n\n    while ((match = regex.exec(attributeString)) !== null) {\n        attributes[match[1]] = match[2];\n    }\n\n    return attributes;\n}\n\n/**\n * Cleans MDX-specific syntax while preserving standard markdown\n * @param content - Content to clean\n * @returns Cleaned content\n */\nfunction cleanMdxSyntax(content: string): string {\n    let cleaned = content;\n\n    // Remove HTML comments from llms.txt output\n    // Generated .mdx files contain AUTO-GENERATED warnings as HTML comments for developers,\n    // but these developer notes don't belong in AI-friendly documentation files.\n    // Note: Section markers (<!-- section-name -->) in .incl.md source files are processed\n    // earlier by generate-language-docs.ts and never appear in generated .mdx files.\n    cleaned = cleaned.replace(/<!--[\\s\\S]*?-->/g, '');\n\n    // Remove JSX components (except code blocks which are handled separately)\n    cleaned = cleaned.replace(/<\\/?[A-Z][^>]*>/g, '');\n\n    // Remove empty JSX fragments\n    cleaned = cleaned.replace(/<>\\s*<\\/>/g, '');\n\n    // Remove JSX expressions but keep the content if it's simple text\n    // IMPORTANT: Don't process content inside code blocks (```)\n    const codeBlockRegex = /```[\\s\\S]*?```/g;\n    const codeBlocks: string[] = [];\n\n    // Extract code blocks temporarily\n    cleaned = cleaned.replace(codeBlockRegex, (match) => {\n        codeBlocks.push(match);\n        return `___CODE_BLOCK_${codeBlocks.length - 1}___`;\n    });\n\n    // Now remove JSX expressions outside code blocks\n    cleaned = cleaned.replace(/\\{([^{}]+)\\}/g, (match, expr) => {\n        // Keep simple text expressions, remove complex ones\n        if (expr.includes('(') || expr.includes('.') || expr.includes('[')) {\n            return '';\n        }\n        return expr;\n    });\n\n    // Restore code blocks\n    cleaned = cleaned.replace(/___CODE_BLOCK_(\\d+)___/g, (match, index) => {\n        return codeBlocks[parseInt(index)];\n    });\n\n    // Clean up multiple empty lines\n    cleaned = cleaned.replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n');\n\n    return cleaned;\n}\n\n\n/**\n * Generates a relative URL for a documentation file\n * @param filePath - Full path to the file\n * @param baseDir - Base directory path\n * @returns Relative URL for the file\n */\nexport function generateRelativeUrl(filePath: string, baseDir: string): string {\n    const relativePath = path.relative(path.join(baseDir, 'docs'), filePath);\n\n    // Convert file path to URL format\n    let url = relativePath\n        .replace(/\\\\/g, '/') // Convert Windows paths\n        .replace(/\\.mdx?$/, '') // Remove .md/.mdx extension\n        .replace(/\\/README$/i, '') // Remove /README from end\n        .replace(/\\/index$/i, ''); // Remove /index from end\n\n    // Add leading slash\n    if (!url.startsWith('/')) {\n        url = '/' + url;\n    }\n\n    // Handle empty URL (root README)\n    if (url === '/') {\n        url = '';\n    }\n\n    return url;\n}\n\n/**\n * Fixes internal relative links to point to generated .txt files\n * @param content - Content containing markdown links\n * @param currentFilePath - Path of the current file being processed\n * @param fileMapping - Optional mapping of source files to generated filenames\n * @param config - Optional Docusaurus config for full URL generation\n * @param language - Optional language identifier for URL generation\n * @returns Content with fixed links\n */\nexport function fixInternalLinks(\n    content: string,\n    currentFilePath: string,\n    fileMapping: Map<string, string> | null,\n    config: DocusaurusConfig | null,\n    language: string | null\n): string {\n    // Pattern to match markdown links: [text](link)\n    const linkRegex = /\\[([^\\]]+)\\]\\(([^)]+)\\)/g;\n\n    return content.replace(linkRegex, (match, text, link) => {\n        // Skip external links (http/https/mailto/etc)\n        if (link.startsWith('http') || link.startsWith('mailto') || link.startsWith('#')) {\n            return match;\n        }\n\n        // Skip absolute paths starting with /\n        if (link.startsWith('/') && !link.startsWith('//')) {\n            return match;\n        }\n\n        // Handle relative links\n        if (!link.includes('://')) {\n            // Remove any file extensions and anchors\n            const cleanLink = link.split('#')[0].replace(/\\.(md|mdx)$/, '');\n\n            // If it's just a filename without path separators, it's likely a sibling file\n            if (!cleanLink.includes('/')) {\n                // Try to resolve using file mapping first\n                if (fileMapping) {\n                    const currentDir = path.dirname(currentFilePath);\n                    const possiblePath = path.join(currentDir, cleanLink + '.md');\n                    const possibleMdxPath = path.join(currentDir, cleanLink + '.mdx');\n\n                    // Look for the file in the mapping\n                    for (const [sourcePath, generatedName] of fileMapping.entries()) {\n                        // Check exact path match or basename match\n                        if (sourcePath === possiblePath ||\n                            sourcePath === possibleMdxPath ||\n                            path.basename(sourcePath, '.md') === cleanLink ||\n                            path.basename(sourcePath, '.mdx') === cleanLink) {\n\n                            // Generate full URL if config and language are provided\n                            if (config && language) {\n                                const cleanUrl = config.url.replace(/\\/$/, '');\n                                const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl;\n                                const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`;\n                                return `[${text}](${fullBaseUrl}llms_docs/docs_${language}/${generatedName}.txt)`;\n                            } else {\n                                return `[${text}](${generatedName}.txt)`;\n                            }\n                        }\n                    }\n                }\n\n                // Fallback to simple conversion\n                const safeFileName = cleanLink\n                    .toLowerCase()\n                    .replace(/[^a-z0-9\\s-]/g, '')\n                    .replace(/\\s+/g, '-')\n                    .replace(/-+/g, '-')\n                    .replace(/^-|-$/g, '')\n                    .substring(0, 50)\n                    || 'untitled';\n\n                // Generate full URL if config and language are provided\n                if (config && language) {\n                    const cleanUrl = config.url.replace(/\\/$/, '');\n                    const cleanBaseUrl = config.baseUrl.startsWith('/') ? config.baseUrl : '/' + config.baseUrl;\n                    const fullBaseUrl = `${cleanUrl}${cleanBaseUrl}`;\n                    return `[${text}](${fullBaseUrl}llms_docs/docs_${language}/${safeFileName}.txt)`;\n                } else {\n                    return `[${text}](${safeFileName}.txt)`;\n                }\n            }\n        }\n\n        // Return original link if we can't process it\n        return match;\n    });\n}\n\n/**\n * Extracts a summary from content (first paragraph or section)\n * @param content - Content to summarize\n * @param maxLength - Maximum length of summary\n * @returns Content summary\n */\nexport function extractSummary(content: string, maxLength: number = 200): string {\n    // Remove markdown formatting for summary\n    let summary = content\n        .replace(/#+\\s*/g, '') // Remove headers\n        .replace(/\\*\\*(.+?)\\*\\*/g, '$1') // Remove bold\n        .replace(/\\*(.+?)\\*/g, '$1') // Remove italic\n        .replace(/`(.+?)`/g, '$1') // Remove inline code\n        .replace(/\\[(.+?)\\]\\(.+?\\)/g, '$1') // Remove links, keep text\n        .trim();\n\n    // Get first paragraph\n    const firstParagraph = summary.split('\\n\\n')[0];\n\n    // Truncate if too long\n    if (firstParagraph.length > maxLength) {\n        return firstParagraph.substring(0, maxLength).trim() + '...';\n    }\n\n    return firstParagraph;\n}\n"
  },
  {
    "path": "teams.md/scripts/lib/file-collector.ts",
    "content": "import * as fs from 'fs';\nimport * as path from 'path';\n\nimport { FrontmatterParser } from './frontmatter-parser';\nimport readFileUtf8Normalized from '../../src/utils/readFileUtf8Normalized';\n\ninterface FileInfo {\n    name: string;\n    title: string;\n    path: string;\n    order: number;\n}\n\ninterface FolderStructure {\n    title: string;\n    order: number;\n    path: string;\n    files: FileInfo[];\n    children: { [key: string]: FolderStructure };\n}\n\ninterface HierarchicalFiles {\n    language: { [key: string]: FolderStructure };\n}\n\n/**\n * Recursively collects all markdown and MDX files from a directory\n * @param dirPath - Directory path to search\n * @param extensions - File extensions to collect (default: ['.md', '.mdx'])\n * @returns Array of file paths\n */\nexport function collectFiles(dirPath: string, extensions: string[] = ['.md', '.mdx']): string[] {\n    const files: string[] = [];\n\n    if (!fs.existsSync(dirPath)) {\n        console.warn(`⚠️ Directory not found: ${dirPath}`);\n        return files;\n    }\n\n    /**\n     * Recursively traverse directory\n     * @param currentPath - Current directory path\n     */\n    function traverse(currentPath: string): void {\n        const items = fs.readdirSync(currentPath, { withFileTypes: true });\n\n        for (const item of items) {\n            const fullPath = path.join(currentPath, item.name);\n\n            if (item.isDirectory()) {\n                // Skip common directories that don't contain docs\n                if (!shouldSkipDirectory(item.name)) {\n                    traverse(fullPath);\n                }\n            } else if (item.isFile()) {\n                const ext = path.extname(item.name).toLowerCase();\n                if (extensions.includes(ext)) {\n                    files.push(fullPath);\n                }\n            }\n        }\n    }\n\n    traverse(dirPath);\n\n    // Sort files for consistent ordering\n    return files.sort();\n}\n\n/**\n * Determines if a directory should be skipped during traversal\n * @param dirName - Directory name\n * @returns True if directory should be skipped\n */\nexport function shouldSkipDirectory(dirName: string): boolean {\n    const skipDirs = [\n        'node_modules',\n        '.git',\n        'build',\n        'dist',\n        '.next',\n        '.docusaurus',\n        'coverage',\n        '__pycache__'\n    ];\n\n    return skipDirs.includes(dirName) || dirName.startsWith('.');\n}\n\n/**\n * Gets files organized hierarchically based on folder structure and sidebar_position\n * @param basePath - Base documentation path\n * @param language - Language identifier ('typescript' or 'csharp' or 'python')\n * @returns Hierarchically organized file structure\n */\nexport function getHierarchicalFiles(basePath: string, language: string): HierarchicalFiles {\n    const langPath = path.join(basePath, 'docs', language);\n\n    const structure: HierarchicalFiles = {\n        language: buildHierarchicalStructure(langPath)\n    };\n\n    return structure;\n}\n\n/**\n * Builds hierarchical structure for a directory\n * @param rootPath - Root directory path\n * @returns Hierarchical structure with folders and files\n */\nexport function buildHierarchicalStructure(rootPath: string): { [key: string]: FolderStructure } {\n    if (!fs.existsSync(rootPath)) {\n        return {};\n    }\n\n    const structure: { [key: string]: FolderStructure } = {};\n    const seenTitles = new Map<string, string>(); // Track titles and their file paths for duplicate detection\n\n    /**\n     * Recursively processes a directory\n     * @param dirPath - Current directory path\n     * @param currentLevel - Current level in the structure\n     */\n    function processDirectory(dirPath: string, currentLevel: { files: FileInfo[]; children: { [key: string]: FolderStructure } }): void {\n        const items = fs.readdirSync(dirPath, { withFileTypes: true });\n\n        // Collect folders and files separately\n        const folders: FileInfo[] = [];\n        const files: FileInfo[] = [];\n\n        for (const item of items) {\n            const fullPath = path.join(dirPath, item.name);\n\n            if (item.isDirectory() && !shouldSkipDirectory(item.name)) {\n                // Process subdirectory\n                // Check for both README.md and index.mdx\n                const readmePath = path.join(fullPath, 'README.md');\n                const indexPath = path.join(fullPath, 'index.mdx');\n                let folderOrder = 999;\n                let folderTitle = item.name;\n\n                // Get folder ordering from README.md or index.mdx\n                const indexFilePath = fs.existsSync(readmePath) ? readmePath : (fs.existsSync(indexPath) ? indexPath : null);\n\n                if (indexFilePath) {\n                    try {\n                        const indexContent = readFileUtf8Normalized(indexFilePath);\n                        const { frontmatter, content } = FrontmatterParser.extract(indexContent);\n\n                        // Skip this entire folder if index file is marked to ignore\n                        if (frontmatter.llms === 'ignore' || frontmatter.llms === false) {\n                            continue; // Skip this folder entirely\n                        }\n\n                        // If index file is marked ignore-file, skip just the file but process folder\n                        // (folderOrder and folderTitle will use defaults)\n\n                        folderOrder = (frontmatter.sidebar_position as number) || 999;\n\n                        // Extract title from frontmatter or first # header\n                        if (frontmatter.title || frontmatter.sidebar_label) {\n                            folderTitle = (frontmatter.title || frontmatter.sidebar_label) as string;\n                        } else {\n                            // Extract from first # header\n                            const headerMatch = content.match(/^#\\s+(.+)$/m);\n                            if (headerMatch) {\n                                folderTitle = headerMatch[1].trim();\n                            }\n                        }\n                    } catch (error) {\n                        // Ignore errors reading index file\n                    }\n                }\n\n                folders.push({\n                    name: item.name,\n                    title: folderTitle,\n                    path: fullPath,\n                    order: folderOrder\n                });\n            } else if (item.isFile() && (item.name.endsWith('.md') || item.name.endsWith('.mdx'))) {\n                // Process file\n                let fileOrder = 999;\n                let fileTitle = item.name;\n\n                try {\n                    const fileContent = readFileUtf8Normalized(fullPath);\n                    const { frontmatter, content } = FrontmatterParser.extract(fileContent);\n\n                    // Skip this file if marked to ignore (including ignore-file)\n                    if (frontmatter.llms === 'ignore' || frontmatter.llms === 'ignore-file' || frontmatter.llms === false) {\n                        continue; // Skip this file\n                    }\n\n                    fileOrder = (frontmatter.sidebar_position as number) || 999;\n\n                    // Extract title from first # header\n                    const headerMatch = content.match(/^#\\s+(.+)$/m);\n                    if (headerMatch) {\n                        fileTitle = headerMatch[1].trim();\n                    }\n\n                    // Check for duplicate titles\n                    if (seenTitles.has(fileTitle)) {\n                        const existingPath = seenTitles.get(fileTitle)!;\n                        throw new Error(\n                            `Duplicate title found: \"${fileTitle}\"\\n` +\n                            `  First occurrence: ${existingPath}\\n` +\n                            `  Duplicate found in: ${fullPath}`\n                        );\n                    }\n                    seenTitles.set(fileTitle, fullPath);\n                } catch (error) {\n                    // Re-throw to fail the build\n                    throw error;\n                }\n\n                files.push({\n                    name: item.name,\n                    title: fileTitle,\n                    path: fullPath,\n                    order: fileOrder\n                });\n            }\n        }\n\n        // Sort files by order and add to current level\n        files.sort((a, b) => {\n            if (a.order !== b.order) return a.order - b.order;\n            return a.name.localeCompare(b.name);\n        });\n\n        if (files.length > 0) {\n            if (!currentLevel.files) currentLevel.files = [];\n            currentLevel.files.push(...files);\n        }\n\n        // Sort folders by order and process each one\n        folders.sort((a, b) => {\n            if (a.order !== b.order) return a.order - b.order;\n            return a.name.localeCompare(b.name);\n        });\n\n        if (!currentLevel.children) currentLevel.children = {};\n\n        for (const folder of folders) {\n            currentLevel.children[folder.name] = {\n                title: folder.title,\n                order: folder.order,\n                path: folder.path,\n                files: [],\n                children: {}\n            };\n\n            // Recursively process subdirectory\n            processDirectory(folder.path, currentLevel.children[folder.name]);\n        }\n    }\n\n    // Create a temporary wrapper to handle the root properly\n    const tempWrapper: { files: FileInfo[]; children: { [key: string]: FolderStructure } } = { files: [], children: {} };\n    processDirectory(rootPath, tempWrapper);\n\n    // Return the children (which contain the actual folder structure)\n    return tempWrapper.children;\n}\n\n/**\n * Gets priority files for small version generation\n * @param organized - Organized file structure from getOrganizedFiles\n * @returns Priority files for small version\n */\nexport function getPriorityFiles(organized: any): string[] {\n    const priorityFiles: string[] = [];\n\n    // Add welcome/overview files\n    priorityFiles.push(...organized.main.welcome);\n\n    // Add key team concepts\n    const keyTeamFiles = organized.main.teams.filter((file: string) =>\n        file.includes('core-concepts') ||\n        file.includes('README.md')\n    );\n    priorityFiles.push(...keyTeamFiles);\n\n    // Add getting started files\n    priorityFiles.push(...organized.language.gettingStarted);\n\n    // Add essential README files\n    const essentialReadmes = organized.language.essentials.filter((file: string) =>\n        file.includes('README.md') ||\n        file.includes('app-basics')\n    );\n    priorityFiles.push(...essentialReadmes);\n\n    return priorityFiles;\n}\n"
  },
  {
    "path": "teams.md/scripts/lib/frontmatter-parser.ts",
    "content": "import * as fs from 'fs';\nimport * as yaml from 'js-yaml';\n\nimport readFileUtf8Normalized from '../../src/utils/readFileUtf8Normalized';\n\n/**\n * Regular expression to match YAML frontmatter at the start of a file\n * Matches content between --- delimiters\n */\nexport const FRONTMATTER_REGEX = /^---\\s*\\r?\\n([\\s\\S]*?)\\r?\\n---/;\n\ninterface FrontmatterData {\n    [key: string]: string | number | boolean;\n}\n\ninterface ExtractResult {\n    frontmatter: FrontmatterData;\n    content: string;\n    hasFrontmatter: boolean;\n}\n\n/**\n * Parser for YAML frontmatter in markdown/MDX files\n */\nexport class FrontmatterParser {\n    /**\n     * Extracts and parses frontmatter from content using js-yaml\n     * @param content - Raw file content\n     * @returns Object with parsed frontmatter and content without frontmatter\n     */\n    static extract(content: string): ExtractResult {\n        const match = content.match(FRONTMATTER_REGEX);\n\n        if (!match) {\n            return {\n                frontmatter: {},\n                content: content,\n                hasFrontmatter: false\n            };\n        }\n\n        try {\n            // Use js-yaml for robust YAML parsing instead of custom parser\n            const frontmatter = yaml.load(match[1]) as FrontmatterData || {};\n            const contentWithoutFrontmatter = content.replace(match[0], '').trimStart();\n\n            return {\n                frontmatter,\n                content: contentWithoutFrontmatter,\n                hasFrontmatter: true\n            };\n        } catch (error) {\n            console.warn(`Warning: Error parsing frontmatter with js-yaml, falling back to simple parser:`, error);\n            // Fallback to simple parser\n            const frontmatter = this.parseSimple(match[1]);\n            const contentWithoutFrontmatter = content.replace(match[0], '').trimStart();\n\n            return {\n                frontmatter,\n                content: contentWithoutFrontmatter,\n                hasFrontmatter: true\n            };\n        }\n    }\n\n    /**\n     * Parses frontmatter text into an object (simple parser - kept for fallback)\n     * @param frontmatterText - Raw frontmatter content (without --- delimiters)\n     * @returns Parsed frontmatter object\n     */\n    static parseSimple(frontmatterText: string): FrontmatterData {\n        const frontmatter: FrontmatterData = {};\n        const lines = frontmatterText.split('\\n');\n\n        for (const line of lines) {\n            const match = line.match(/^(\\w+):\\s*(.+)$/);\n            if (!match) continue;\n\n            const key = match[1];\n            let value: string | number | boolean = match[2].trim();\n\n            value = this._parseValue(value);\n            frontmatter[key] = value;\n        }\n\n        return frontmatter;\n    }\n\n    /**\n     * Extracts frontmatter from a file\n     * @param filePath - Path to the file\n     * @returns Parsed frontmatter or null if file doesn't exist\n     */\n    static extractFromFile(filePath: string): ExtractResult | null {\n        if (!fs.existsSync(filePath)) {\n            return null;\n        }\n\n        try {\n            const content = readFileUtf8Normalized(filePath);\n            return this.extract(content);\n        } catch (error) {\n            console.warn(`⚠️ Error reading frontmatter from ${filePath}:`, (error as Error).message);\n            return null;\n        }\n    }\n\n    /**\n     * Gets a specific property from frontmatter\n     * @param content - File content or path\n     * @param propertyName - Property to extract\n     * @param defaultValue - Default value if property not found\n     * @returns Property value or default\n     */\n    static getProperty<T = any>(content: string, propertyName: string, defaultValue?: T): T | undefined {\n        const result = typeof content === 'string' && fs.existsSync(content)\n            ? this.extractFromFile(content)\n            : this.extract(content);\n\n        if (!result) {\n            return defaultValue;\n        }\n\n        const { frontmatter } = result;\n\n        return frontmatter[propertyName] !== undefined\n            ? (frontmatter[propertyName] as T)\n            : defaultValue;\n    }\n\n    /**\n     * Checks if a file or content should be ignored based on frontmatter\n     * @param content - File content or path\n     * @returns True if should be ignored\n     */\n    static shouldIgnore(content: string): boolean {\n        const result = typeof content === 'string' && fs.existsSync(content)\n            ? this.extractFromFile(content)\n            : this.extract(content);\n\n        if (!result) {\n            return false;\n        }\n\n        const { frontmatter } = result;\n        const llmsValue = frontmatter.llms;\n        return llmsValue === 'ignore' || llmsValue === 'ignore-file' || llmsValue === false;\n    }\n\n    /**\n     * Parses a value from frontmatter, converting types as needed\n     * @private\n     * @param value - Raw value string\n     * @returns Parsed value (string, number, boolean)\n     */\n    private static _parseValue(value: string): string | number | boolean {\n        // Remove surrounding quotes\n        if ((value.startsWith('\"') && value.endsWith('\"')) ||\n            (value.startsWith(\"'\") && value.endsWith(\"'\"))) {\n            return value.slice(1, -1);\n        }\n\n        // Parse booleans\n        if (value === 'true') return true;\n        if (value === 'false') return false;\n\n        // Parse integers\n        if (/^\\d+$/.test(value)) {\n            return parseInt(value, 10);\n        }\n\n        // Return as string\n        return value;\n    }\n}\n"
  },
  {
    "path": "teams.md/sidebars.ts",
    "content": "import type {SidebarsConfig} from '@docusaurus/plugin-content-docs';\n\nexport default {\n  default: [{ type: 'autogenerated', dirName: '.' }],\n} satisfies SidebarsConfig;\n"
  },
  {
    "path": "teams.md/src/components/FileCodeBlock.tsx",
    "content": "import { useEffect, useState, PropsWithChildren } from \"react\";\nimport CodeBlock from \"@theme/CodeBlock\";\nimport useBaseUrl from \"@docusaurus/useBaseUrl\";\n\nexport type FileCodeBlockParams = {\n    readonly src: string;\n    readonly lang?: string;\n};\n\nexport default function FileCodeBlock({ src, lang }: PropsWithChildren<FileCodeBlockParams>) {\n    const [code, setCode] = useState<string>();\n    const url = useBaseUrl(src);\n\n    useEffect(() => {\n        (async () => {\n            try {\n                const res = await fetch(url);\n\n                if (!res.ok || res.status != 200) {\n                    throw new Error(`failed to load file code block with status \"${res.status}\"`);\n                }\n\n                const blob = await res.blob();\n                const data = await blob.text();\n                setCode(data.trim());\n            } catch (err) {\n                console.error('failed to load file code block', err);\n            }\n        })();\n    }, [src]);\n\n    return (\n        <CodeBlock title={process.env.NODE_ENV === 'development' ? src : undefined} language={lang}>\n            {code}\n        </CodeBlock>\n    );\n}\n"
  },
  {
    "path": "teams.md/src/components/LangLink.tsx",
    "content": "import React, { type PropsWithChildren } from 'react';\nimport Link from '@docusaurus/Link';\nimport { useLanguagePreference } from '../hooks/useLanguagePreference';\n\ninterface LangLinkProps {\n  /** Path relative to the language root, e.g. \"essentials/app-authentication\" */\n  to: string;\n}\n\n/**\n * A link component that automatically prefixes the path with the user's\n * selected language preference.\n *\n * Usage: <LangLink to=\"essentials/app-authentication\">Link text</LangLink>\n * Renders: <a href=\"/typescript/essentials/app-authentication\">Link text</a>\n */\nexport default function LangLink({ to, children }: PropsWithChildren<LangLinkProps>) {\n  const { language } = useLanguagePreference();\n  return <Link to={`/${language}/${to}`}>{children}</Link>;\n}\n"
  },
  {
    "path": "teams.md/src/components/Language.tsx",
    "content": "import { PropsWithChildren } from 'react';\nimport { useLocation } from '@docusaurus/router';\nimport { type Language } from '../constants/languages';\n\nexport type LanguageProps = {\n  readonly language: Language;\n};\n\n// Component for inserting language-specific content onto a page.\nexport default function Language({ language, children }: PropsWithChildren<LanguageProps>) {\n  const location = useLocation();\n\n  // Only render if current path matches language\n  if (!location.pathname.includes(`/${language}/`)) {\n    return null;\n  }\n\n  return <>{children}</>;\n}\n"
  },
  {
    "path": "teams.md/src/components/LanguageBanner.tsx",
    "content": "import React, { useEffect, useRef } from 'react';\nimport { createPortal } from 'react-dom';\nimport { LANGUAGE_NAMES, type Language } from '../constants/languages';\n\ninterface LanguageBannerProps {\n  targetLanguage: Language;\n  baseUrl: string;\n  onDismiss: () => void;\n}\n\nexport default function LanguageBanner({\n  targetLanguage,\n  baseUrl,\n  onDismiss,\n}: LanguageBannerProps) {\n  const actionRef = useRef<HTMLButtonElement>(null);\n\n  useEffect(() => {\n    actionRef.current?.focus();\n  }, []);\n\n  const handleGoToLanguageDocs = () => {\n    window.location.href = `${baseUrl}${targetLanguage}/`;\n  };\n\n  // Find the container to render the banner in - prefer docMainContainer for proper width\n  const container =\n    document.querySelector('.docMainContainer') ||\n    document.querySelector('main') ||\n    document.querySelector('.main-wrapper') ||\n    document.querySelector('[role=\"main\"]') ||\n    document.body;\n\n  return createPortal(\n    <div\n      aria-live=\"polite\"\n      aria-labelledby=\"language-banner-heading\"\n      role=\"region\"\n      className=\"language-banner\"\n    >\n      <div className=\"language-banner__content\">\n        <div className=\"language-banner__text\">\n          <strong>This page isn't available for {LANGUAGE_NAMES[targetLanguage]}. </strong>\n          Go to the {LANGUAGE_NAMES[targetLanguage]} documentation home instead?\n        </div>\n        <div className=\"language-banner__actions\">\n          <button\n            type=\"button\"\n            ref={actionRef}\n            className=\"language-banner__button language-banner__button--primary\"\n            onClick={handleGoToLanguageDocs}\n          >\n            Go to {LANGUAGE_NAMES[targetLanguage]} docs\n          </button>\n          <button\n            aria-label=\"Close\"\n            title=\"Close\"\n            className=\"language-banner__button language-banner__button--secondary\"\n            onClick={onDismiss}\n          >\n            ×\n          </button>\n        </div>\n      </div>\n    </div>,\n    container\n  );\n}\n"
  },
  {
    "path": "teams.md/src/components/LanguageDropdown.tsx",
    "content": "import React, { useEffect, useState, useRef } from 'react';\nimport { useLocation } from '@docusaurus/router';\nimport { useHistory } from '@docusaurus/router';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\nimport { LANGUAGE_NAMES, LANGUAGES, type Language } from '../constants/languages';\nimport { useLanguagePreference } from '../hooks/useLanguagePreference';\nimport {\n  getLanguageFromPath,\n  getLanguageFromPathStrict,\n  replaceLanguageInPath,\n  getManifestPathFromUrl,\n} from '../utils/languageUtils';\nimport { isPageAvailableForLanguage } from '../utils/pageAvailability';\nimport LanguageBanner from './LanguageBanner';\n\ninterface LanguageDropdownProps {\n  // Docusaurus navbar item props\n  className?: string;\n  position?: 'left' | 'right';\n  // Catch for default docusaurus navbar item props\n  [key: string]: any;\n}\n\nexport default function LanguageDropdown(props: LanguageDropdownProps) {\n  const { className = '', position, ...otherProps } = props;\n  const location = useLocation();\n  const history = useHistory();\n  const baseUrl = useBaseUrl('/');\n  const { language, setLanguage } = useLanguagePreference();\n\n  const buttonRef = useRef<HTMLButtonElement>(null);\n  const listRef = useRef<HTMLUListElement>(null);\n  const skipNextSync = useRef(false);\n\n  const [bannerRender, setBannerRender] = useState<{ language: Language } | null>(null);\n  const [focusedIndex, setFocusedIndex] = useState<number | null>(null);\n  const [isOpen, setIsOpen] = useState(false);\n\n  const languagesArray = Object.entries(LANGUAGE_NAMES);\n\n  const getLanguageIndex = (lang: Language): number => {\n    return Math.max(\n      0,\n      languagesArray.findIndex(([l]) => l === lang)\n    );\n  };\n\n  const handleLanguageChange = async (newLanguage: Language) => {\n    if (newLanguage === language) {\n      return;\n    }\n\n    skipNextSync.current = true;\n\n    setIsOpen(false);\n    setLanguage(newLanguage);\n\n    const currentPath = location.pathname;\n    const currentLanguage = getLanguageFromPathStrict(currentPath, baseUrl);\n\n    // Navigate to parallel newLanguage's page if we're currently in a language-specific page\n    if (currentLanguage && LANGUAGES.includes(currentLanguage)) {\n      const targetUrl = replaceLanguageInPath(currentPath, baseUrl, newLanguage);\n\n      if (targetUrl === currentPath) {\n        history.push(`${baseUrl}${newLanguage}/`);\n      } else {\n        // Convert URL path to manifest path format\n        const manifestPath = getManifestPathFromUrl(currentPath, baseUrl);\n\n        try {\n          // Debugging output\n          console.log('[LanguageDropdown] Checking page availability:', {\n            manifestPath,\n            newLanguage,\n          });\n          // Check if target page exists for the new language using availability data\n          const pageExists = await isPageAvailableForLanguage(manifestPath, newLanguage);\n          console.log('[LanguageDropdown] Page exists result:', {\n            manifestPath,\n            newLanguage,\n            pageExists,\n          });\n\n          if (pageExists) {\n            history.push(targetUrl);\n          } else {\n            // Page doesn't exist, show redirect banner instead of navigating\n            setBannerRender({ language: newLanguage });\n          }\n        } catch (error) {\n          console.error('Error checking page availability:', error, { manifestPath, newLanguage });\n          // On error, just navigate normally as fallback\n          history.push(targetUrl);\n        }\n      }\n    }\n    // No navigation necessary if on a page from `/main/` folder (general content)\n  };\n\n  const handleBannerDismiss = () => {\n    // Get the current URL language context to restore\n    const currentUrlLanguage = getLanguageFromPath(location.pathname, baseUrl);\n\n    // Restore language preference to match the current URL context\n    if (currentUrlLanguage && LANGUAGES.includes(currentUrlLanguage)) {\n      setLanguage(currentUrlLanguage);\n    }\n\n    setBannerRender(null);\n  };\n\n  const openListbox = () => {\n    setIsOpen(true);\n    setFocusedIndex(getLanguageIndex(language));\n    // Move focus to listbox\n    setTimeout(() => listRef.current?.focus(), 0);\n  };\n\n  const closeListbox = () => {\n    setIsOpen(false);\n    setFocusedIndex(null);\n    buttonRef.current?.focus();\n  };\n\n  const handleButtonClick = () => {\n    if (isOpen) {\n      closeListbox();\n    } else {\n      openListbox();\n    }\n  };\n\n  const handleBlur: React.FocusEventHandler<HTMLButtonElement> = (e) => {\n    const next = e.relatedTarget as Node | null;\n    if (!next || !listRef.current?.contains(next)) {\n      // If focus didn’t move into the listbox, close\n      setIsOpen(false);\n    }\n  };\n  const handleButtonKeyDown: React.KeyboardEventHandler<HTMLButtonElement> = (e) => {\n    if (e.key === 'ArrowDown' || e.key === 'Enter' || e.key === ' ') {\n      e.preventDefault();\n      if (!isOpen) {\n        openListbox();\n      }\n    }\n  };\n\n  // Keyboard navigation handling\n  const handleListKeyDown = (e: React.KeyboardEvent<HTMLButtonElement | HTMLUListElement>) => {\n    if (!isOpen) {\n      return;\n    }\n\n    switch (e.key) {\n      case 'Home':\n        e.preventDefault();\n        setFocusedIndex(0);\n        break;\n      case 'End':\n        e.preventDefault();\n        setFocusedIndex(languagesArray.length - 1);\n        break;\n      case 'ArrowDown':\n        e.preventDefault();\n        setFocusedIndex((prev) => {\n          if (prev === null) {\n            return 0;\n          }\n          const nextIndex = prev + 1;\n          if (nextIndex >= languagesArray.length) {\n            return languagesArray.length - 1;\n          }\n          return nextIndex;\n        });\n        break;\n      case 'ArrowUp':\n        e.preventDefault();\n        setFocusedIndex((prev) => {\n          if (prev === null) {\n            return languagesArray.length - 1;\n          }\n          const nextIndex = prev - 1;\n          if (nextIndex < 0) {\n            return 0;\n          }\n          return nextIndex;\n        });\n        break;\n      case 'Tab':\n        closeListbox();\n        break;\n      case 'Escape':\n        e.preventDefault();\n        closeListbox();\n        break;\n      case 'Enter':\n        e.preventDefault();\n        if (focusedIndex !== null) {\n          const [lang] = languagesArray[focusedIndex];\n          handleLanguageChange(lang as Language);\n          closeListbox();\n        }\n        break;\n    }\n  };\n\n  const handleOptionClick = (language: Language) => {\n    handleLanguageChange(language);\n    closeListbox();\n  };\n\n  const handleOptionMouseMove = (index: number) => {\n    setFocusedIndex(index);\n  };\n\n  // Sync language preference with URL context whenever location changes\n  useEffect(() => {\n    // prevent URL sync from overriding user update\n    if (skipNextSync.current) {\n      skipNextSync.current = false;\n      return;\n    }\n\n    const currentUrlLanguage = getLanguageFromPathStrict(location.pathname, baseUrl);\n\n    if (\n      currentUrlLanguage &&\n      currentUrlLanguage !== language &&\n      !document.title.includes('Page Not Found')\n    ) {\n      const manifestPath = getManifestPathFromUrl(location.pathname, baseUrl);\n\n      const syncFunction = async () => {\n        try {\n          const pageExists = await isPageAvailableForLanguage(manifestPath, currentUrlLanguage);\n          if (pageExists && language !== currentUrlLanguage) {\n            setLanguage(currentUrlLanguage);\n          }\n        } catch {\n          if (language !== currentUrlLanguage) {\n            setLanguage(currentUrlLanguage);\n          }\n        }\n      };\n\n      syncFunction();\n    }\n  }, [location.pathname, baseUrl]);\n\n  useEffect(() => {\n    const handleClickOutside = (event: MouseEvent) => {\n      if (\n        isOpen &&\n        !buttonRef.current?.contains(event.target as Node) &&\n        !listRef.current?.contains(event.target as Node)\n      ) {\n        setIsOpen(false);\n      }\n    };\n    document.addEventListener('mousedown', handleClickOutside);\n    return () => document.removeEventListener('mousedown', handleClickOutside);\n  }, [isOpen]);\n\n  return (\n    <div className={`language-dropdown ${className}`.trim()} {...otherProps}>\n      <button\n        type=\"button\"\n        ref={buttonRef}\n        id=\"language-switch-dropdown-button\"\n        // Docusaurus navbar styling\n        className=\"navbar__link\"\n        onBlur={handleBlur}\n        onClick={handleButtonClick}\n        onKeyDown={handleButtonKeyDown}\n        aria-controls=\"language-switch-list\"\n        aria-expanded={isOpen}\n        aria-haspopup=\"listbox\"\n        aria-label={`Current language: ${LANGUAGE_NAMES[language]}. Open to change language.`}\n      >\n        {LANGUAGE_NAMES[language]}\n        {/* Visual dropdown indicator arrow - aria-hidden ok */}\n        <span aria-hidden=\"true\" className=\"language-dropdown-arrow\"></span>\n      </button>\n      {isOpen && (\n        <ul\n          id=\"language-switch-list\"\n          ref={listRef}\n          role=\"listbox\"\n          tabIndex={-1}\n          aria-label=\"Language\"\n          aria-activedescendant={\n            focusedIndex !== null ? `selection-${languagesArray[focusedIndex][0]}` : undefined\n          }\n          onKeyDown={handleListKeyDown}\n        >\n          {Object.entries(LANGUAGE_NAMES).map(([lang, label], index) => (\n            <li\n              aria-selected={lang === language}\n              key={lang}\n              id={`selection-${lang}`}\n              role=\"option\"\n              data-active={focusedIndex === index}\n              className=\"language-dropdown-option\"\n              onClick={() => handleOptionClick(lang as Language)}\n              onMouseMove={() => handleOptionMouseMove(index)}\n            >\n              {label}\n            </li>\n          ))}\n        </ul>\n      )}\n      {bannerRender && (\n        <LanguageBanner\n          targetLanguage={bannerRender.language}\n          baseUrl={baseUrl}\n          onDismiss={handleBannerDismiss}\n        />\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/api/csharp.incl.md",
    "content": "<!-- api-object-name -->\n\n`app.Api`\n\n<!-- api-table -->\n\n| Area            | Description                                                                                                                                                          |\n| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `Conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user) |\n| `Meetings`      | Gives your application access to meeting details and participant information via `GetByIdAsync` and `GetParticipantAsync`                                             |\n| `Teams`         | Gives your application access to team or channel details                                                                                                             |\n\n<!-- api-object-description -->\n\n`Api`\n\n<!-- handler-example -->\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    var members = await context.Api.Conversations.Members.Get(context.Conversation.Id);\n});\n```\n\n\n<!-- meetings-example -->\n\n```csharp\napp.OnMeetingStart(async (context, cancellationToken) =>\n{\n    var meetingId = context.Activity.Value.Id;\n    var tenantId = context.Activity.ChannelData?.Tenant?.Id;\n    var userId = context.Activity.From?.AadObjectId;\n\n    if (meetingId != null && tenantId != null && userId != null)\n    {\n        var participant = await context.Api.Meetings.GetParticipantAsync(meetingId, userId, tenantId);\n        // participant.Meeting?.Role — \"Organizer\", \"Presenter\", \"Attendee\"\n        // participant.Meeting?.InMeeting — true/false\n    }\n});\n```\n\n<!-- proactive-example -->\n\n```csharp\nvar members = await app.Api.Conversations.Members.Get(\"...\");\n```\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/api/python.incl.md",
    "content": "<!-- api-object-name -->\n\n`app.api`\n\n<!-- api-table -->\n\n| Area            | Description                                                                                                                                                          |\n| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user) |\n| `meetings`      | Gives your application access to meeting details and participant information via `get_by_id` and `get_participant`                                                    |\n| `teams`         | Gives your application access to team or channel details                                                                                                             |\n\n<!-- api-object-description -->\n\n`api`\n\n<!-- handler-example -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    members = await ctx.api.conversations.members.get(ctx.activity.conversation.id)\n```\n\n<!-- meetings-example -->\n\n```python\n@app.on_activity(\"meetingStart\")\nasync def handle_meeting_start(ctx: ActivityContext):\n    meeting_id = ctx.activity.channel_data.meeting.id\n    tenant_id = ctx.activity.channel_data.tenant.id\n    user_id = ctx.activity.from_.aad_object_id\n\n    if meeting_id and tenant_id and user_id:\n        participant = await ctx.api.meetings.get_participant(meeting_id, user_id, tenant_id)\n        # participant.meeting.role — \"Organizer\", \"Presenter\", \"Attendee\"\n        # participant.meeting.in_meeting — True/False\n```\n\n<!-- proactive-example -->\n\n```python\nmembers = await app.api.conversations.members.get(\"...\")\n```\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/api/typescript.incl.md",
    "content": "<!-- api-object-name -->\n\n`app.api`\n\n<!-- api-table -->\n\n| Area            | Description                                                                                                                                                          |\n| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| `conversations` | Gives your application the ability to perform activities on conversations (send, update, delete messages, etc.), or create conversations (like 1:1 chat with a user) |\n| `meetings`      | Gives your application access to meeting details and participant information via `getById` and `getParticipant`                                                       |\n| `teams`         | Gives your application access to team or channel details                                                                                                             |\n\n<!-- api-object-description -->\n\n`api`\n\n<!-- handler-example -->\n\n```typescript\napp.on('message', async ({ activity, api }) => {\n  const members = await api.conversations.members(activity.conversation.id).get();\n});\n```\n\n<!-- meetings-example -->\n\n```typescript\napp.on('meetingStart', async ({ activity, api }) => {\n  const meetingId = activity.channelData?.meeting?.id;\n  const tenantId = activity.channelData?.tenant?.id;\n  const userId = activity.from?.aadObjectId;\n\n  if (meetingId && tenantId && userId) {\n    const participant = await api.meetings.getParticipant(meetingId, userId, tenantId);\n    // participant.meeting?.role — \"Organizer\", \"Presenter\", \"Attendee\"\n    // participant.meeting?.inMeeting — true/false\n  }\n});\n```\n\n<!-- proactive-example -->\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst res = await app.api.graph.call(endpoints.chats.getAllMessages.get);\n```\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/app-authentication/csharp.incl.md",
    "content": "<!-- configure-application -->\n\n:::note\nThe environment file approach is not yet supported for C#. You need to configure authentication programmatically in your code.\n:::\n\nIn your `Program.cs`, replace the initialization:\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.AddTeams();\n```\nwith the following code to enable User Assigned Managed Identity authentication:\n```csharp\nvar builder = WebApplication.CreateBuilder(args);\n\nFunc<string[], string?, Task<ITokenResponse>> createTokenFactory = async (string[] scopes, string? tenantId) =>\n{\n    var clientId = Environment.GetEnvironmentVariable(\"CLIENT_ID\");\n    var managedIdentityCredential = new ManagedIdentityCredential(clientId);\n    var tokenRequestContext = new TokenRequestContext(scopes, tenantId: tenantId);\n    var accessToken = await managedIdentityCredential.GetTokenAsync(tokenRequestContext);\n\n    return new TokenResponse\n    {\n        TokenType = \"Bearer\",\n        AccessToken = accessToken.Token,\n    };\n};\n\nvar appBuilder = App.Builder()\n    .AddCredentials(new TokenCredentials(\n        Environment.GetEnvironmentVariable(\"CLIENT_ID\") ?? string.Empty,\n        async (tenantId, scopes) =>\n        {\n            return await createTokenFactory(scopes, tenantId);\n        }\n    ));\n\nbuilder.AddTeams(appBuilder);\n```\n\nThe `createTokenFactory` function provides a method to retrieve access tokens from Azure on demand, and `TokenCredentials` passes this method to the app.\n\n## Configuration\n\nSet the following environment variable:\n\n- `CLIENT_ID`: Your Application (client) ID\n\n<!-- availability-note -->\n\n:::note\nSupport for C# is coming soon.\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/app-authentication/python.incl.md",
    "content": "<!-- configure-application -->\n\nYour application should automatically use User Managed Identity authentication when you provide the `CLIENT_ID` environment variable without a `CLIENT_SECRET`.\n\n## Configuration\n\nSet the following environment variables in your application:\n\n- `CLIENT_ID`: Your Application (client) ID\n- **Do not set** `CLIENT_SECRET`\n- `TENANT_ID`: The tenant id where your bot is registered\n\n```env\nCLIENT_ID=your-client-id-here\n# Do not set CLIENT_SECRET\nTENANT_ID=your-tenant-id\n```\n\n<!-- availability-note -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/app-authentication/typescript.incl.md",
    "content": "<!-- configure-application -->\n\nYour application should automatically use User Managed Identity authentication when you provide the `CLIENT_ID` environment variable without a `CLIENT_SECRET`.\n\n## Configuration\n\nSet the following environment variables in your application:\n\n- `CLIENT_ID`: Your Application (client) ID\n- **Do not set** `CLIENT_SECRET`\n- `TENANT_ID`: The tenant id where your bot is registered\n\n```env\nCLIENT_ID=your-client-id-here\n# Do not set CLIENT_SECRET\nTENANT_ID=your-tenant-id\n```\n\n<!-- availability-note -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/csharp.incl.md",
    "content": "<!-- key-terms -->\n\n- Event: Anything interesting that happens on Teams — or within your application as a result of handling an earlier event.\n- Activity: A special type of Teams-specific event. Activities include things like messages, reactions, and adaptive card actions.\n- InvokeActivity: A specific kind of activity triggered by user interaction (like submitting a form), which may or may not require a response.\n- Handler: The logic in your application that reacts to events or activities. Handlers decide what to do, when, and how to respond.\n\n<!-- event-handler-label -->\n\nEvent Handler (app.OnEvent())\n\n<!-- activity-handler-label -->\n\nActivity Handlers (app.OnActivity())\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/graph/csharp.incl.md",
    "content": "<!-- package-info -->\n\n`Microsoft.Graph` package\n\n<!-- migration-note -->\n\nN/A\n\n<!-- package-overview -->\n\nN/A\n\n<!-- app-graph-object -->\n\n`app.Graph`\n\n<!-- app-access-method -->\n\nN/A\n\n<!-- app-graph-example -->\n\n```csharp\n// Equivalent of https://learn.microsoft.com/en-us/graph/api/user-get\n// Gets the details of the bot-user\nvar user = app.Graph.Me.GetAsync().GetAwaiter().GetResult();\nConsole.WriteLine($\"User ID: {user.id}\");\nConsole.WriteLine($\"User Display Name: {user.displayName}\");\nConsole.WriteLine($\"User Email: {user.mail}\");\nConsole.WriteLine($\"User Job Title: {user.jobTitle}\");\n```\n\n<!-- user-graph-intro -->\n\nTo access the graph using the user's token, you need to do this as part of a message handler:\n\n<!-- user-graph-example -->\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    var user = await context.UserGraph.Me.GetAsync();\n    Console.WriteLine($\"User ID: {user.id}\");\n    Console.WriteLine($\"User Display Name: {user.displayName}\");\n    Console.WriteLine($\"User Email: {user.mail}\");\n    Console.WriteLine($\"User Job Title: {user.jobTitle}\");\n});\n```\n\n\n<!-- user-graph-object -->\n\n`userGraph`\n\n<!-- app-graph-in-handler -->\n\n`appGraph`\n\n<!-- app-graph-reference -->\n\n`app.Graph`\n\n<!-- advanced-sections -->\n\nN/A\n\n<!-- additional-resources -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/graph/python.incl.md",
    "content": "<!-- package-info -->\n\n`microsoft-teams-graph` package\n\n<!-- migration-note -->\n\nN/A\n\n<!-- package-overview -->\n\nN/A\n\n<!-- app-graph-object -->\n\n`app.graph`\n\n<!-- app-access-method -->\n\nto call the endpoint of your choice\n\n<!-- app-graph-example -->\n\n```python\n# Equivalent of https://learn.microsoft.com/en-us/graph/api/user-get\n# Gets the details of the bot-user\nuser = await app.graph.me.get()\nprint(f\"User ID: {user.id}\")\nprint(f\"User Display Name: {user.display_name}\")\nprint(f\"User Email: {user.mail}\")\nprint(f\"User Job Title: {user.job_title}\")\n```\n\n<!-- user-graph-intro -->\n\nYou can also access the graph using the user's token from within a message handler via the `user_graph` property.\n\n<!-- user-graph-example -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    user = await ctx.user_graph.me.get()\n    print(f\"User ID: {user.id}\")\n    print(f\"User Display Name: {user.display_name}\")\n    print(f\"User Email: {user.mail}\")\n    print(f\"User Job Title: {user.job_title}\")\n```\n\n<!-- user-graph-object -->\n\n`user_graph`\n\n<!-- app-graph-in-handler -->\n\n`app_graph`\n\n<!-- app-graph-reference -->\n\n`app.graph`\n\n<!-- advanced-sections -->\n\nN/A\n\n<!-- additional-resources -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/graph/typescript.incl.md",
    "content": "<!-- package-info -->\n\n`@microsoft/teams.graph`, `@microsoft/teams.graph-endpoints` and `@microsoft/teams.graph-endpoints-beta` packages\n\n<!-- migration-note -->\n\n:::note\nIf you're migrating from an earlier preview version of the Teams SDK, please see the [migration guide](../migrations/v2-previews) for details on breaking changes.\n:::\n\n<!-- package-overview -->\n\n## Package overview\n\nThe Graph API surface is vast, and this is reflected in the size of the endpoints packages. To help you manage the size of your product, we made sure that the endpoints code is tree-shakable. We also made most of the code into an optional dependency, in case tree-shaking is not supported in your environment.\n\n| Package                                 | Optional | Contains                                                                            |\n| --------------------------------------- | -------- | ----------------------------------------------------------------------------------- |\n| `@microsoft/teams.graph`                | No       | A tiny client to create and issue Graph HTTP requests.                              |\n| `@microsoft/teams.graph-endpoints`      | Yes      | Request-builder functions and types to call any of the production ready Graph APIs. |\n| `@microsoft/teams.graph-endpoints-beta` | Yes      | Same, but for Graph APIs still in preview.                                          |\n\nTo use this SDK to call Graph APIs, the first step is to install the optional endpoints package using your favorite package manager. For instance:\n\n```sh\nnpm install @microsoft/teams.graph-endpoints\n```\n\n<!-- app-graph-object -->\n\n`app.graph`\n\n<!-- app-access-method -->\n\nto call the endpoint of your choice\n\n<!-- app-graph-example -->\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\n// Equivalent of https://learn.microsoft.com/en-us/graph/api/user-get\n// Gets the details of the bot-user\napp.graph.call(endpoints.me.get).then((user) => {\n  console.log(`User ID: ${user.id}`);\n  console.log(`User Display Name: ${user.displayName}`);\n  console.log(`User Email: ${user.mail}`);\n  console.log(`User Job Title: ${user.jobTitle}`);\n});\n```\n\n<!-- user-graph-intro -->\n\nYou can also access the graph using the user's token from within a message handler via the `userGraph` prop.\n\n<!-- user-graph-example -->\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\n// Gets details of the current user\napp.on('message', async ({ activity, userGraph }) => {\n  const me = await userGraph.call(endpoints.me.get);\n  console.log(`User ID: ${me.id}`);\n  console.log(`User Display Name: ${me.displayName}`);\n  console.log(`User Email: ${me.mail}`);\n  console.log(`User Job Title: ${me.jobTitle}`);\n});\n```\n\n<!-- user-graph-object -->\n\n`userGraph`\n\n<!-- app-graph-in-handler -->\n\n`appGraph`\n\n<!-- app-graph-reference -->\n\n`app.graph`\n\n<!-- advanced-sections -->\n\n## The Graph Client\n\nThe Graph Client provides a straight-forward `call` method to interact with Microsoft Graph and issue requests scoped to a specific user or application. Paired with the Graph Endpoints packages, it offers discoverable and type-safe access to the vast Microsoft Graph API surface.\n\nHaving an understanding of [how the graph API works](https://learn.microsoft.com/en-us/graph/use-the-api) will help you make the most of the SDK. For example, to get the `id` of the chat instance between a user and an app, [Microsoft Graph](https://learn.microsoft.com/en-us/graph/api/userscopeteamsappinstallation-get-chat?view=graph-rest-1.0&tabs=http) exposes it via:\n\n```\nGET /users/{user-id | user-principal-name}/teamwork/installedApps/{app-installation-id}/chat\n```\n\nThe equivalent using the graph client would look like this:\n\n```ts\nimport { users } from '@microsoft/teams.graph-endpoints';\n\nconst chat = await userGraph.call(users.teamwork.installedApps.chat.get, {\n  'user-id': user.id,\n  'userScopeTeamsAppInstallation-id': appInstallationId,\n  $select: ['id'],\n});\n```\n\nGraph APIs often accept arguments that may go into the URL path, the query string, or the request body. As illustrated in this example, all arguments are provided as a second parameter to the `graph.call` method. The graph client puts each value in its place and attaches an authentication token as the request is constructed, and performs the fetch request for you.\n\n## Graph Preview APIs\n\nThe Graph Preview APIs are not recommended for production use. However, if you have a need to explore preview APIs, the `@microsoft/teams.graph-endpoints-beta` package makes it easy.\n\nFirst, install the optional dependency:\n\n```sh\nnpm install @microsoft/teams.graph-endpoints-beta\n```\n\nThen use it just like the regular `@microsoft/teams.graph-endpoints` package.\n\n```ts\nimport * as endpointsBeta from '@microsoft/teams.graph-endpoints-beta';\n\n// Gets the current user details from /beta/me, rather than from /v1.0/me.\nconst me = await app.graph.call(endpointsBeta.me.get);\n```\n\nThe key differences between `@microsoft/teams.graph-endpoints` and `@microsoft/teams.graph-endpoints-beta` are that they represent different Graph API schemas, and that the `graph.call()` method knows to route the request to either /v1.0 or /beta. This means that it's possible to mix'n'match v1.0 and beta endpoints, for instance to explore a novel beta API in a code base that's already relying on v1.0 for all stable APIs.\n\n## Custom Graph API calls\n\nIt's possible to craft custom builder functions that work just like the ones provided in the `@microsoft/teams.graph-endpoints` and `@microsoft/teams.graph-endpoints-beta` packages. This can be handy if you wish to provide narrower return types, call some novel API that is supported by the Graph backend but not yet included in the endpoints packages, or avoid taking a dependency on the endpoints packages altogether.\n\nFor instance, this will `GET https://graph.microsoft.com/beta/me?$select=displayName` and return an object typed to contain just `displayName`, without taking a dependency on the endpoints packages.\n\n```ts\nimport { type EndpointRequest } from '@microsoft/teams.graph';\n\nconst getMyDisplayName = (): EndpointRequest<{ displayName: string }> => ({\n  ver: 'beta', // use the beta endpoint; defaults to 'v1.0' if omitted\n  method: 'get', // HTTP method to use\n  path: '/me', // endpoint path\n  paramDefs: {\n    query: ['$select'], // the $select parameter goes in the query string\n  },\n  params: {\n    $select: ['displayName'], // the attribute(s) to select\n  },\n});\n\nconst { displayName } = await app.graph.call(getMyDisplayName);\n```\n\n<!-- additional-resources -->\n\n## Additional resources\n\nMicrosoft Graph offers an extensive and thoroughly documented API surface. These essential resources will serve as your go-to references for any Graph development work:\n\n- The [Microsoft Graph Rest API reference documentation](https://learn.microsoft.com/en-us/graph/api/overview) gives details for each API, including permissions requirements.\n- The [Microsoft Graph REST API beta endpoint reference](https://learn.microsoft.com/en-us/graph/api/overview?view=graph-rest-beta) gives similar information for preview APIs.\n- The [Graph Explorer](https://developer.microsoft.com/en-us/graph/graph-explorer) lets you discover and test drive APIs.\n\nIn addition, the following endpoints may be especially interesting to Teams developers:\n\n| Graph endpoints                                                                                                                | Description                                                         |\n| ------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------- |\n| [appCatalogs](https://learn.microsoft.com/en-us/graph/api/appcatalogs-list-teamsapps?view=graph-rest-1.0)                      | Apps in the Teams App Catalog                                       |\n| [appRoleAssignments](https://learn.microsoft.com/en-us/graph/api/serviceprincipal-list-approleassignments?view=graph-rest-1.0) | App role assignments                                                |\n| [applicationTemplates](https://learn.microsoft.com/en-us/graph/api/resources/applicationtemplate?view=graph-rest-1.0)          | Applications in the Microsoft Entra App Gallery                     |\n| [applications](https://learn.microsoft.com/en-us/graph/api/resources/application?view=graph-rest-1.0)                          | Application resources                                               |\n| [chats](https://learn.microsoft.com/en-us/graph/api/chat-list?view=graph-rest-1.0&tabs=http)                                   | Chat resources between users                                        |\n| [communications](https://learn.microsoft.com/en-us/graph/api/application-post-calls?view=graph-rest-1.0)                       | Calls and Online meetings                                           |\n| [employeeExperience](https://learn.microsoft.com/en-us/graph/api/resources/engagement-api-overview?view=graph-rest-1.0)        | Employee Experience and Engagement                                  |\n| [me](https://learn.microsoft.com/en-us/graph/api/user-get?view=graph-rest-1.0&tabs=http)                                       | Same as `/users` but scoped to one user (who is making the request) |\n| [teams](https://learn.microsoft.com/en-us/graph/api/resources/team?view=graph-rest-1.0)                                        | Team resources in Microsoft Teams                                   |\n| [teamsTemplates](https://learn.microsoft.com/en-us/microsoftteams/get-started-with-teams-templates)                            | Templates used to create teams                                      |\n| [teamwork](https://learn.microsoft.com/en-us/graph/api/resources/teamwork?view=graph-rest-1.0)                                 | A range of Microsoft Teams functionalities                          |\n| [users](https://learn.microsoft.com/en-us/graph/api/resources/users?view=graph-rest-1.0)                                       | User resources                                                      |\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/on-activity/csharp.incl.md",
    "content": "<!-- intro -->\n\nThe Teams SDK exposes a fluent router so you can subscribe to these activities with `app.OnActivity(...)` using minimal APIs.\n\n<!-- basic-example -->\n\n    ```csharp\n    app.OnMessage(async (context, cancellationToken) =>\n    {\n        await context.Send($\"you said: {context.activity.Text}\", cancellationToken);\n    });\n    ```\n\n<!-- example-explanation -->\n\nIn the above example, the `context.activity` parameter is of type `MessageActivity`, which has a `Text` property. You'll notice that the handler here does not return anything, but instead handles it by `send`ing a message back. For message activities, Teams does not expect your application to return anything (though it's usually a good idea to send some sort of friendly acknowledgment!).\n\n<!-- middleware-intro -->\n\nThe `OnActivity` activity handlers (and attributes) follow a [middleware](https://www.patterns.dev/vanilla/mediator-pattern/) pattern similar to how `dotnet` middlewares work. This means that for each activity handler, a `Next` function is passed in which can be called to pass control to the next handler. This allows you to build a chain of handlers that can process the same activity in different ways.\n\n<!-- middleware-examples -->\n  ```csharp\n  app.OnMessage(async (context, cancellationToken) =>\n  {\n      Console.WriteLine(\"global logger\");\n      context.Next(); // pass control onward\n      return Task.CompletedTask;\n  });\n  ```\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    if (context.Activity.Text == \"/help\")\n    {\n        await context.Send(\"Here are all the ways I can help you...\", cancellationToken);\n    }\n\n    // Conditionally pass control to the next handler\n    context.Next();\n});\n\n  app.OnMessage(async (context, cancellationToken) =>\n  {\n      // Fallthrough to the final handler\n      await context.Send($\"Hello! you said {context.Activity.Text}\", cancellationToken);\n  });\n  ```\n\n\n<!-- activity-reference-footer -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/on-activity/python.incl.md",
    "content": "<!-- intro -->\n\nThe Teams SDK exposes a fluent router so you can subscribe to these activities with `@app.event(\"activity\")`.\n\n<!-- basic-example -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    await ctx.send(f\"You said '{ctx.activity.text}'\")\n```\n\n<!-- example-explanation -->\n\nIn the above example, the `ctx.activity` parameter is of type `MessageActivity`, which has a `text` property. You'll notice that the handler here does not return anything, but instead handles it by `send`ing a message back. For message activities, Teams does not expect your application to return anything (though it's usually a good idea to send some sort of friendly acknowledgment!).\n\n<!-- middleware-intro -->\n\nThe `event` activity handlers (and attributes) follow a [middleware](https://www.patterns.dev/vanilla/mediator-pattern/) pattern similar to how `python` middlewares work. This means that for each activity handler, a `next` function is passed in which can be called to pass control to the next handler. This allows you to build a chain of handlers that can process the same activity in different ways.\n\n<!-- middleware-examples -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle message activities using the new generated handler system.\"\"\"\n    print(f\"[GENERATED onMessage] Message received: {ctx.activity.text}\")\n    await ctx.next()\n```\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle message activities using the new generated handler system.\"\"\"\n    if ctx.activity.text == \"/help\":\n        await ctx.send(\"Here are all the ways I can help you...\")\n    await ctx.next()\n```\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    await ctx.send(f\"You said '{ctx.activity.text}'\")\n```\n\n<!-- activity-reference-footer -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/on-activity/typescript.incl.md",
    "content": "<!-- intro -->\n\nThe Teams SDK exposes a fluent router so you can subscribe to these activities with `app.on('<route>', …)`.\n\n<!-- basic-example -->\n\n```typescript\napp.on('message', async ({ activity, send }) => {\n  await send(`You said: ${activity.text}`);\n});\n```\n\n<!-- example-explanation -->\n\nIn the above example, the `activity` parameter is of type `MessageActivity`, which has a `text` property. You'll notice that the handler here does not return anything, but instead handles it by `send`ing a message back. For message activities, Teams does not expect your application to return anything (though it's usually a good idea to send some sort of friendly acknowledgment!).\n\n[Other activity types](./activity-ref) have different properties and different required results. For a given handler, the SDK will automatically determine the type of `activity` and also enforce the correct return type.\n\n<!-- middleware-intro -->\n\nThe `on` activity handlers follow a [middleware](https://www.patterns.dev/vanilla/mediator-pattern/) pattern similar to how `express` middlewares work. This means that for each activity handler, a `next` function is passed in which can be called to pass control to the next handler. This allows you to build a chain of handlers that can process the same activity in different ways.\n\n<!-- middleware-examples -->\n\n```typescript\napp.on('message', async ({ next }) => {\n  console.log('global logger');\n  next(); // pass control onward\n});\n```\n\n```typescript\napp.on('message', async ({ activity, next }) => {\n  if (activity.text === '/help') {\n    await send('Here are all the ways I can help you...');\n    return;\n  }\n\n  // Conditionally pass control to the next handler\n  next();\n});\n```\n\n```typescript\napp.on('message', async ({ activity }) => {\n  // Fallthrough to the final handler\n  await send(`Hello! you said ${activity.text}`);\n});\n```\n\n<!-- activity-reference-footer -->\n\n## Activity Reference\n\nFor a list of supported activities that your application can listen to, see the [activity reference](./activity-ref).\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/on-event/csharp.incl.md",
    "content": "<!-- mermaid-diagram -->\n\n```mermaid\nflowchart LR\n    Teams[\"Teams\"]:::less-interesting\n    Server[\"App Server\"]:::interesting\n    AppEventHandlers[\"Event Handler (app.OnEvent())\"]:::interesting\n\n    Teams --> |Activity| Server\n    Teams --> |Signed In| Server\n    Teams --> |...other<br/>incoming events| Server\n    Server ---> |incoming<br/>events| AppEventHandlers\n    Server ---> |outgoing<br/>events<br/>| AppEventHandlers\n\n\n    linkStyle 0,1,2,3,4 stroke:#b1650f,stroke-width:1px\n    classDef interesting fill:#b1650f,stroke:#333,stroke-width:4px;\n```\n\n<!-- events-table -->\n\n| **Event Name**      | **Description**                                                                |\n| ------------------- | ------------------------------------------------------------------------------ |\n| `start`             | Triggered when your application starts. Useful for setup or boot-time logging. |\n| `signin`            | Triggered during a sign-in flow via Teams.                                     |\n| `error`             | Triggered when an unhandled error occurs in your app. Great for diagnostics.   |\n| `activity`          | A catch-all for incoming Teams activities (messages, commands, etc.).          |\n| `activity.response` | Triggered when your app sends a response to an activity. Useful for logging.   |\n| `activity.sent`     | Triggered when an activity is sent (not necessarily in response).              |\n\n<!-- example-1 -->\n\n```csharp\napp.OnError((sender, @event) =>\n{\n    // do something with the error\n    app.Logger.Info(@event.Exception.ToString());\n});\n```\n\n<!-- example-2 -->\n\nWhen an activity is received, log its `JSON` payload.\n\n```csharp\napp.OnActivity((sender, @event) =>\n{\n    app.Logger.Info(@event.Activity.ToString());\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/on-event/python.incl.md",
    "content": "<!-- mermaid-diagram -->\n\n```mermaid\nflowchart LR\n    Teams[\"Teams\"]:::less-interesting\n    Server[\"App Server\"]:::interesting\n    AppEventHandlers[\"Event Handler (app.event())\"]:::interesting\n\n    Teams --> |Activity| Server\n    Teams --> |Signed In| Server\n    Teams --> |...other<br/>incoming events| Server\n    Server ---> |incoming<br/>events| AppEventHandlers\n    Server ---> |outgoing<br/>events<br/>| AppEventHandlers\n\n\n    linkStyle 0,1,2,3,4 stroke:#b1650f,stroke-width:1px\n    classDef interesting fill:#b1650f,stroke:#333,stroke-width:4px;\n```\n\n<!-- events-table -->\n\n| **Event Name**      | **Description**                                                                |\n| ------------------- | ------------------------------------------------------------------------------ |\n| `start`             | Triggered when your application starts. Useful for setup or boot-time logging. |\n| `sign_in`           | Triggered during a sign-in flow via Teams.                                     |\n| `error`             | Triggered when an unhandled error occurs in your app. Great for diagnostics.   |\n| `activity`          | Triggered for all incoming Teams activities (messages, commands, etc.).        |\n| `activity_response` | Triggered when your app sends a response to an activity. Useful for logging.   |\n| `activity_sent`     | Triggered when an activity is sent (not necessarily in response).              |\n\n<br/>\n:::info\nEvent handler registration uses `@app.event(\"<event_name>\")` with an async function that receives an event object specific to the event type (e.g., `ErrorEvent`, `ActivityEvent`).\n:::\n\n<!-- example-1 -->\n\n```python\n@app.event(\"error\")\nasync def handle_error(event: ErrorEvent):\n    \"\"\"Handle error events.\"\"\"\n    print(f\"Error occurred: {event.error}\")\n    if hasattr(event, \"context\") and event.context:\n        print(f\"Context: {event.context}\")\n```\n\n<!-- example-2 -->\n\nWhen an activity is received, log its payload.\n\n```python\n@app.event(\"activity\")\nasync def handle_activity(event: ActivityEvent):\n    \"\"\"Handle activity events.\"\"\"\n    print(f\"Activity received: {event.activity}\")\n```\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/on-event/typescript.incl.md",
    "content": "<!-- mermaid-diagram -->\n\n```mermaid\nflowchart LR\n    Teams[\"Teams\"]:::less-interesting\n    Server[\"App Server\"]:::interesting\n    AppEventHandlers[\"Event Handler (app.event())\"]:::interesting\n\n    Teams --> |Activity| Server\n    Teams --> |Signed In| Server\n    Teams --> |...other<br/>incoming events| Server\n    Server ---> |incoming<br/>events| AppEventHandlers\n    Server ---> |outgoing<br/>events<br/>| AppEventHandlers\n\n\n    linkStyle 0,1,2,3,4 stroke:#b1650f,stroke-width:1px\n    classDef interesting fill:#b1650f,stroke:#333,stroke-width:4px;\n```\n\n<!-- events-table -->\n\n| **Event Name**      | **Description**                                                                |\n| ------------------- | ------------------------------------------------------------------------------ |\n| `start`             | Triggered when your application starts. Useful for setup or boot-time logging. |\n| `signin`            | Triggered during a sign-in flow via Teams.                                     |\n| `error`             | Triggered when an unhandled error occurs in your app. Great for diagnostics.   |\n| `activity`          | A catch-all for incoming Teams activities (messages, commands, etc.).          |\n| `activity.response` | Triggered when your app sends a response to an activity. Useful for logging.   |\n| `activity.sent`     | Triggered when an activity is sent (not necessarily in response).              |\n\n<!-- example-1 -->\n\n```typescript\napp.event('error', ({ error }) => {\n  app.log.error(error);\n  // Or Alternatively, send it to an observability platform\n});\n```\n\n<!-- example-2 -->\n\nWhen a user signs in using `OAuth` or `SSO`, use the graph api to fetch their profile and say hello.\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\napp.event('signin', async ({ activity, send, userGraph }) => {\n  const me = await userGraph.call(endpoints.me.get);\n  await send(`👋 Hello ${me.name}`);\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/python.incl.md",
    "content": "<!-- key-terms -->\n\n- Event: Anything interesting that happens on Teams — or within your application as a result of handling an earlier event.\n- Activity: A special type of Teams-specific event. Activities include things like messages, reactions, and adaptive card actions.\n- InvokeActivity: A specific kind of activity triggered by user interaction (like submitting a form), which may or may not require a response.\n- Handler: The logic in your application that reacts to events or activities. Handlers decide what to do, when, and how to respond.\n\n<!-- event-handler-label -->\n\nEvent Handler decorator (@app.event())\n\n<!-- activity-handler-label -->\n\nActivity Handler decorators (@app.on_activity())\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/sending-messages/csharp.incl.md",
    "content": "<!-- basic-message-example -->\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    await context.Send($\"you said: {context.activity.Text}\", cancellationToken);\n});\n```\n\n<!-- signin-example -->\n\n  ```csharp\n  app.OnVerifyState(async (context, cancellationToken) =>\n  {\n      await context.Send(\"You have successfully signed in!\", cancellationToken);\n  });\n  ```\n\n<!-- signin-event-name -->\n\n`SignIn.VerifyState`\n\n<!-- streaming-example -->\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    context.Stream.Emit(\"hello\");\n    context.Stream.Emit(\", \");\n    context.Stream.Emit(\"world!\");\n    // result message: \"hello, world!\"\n    return Task.CompletedTask;\n});\n```\n\n<!-- mention-method-name -->\n\n`AddMention`\n\n<!-- mention-example -->\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    await context.Send(new MessageActivity(\"hi!\").AddMention(activity.From), cancellationToken);\n});\n```\n\n<!-- targeted-method-name -->\n\n`WithRecipient`\n\n<!-- targeted-send-example -->\n\n```csharp\napp.OnMessage(async (context, cancellationToken) =>\n{\n    // Using WithRecipient with isTargeted=true explicitly targets the specified recipient\n    await context.Send(\n        new MessageActivity(\"This message is only visible to you!\")\n            .WithRecipient(context.Activity.From, isTargeted: true),\n        cancellationToken\n    );\n});\n```\n\n<!-- targeted-preview-note -->\n\n:::tip[.NET]\nIn .NET, targeted message APIs are marked with `[Experimental(\"ExperimentalTeamsTargeted\")]` and will produce a compiler error until you opt in. Suppress the diagnostic inline with `#pragma warning disable ExperimentalTeamsTargeted` or project-wide in your `.csproj`:\n\n```xml\n<PropertyGroup>\n  <NoWarn>$(NoWarn);ExperimentalTeamsTargeted</NoWarn>\n</PropertyGroup>\n```\n:::\n\n<!-- reactions-preview-note -->\n\n:::tip[.NET]\nIn .NET, reaction APIs are marked with `[Experimental(\"ExperimentalTeamsReactions\")]` and will produce a compiler error until you opt in. Suppress the diagnostic inline with `#pragma warning disable ExperimentalTeamsReactions` or project-wide in your `.csproj`:\n\n```xml\n<PropertyGroup>\n  <NoWarn>$(NoWarn);ExperimentalTeamsReactions</NoWarn>\n</PropertyGroup>\n```\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/essentials/sending-messages/proactive-messaging/csharp.incl.md",
    "content": "<!-- conversation-id-field -->\n\n`conversationId`\n\n<!-- install-handler-example -->\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n<Tabs>\n  <TabItem label=\"Minimal\" value=\"minimal\">\n    ```csharp \n    app.OnInstall(async (context, cancellationToken) =>\n    {\n        // Save the conversation id in\n        context.Storage.Set(activity.From.AadObjectId!, activity.Conversation.Id);\n        await context.Send(\"Hi! I am going to remind you to say something to me soon!\", cancellationToken);\n        notificationQueue.AddReminder(activity.From.AadObjectId!, Notifications.SendProactive, 10_000);\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- send-proactive-example -->\n\n```csharp\npublic static class Notifications\n{\n    public static async Task SendProactive(string userId)\n    {\n        var conversationId = (string?)storage.Get(userId);\n\n        if (conversationId is null) return;\n\n        await app.Send(conversationId, \"Hey! It's been a while. How are you?\");\n    }\n}\n```\n\n<!-- targeted-proactive-example -->\n\n```csharp\n// When sending proactively, you must provide an explicit recipient account\npublic static async Task SendTargetedNotification(string conversationId, Account recipient)\n{\n    var teams = app.UseTeams();\n    await teams.Send(\n        conversationId,\n        new MessageActivity(\"This is a private notification just for you!\")\n            .WithRecipient(recipient, isTargeted: true)\n    );\n}\n```"
  },
  {
    "path": "teams.md/src/components/include/essentials/sending-messages/proactive-messaging/python.incl.md",
    "content": "<!-- conversation-id-field -->\n\n`conversation_id`\n\n<!-- install-handler-example -->\n\n```python\nfrom microsoft_teams.api import InstalledActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n# This would be some persistent storage\nstorage = dict[str, str]()\n\n# Installation is just one place to get the conversation_id. All activities have this field as well.\n@app.on_install_add\nasync def handle_install_add(ctx: ActivityContext[InstalledActivity]):\n    # Save the conversation_id\n    storage[ctx.activity.from_.aad_object_id] = ctx.activity.conversation.id\n    await ctx.send(\"Hi! I am going to remind you to say something to me soon!\")\n    # This queues up the proactive notifaction to be sent in 1 minute\n    notication_queue.add_reminder(ctx.activity.from_.aad_object_id, send_proactive_notification, 60000)\n```\n\n<!-- send-proactive-example -->\n\n```python\nfrom microsoft_teams.api import MessageActivityInput\n# ...\n\nasync def send_proactive_notification(user_id: str):\n    conversation_id = storage.get(user_id, \"\")\n    if not conversation_id:\n        return\n    activity = MessageActivityInput(text=\"Hey! It's been a while. How are you?\")\n    await app.send(conversation_id, activity)\n```\n\n<!-- targeted-proactive-example -->\n\n```python\nfrom microsoft_teams.api import MessageActivityInput, Account\n\n# When sending proactively, you must provide an explicit recipient account\nasync def send_targeted_notification(conversation_id: str, recipient: Account):\n    await app.send(\n        conversation_id,\n        MessageActivityInput(text=\"This is a private notification just for you!\")\n            .with_recipient(recipient, is_targeted=True)\n    )\n```"
  },
  {
    "path": "teams.md/src/components/include/essentials/sending-messages/proactive-messaging/typescript.incl.md",
    "content": "<!-- conversation-id-field -->\n\n`conversationId`\n\n<!-- install-handler-example -->\n\n```typescript\nimport { MessageActivity } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\n// This would be some persistent storage\nconst myConversationIdStorage = new Map<string, string>();\n\n// Installation is just one place to get the conversation id. All activities\n// have the conversation id, so you can use any activity to get it.\napp.on('install.add', async ({ activity, send }) => {\n  // Save the conversation id in\n  myConversationIdStorage.set(activity.from.aadObjectId!, activity.conversation.id);\n\n  await send('Hi! I am going to remind you to say something to me soon!');\n  notificationQueue.addReminder(activity.from.aadObjectId!, sendProactiveNotification, 10_000);\n});\n```\n\n<!-- send-proactive-example -->\n\n```typescript\nimport { MessageActivity } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\nconst sendProactiveNotification = async (userId: string) => {\n  const conversationId = myConversationIdStorage.get(userId);\n  if (!conversationId) {\n    return;\n  }\n  const activity = new MessageActivity('Hey! It\\'s been a while. How are you?');\n  await app.send(conversationId, activity);\n};\n```\n\n<!-- targeted-proactive-example -->\n\n```typescript\nimport { MessageActivity, Account } from '@microsoft/teams.api';\n\n// When sending proactively, you must provide an explicit recipient account\nconst sendTargetedNotification = async (conversationId: string, recipient: Account) => {\n  await app.send(\n    conversationId,\n    new MessageActivity('This is a private notification just for you!')\n      .withRecipient(recipient, true)\n  );\n};\n```"
  },
  {
    "path": "teams.md/src/components/include/essentials/sending-messages/python.incl.md",
    "content": "<!-- basic-message-example -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    await ctx.send(f\"You said '{ctx.activity.text}'\")\n```\n\n<!-- signin-example -->\n\n```python\n@app.event(\"sign_in\")\nasync def handle_sign_in(event: SignInEvent):\n    \"\"\"Handle sign-in events.\"\"\"\n    await event.activity_ctx.send(\"You are now signed in!\")\n```\n\n<!-- signin-event-name -->\n\n`sign_in`\n\n<!-- streaming-example -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    ctx.stream.update(\"Stream starting...\")\n    await asyncio.sleep(1)\n\n    # Stream messages with delays using ctx.stream.emit\n    for message in STREAM_MESSAGES:\n        # Add some randomness to timing\n        await asyncio.sleep(random())\n\n        ctx.stream.emit(message)\n```\n\n<!-- mention-method-name -->\n\n`add_mention`\n\n<!-- mention-example -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n  await ctx.send(MessageActivityInput(text='hi!').add_mention(account=ctx.activity.from_))\n```\n\n<!-- targeted-method-name -->\n\n`with_recipient`\n\n<!-- targeted-send-example -->\n\n```python\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    # Using with_recipient with is_targeted=True explicitly targets the specified recipient\n    await ctx.send(\n        MessageActivityInput(text=\"This message is only visible to you!\")\n            .with_recipient(ctx.activity.from_, is_targeted=True)\n    )\n```\n\n<!-- targeted-preview-note -->\nN/A\n\n<!-- reactions-preview-note -->\nN/A"
  },
  {
    "path": "teams.md/src/components/include/essentials/sending-messages/typescript.incl.md",
    "content": "<!-- basic-message-example -->\n\n```typescript\napp.on('message', async ({ activity, send }) => {\n  await send(`You said: ${activity.text}`);\n});\n```\n\n<!-- signin-example -->\n\n```typescript\napp.on('signin.verify-state', async ({ send }) => {\n  await send('You have successfully signed in!');\n});\n```\n\n<!-- signin-event-name -->\n\n`signin.verify-state`\n\n<!-- streaming-example -->\n\n```typescript\napp.on('message', async ({ activity, stream }) => {\n  stream.emit('hello');\n  stream.emit(', ');\n  stream.emit('world!');\n\n  // result message: \"hello, world!\"\n});\n```\n\n<!-- mention-method-name -->\n\n`addMention`\n\n<!-- mention-example -->\n\n```typescript\napp.on('message', async ({ send, activity }) => {\n  await send(new MessageActivity('hi!').addMention(activity.from));\n});\n```\n\n<!-- targeted-method-name -->\n\n`withRecipient`\n\n<!-- targeted-send-example -->\n\n```typescript\nimport { MessageActivity } from '@microsoft/teams.api';\n\napp.on('message', async ({ send, activity }) => {\n  // Using withRecipient with isTargeted=true explicitly targets the specified recipient\n  await send(\n    new MessageActivity('This message is only visible to you!')\n      .withRecipient(activity.from, true)\n  );\n});\n```\n\n<!-- targeted-preview-note -->\nN/A\n\n<!-- reactions-preview-note -->\nN/A"
  },
  {
    "path": "teams.md/src/components/include/essentials/typescript.incl.md",
    "content": "<!-- key-terms -->\n\n- Event: Anything interesting that happens on Teams — or within your application as a result of handling an earlier event.\n- Activity: A special type of Teams-specific event. Activities include things like messages, reactions, and adaptive card actions.\n- InvokeActivity: A specific kind of activity triggered by user interaction (like submitting a form), which may or may not require a response.\n- Handler: The logic in your application that reacts to events or activities. Handlers decide what to do, when, and how to respond.\n\n<!-- event-handler-label -->\n\nEvent Handler (app.event())\n\n<!-- activity-handler-label -->\n\nActivity Handlers (app.on())\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/_LLMs/csharp.incl.md",
    "content": "<!-- language-name -->\n\nC#\n\n<!-- file-links -->\n\n**Small**: [llms_csharp.txt](https://microsoft.github.io/teams-sdk/llms_docs/llms_csharp.txt) - This file contains an index of the various pages in the C# documentation. The agent needs to selectively read the relevant pages to answer questions and help with development.\n\n**Large**: [llms_csharp_full.txt](https://microsoft.github.io/teams-sdk/llms_docs/llms_csharp_full.txt) - This file contains the full content of the C# documentation, including all pages and code snippets. The agent can keep the entire documentation in memory to answer questions and help with development.\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/_LLMs/python.incl.md",
    "content": "<!-- language-name -->\n\nPython\n\n<!-- file-links -->\n\n**Small**: [llms_python.txt](https://microsoft.github.io/teams-sdk/llms_docs/llms_python.txt) - This file contains an index of the various pages in the Python documentation. The agent needs to selectively read the relevant pages to answer questions and help with development.\n\n**Large**: [llms_python_full.txt](https://microsoft.github.io/teams-sdk/llms_docs/llms_python_full.txt) - This file contains the full content of the Python documentation, including all pages and code snippets. The agent can keep the entire documentation in memory to answer questions and help with development.\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/_LLMs/typescript.incl.md",
    "content": "<!-- language-name -->\n\nTypeScript\n\n<!-- file-links -->\n\n**Small**: [llms_typescript.txt](https://microsoft.github.io/teams-sdk/llms_docs/llms_typescript.txt) - This file contains an index of the various pages in the TypeScript documentation. The agent needs to selectively read the relevant pages to answer questions and help with development.\n\n**Large**: [llms_typescript_full.txt](https://microsoft.github.io/teams-sdk/llms_docs/llms_typescript_full.txt) - This file contains the full content of the TypeScript documentation, including all pages and code snippets. The agent can keep the entire documentation in memory to answer questions and help with development.\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/code-basics/csharp.incl.md",
    "content": "<!-- imports -->\n\nN/A\n\n<!-- project-structure -->\n\n```\nQuote.Agent/\n|── appPackage/       # Teams app package files\n├── Program.cs        # Main application startup code\n```\n\n<!-- project-structure-description -->\n\n- **appPackage/**: Contains the Teams app package files, including the `manifest.json` file and icons. This is required for [sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) the app into Teams for testing. The app manifest defines the app's metadata, capabilities, and permissions.\n\n<!-- app-class-code -->\n\n```csharp title=\"Program.cs\"\nusing Microsoft.Teams.Apps.Activities;\nusing Microsoft.Teams.Apps.Extensions;\nusing Microsoft.Teams.Plugins.AspNetCore.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\nbuilder.AddTeams().AddTeamsDevTools();\nvar app = builder.Build();\nvar teams = app.UseTeams();\n\nteams.OnMessage(async (context, cancellationToken) =>\n{\n    await context.Typing(cancellationToken);\n    await context.Send($\"you said '{context.Activity.Text}'\", cancellationToken);\n});\n\napp.Run();\n```\n\n<!-- plugin-events -->\n\n(onActivity, onActivitySent, etc.)\n\n<!-- message-handling-code -->\n\n```csharp title=\"Program.cs\"\nteams.OnMessage(async (context, cancellationToken) =>\n{\n    await context.Typing(cancellationToken);\n    await context.Send($\"you said \\\"{context.activity.Text}\\\"\", cancellationToken);\n});\n```\n\n\n<!-- message-handling-step1 -->\n\nListens for all incoming messages using `onMessage` handler.\n\n<!-- message-handling-step3 -->\n\nResponds by echoing back the received message.\n\n<!-- message-handling-info -->\n\n:::info\nEach activity type has both an attribute and a functional method for type safety/simplicity\nof routing logic!\n:::\n\n<!-- app-lifecycle-code -->\n\n```csharp\nvar app = builder.Build();\napp.UseTeams();\napp.Run();\n```\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/code-basics/python.incl.md",
    "content": "<!-- imports -->\n\nN/A\n\n<!-- project-structure -->\n\n```\nquote-agent/\n|── appPackage/       # Teams app package files\n├── src\n    ├── main.py       # Main application code\n```\n\n<!-- project-structure-description -->\n\n- **appPackage/**: Contains the Teams app package files, including the `manifest.json` file and icons. This is required for [sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) the app into Teams for testing. The app manifest defines the app's metadata, capabilities, and permissions.\n- **src/**: Contains the main application code. The `main.py` file is the entry point for your application.\n\n<!-- app-class-code -->\n\n```python title=\"src/main.py\"\nfrom microsoft_teams.api import MessageActivity, TypingActivityInput\nfrom microsoft_teams.apps import ActivityContext, App, AppOptions\nfrom microsoft_teams.devtools import DevToolsPlugin\n\napp = App(plugins=[DevToolsPlugin()])\n\n```\n\n<!-- plugin-events -->\n\n(on_activity, on_activity_sent, etc.)\n\n<!-- message-handling-code -->\n\n```python title=\"src/main.py\"\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    await ctx.reply(TypingActivityInput())\n\n    if \"reply\" in ctx.activity.text.lower():\n        await ctx.reply(\"Hello! How can I assist you today?\")\n    else:\n        await ctx.send(f\"You said '{ctx.activity.text}'\")\n```\n\n<!-- message-handling-step1 -->\n\nListens for all incoming messages using `app.on_message`\n\n<!-- message-handling-step3 -->\n\nResponds by echoing back the received message if any other text aside from \"reply\" is sent.\n\n<!-- message-handling-info -->\n\n:::info\nPython uses type hints for better development experience. You can change the activity handler to different supported activities, and the type system will provide appropriate hints and validation.\n:::\n\n<!-- app-lifecycle-code -->\n\n```python\nif __name__ == \"__main__\":\n    asyncio.run(app.start())\n```\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/code-basics/typescript.incl.md",
    "content": "<!-- imports -->\n\nN/A\n\n<!-- project-structure -->\n\n```\nquote-agent/\n|── appPackage/       # Teams app package files\n├── src/\n│   └── index.ts      # Main application code\n```\n\n<!-- project-structure-description -->\n\n- **appPackage/**: Contains the Teams app package files, including the `manifest.json` file and icons. This is required for [sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) the app into Teams for testing. The app manifest defines the app's metadata, capabilities, and permissions.\n- **src/**: Contains the main application code. The `index.ts` file is the entry point for your application.\n\n<!-- app-class-code -->\n\n```typescript title=\"src/index.ts\"\nimport { App } from '@microsoft/teams.apps';\nimport { ConsoleLogger } from '@microsoft/teams.common/logging';\nimport { DevtoolsPlugin } from '@microsoft/teams.dev';\n\nconst app = new App({\n  plugins: [new DevtoolsPlugin()],\n});\n```\n\n<!-- plugin-events -->\n\n(onActivity, onActivitySent, etc.)\n\n<!-- message-handling-code -->\n\n```typescript title=\"src/index.ts\"\napp.on('message', async ({ send, activity }) => {\n  await send({ type: 'typing' });\n  await send(`you said \"${activity.text}\"`);\n});\n```\n\n<!-- message-handling-step1 -->\n\nListens for all incoming messages using `app.on('message')`.\n\n<!-- message-handling-step3 -->\n\nResponds by echoing back the received message.\n\n<!-- message-handling-info -->\n\n:::info\nType safety is a core tenet of this version of the SDK. You can change the activity `name` to a different supported value, and the type system will automatically adjust the type of activity to match the new value.\n:::\n\n<!-- app-lifecycle-code -->\n\n```typescript title=\"src/index.ts\"\nawait app.start();\n```\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/csharp.incl.md",
    "content": "<!-- warning -->\n\nN/A\n\n<!-- language-name -->\n\nC#\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/python.incl.md",
    "content": "<!-- warning -->\n\n:::warning\nOur Python SDK is currently in Public Preview. We're going to do our best to not ship breaking changes, but breaking changes may happen from time to time\n:::\n\n<!-- language-name -->\n\nPython\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/quickstart/csharp.incl.md",
    "content": "<!-- prerequisites -->\n\n- **.NET** v.8 or higher. Install or upgrade from [dotnet.microsoft.com](https://dotnet.microsoft.com/en-us/download).\n\n<!-- create-command -->\n\n```sh\nnpx @microsoft/teams.cli@latest new csharp quote-agent --template echo\n```\n\n<!-- create-explanation -->\n\n1. Creates a new directory called `Quote.Agent`.\n2. Bootstraps the echo agent template files into your project directory.\n3. Creates your agent's manifest files, including a `manifest.json` file and placeholder icons in the `Quote.Agent/appPackage` directory. The Teams [app manifest](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) is required for [sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) the app into Teams.\n\n<!-- running-steps -->\n\n1. Navigate to your new agent's directory:\n\n```sh\ncd Quote.Agent/Quote.Agent\n```\n\n2. Install the dependencies:\n\n```sh\ndotnet restore\n```\n\n3. Start the development server:\n\n```sh\ndotnet run\n```\n\n<!-- console-output -->\n\n4. In the console, you should see a similar output:\n\n```sh\n[INFO] Microsoft.Hosting.Lifetime Now listening on: http://localhost:3978\n[WARN] Echo.Microsoft.Teams.Plugins.AspNetCore.DevTools ⚠️  Devtools are not secure and should not be used production environments ⚠️\n[INFO] Echo.Microsoft.Teams.Plugins.AspNetCore.DevTools Available at http://localhost:3979/devtools\n[INFO] Microsoft.Hosting.Lifetime Application started. Press Ctrl+C to shut down.\n[INFO] Microsoft.Hosting.Lifetime Hosting environment: Development\n```\n\n<!-- manual-install -->\n\nN/A\n\n<!-- manual-code -->\n\nN/A\n\n<!-- manual-more -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/quickstart/python.incl.md",
    "content": "<!-- prerequisites -->\n\n- **Python** v3.12 or higher. Install or upgrade from [python.org/downloads](https://www.python.org/downloads/).\n\n<!-- create-command -->\n\n```sh\nnpx @microsoft/teams.cli@latest new python quote-agent --template echo\n```\n\n<!-- create-explanation -->\n\n1. Creates a new directory called `quote-agent`.\n2. Bootstraps the echo agent template files into it under `quote-agent/src`.\n3. Creates your agent's manifest files, including a `manifest.json` file and placeholder icons in the `quote-agent/appPackage` directory. The Teams [app manifest](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) is required for [sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) the app into Teams.\n\n<!-- running-steps -->\n\nNavigate to your new agent's directory:\n\n```sh\ncd quote-agent\n```\n\nStart the development server:\n\n```sh\npython src/main.py\n```\n\n<!-- console-output -->\n\nIn the console, you should see a similar output:\n\n```sh\n[INFO] @teams/app Successfully initialized all plugins\n[WARNING] @teams/app.DevToolsPlugin ⚠️ Devtools is not secure and should not be used in production environments ⚠️\n[INFO] @teams/app.HttpPlugin Starting HTTP server on port 3978\nINFO:     Started server process [6436]\nINFO:     Waiting for application startup.\n[INFO] @teams/app.DevToolsPlugin available at http://localhost:3979/devtools\n[INFO] @teams/app.HttpPlugin listening on port 3978 🚀\n[INFO] @teams/app Teams app started successfully\n[INFO] @teams/app.DevToolsPlugin listening on port 3979 🚀\nINFO:     Application startup complete..\nINFO:     Uvicorn running on http://0.0.0.0:3979 (Press CTRL+C to quit)\n```\n\n<!-- manual-install -->\n\n```sh\npip install microsoft-teams-apps\n```\n\n<!-- manual-code -->\n\n```python\nimport asyncio\nimport uvicorn\nfrom fastapi import FastAPI\n# highlight-next-line\nfrom microsoft_teams.apps import App, FastAPIAdapter\n\n# Your existing FastAPI app\nmy_fastapi = FastAPI()\n\n# highlight-start\n# Wrap your app in an adapter and create the Teams app\nadapter = FastAPIAdapter(app=my_fastapi)\napp = App(http_server_adapter=adapter)\n\n@app.on_message\nasync def handle_message(ctx):\n    await ctx.send(f\"You said: {ctx.activity.text}\")\n# highlight-end\n\nasync def main():\n    # highlight-next-line\n    await app.initialize()  # Register the Teams endpoint (does not start a server)\n\n    # Start your server as usual\n    config = uvicorn.Config(app=my_fastapi, host=\"0.0.0.0\", port=3978)\n    server = uvicorn.Server(config)\n    await server.serve()\n\nasyncio.run(main())\n```\n\n<!-- manual-more -->\n\nSee the [HTTP Server guide](../in-depth-guides/server/http-server) for full details on adapters and custom server setups.\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/quickstart/typescript.incl.md",
    "content": "<!-- prerequisites -->\n\n- **Node.js** v.20 or higher. Install or upgrade from [nodejs.org](https://nodejs.org/).\n\n<!-- create-command -->\n\n```sh\nnpx @microsoft/teams.cli@latest new typescript quote-agent --template echo\n```\n\n<!-- create-explanation -->\n\n1. Creates a new directory called `quote-agent`.\n2. Bootstraps the echo agent template files into it under `quote-agent/src`.\n3. Creates your agent's manifest files, including a `manifest.json` file and placeholder icons in the `quote-agent/appPackage` directory. The Teams [app manifest](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema) is required for [sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload) the app into Teams.\n\n<!-- running-steps -->\n\n1. Navigate to your new agent's directory:\n\n```sh\ncd quote-agent\n```\n\n2. Install the dependencies:\n\n```sh\nnpm install\n```\n\n3. Start the development server:\n\n```sh\nnpm run dev\n```\n\n<!-- console-output -->\n\n4. In the console, you should see a similar output:\n\n```sh\n> quote-agent@0.0.0 dev\n> npx nodemon -w \"./src/**\" -e ts --exec \"node -r ts-node/register -r dotenv/config ./src/index.ts\"\n\n[nodemon] 3.1.9\n[nodemon] to restart at any time, enter `rs`\n[nodemon] watching path(s): src/**\n[nodemon] watching extensions: ts\n[nodemon] starting `node -r ts-node/register -r dotenv/config ./src/index.ts`\n[WARN] @teams/app/devtools ⚠️  Devtools are not secure and should not be used production environments ⚠️\n[INFO] @teams/app/http listening on port 3978 🚀\n[INFO] @teams/app/devtools available at http://localhost:3979/devtools\n```\n\n<!-- manual-install -->\n\n```sh\nnpm i @microsoft/teams.apps\n```\n\n<!-- manual-code -->\n\n```typescript\nimport http from 'http';\nimport express from 'express';\n// highlight-next-line\nimport { App, ExpressAdapter } from '@microsoft/teams.apps';\n\n// Your existing Express server\nconst expressApp = express();\nconst server = http.createServer(expressApp);\n\n// highlight-start\n// Wrap your server in an adapter and create the Teams app\nconst adapter = new ExpressAdapter(server);\nconst app = new App({ httpServerAdapter: adapter });\n\napp.on('message', async ({ send, activity }) => {\n  await send(`You said: ${activity.text}`);\n});\n\n// Register the Teams endpoint on your server (does not start it)\nawait app.initialize();\n// highlight-end\n\n// Start your server as usual\nserver.listen(3978);\n```\n\n<!-- manual-more -->\n\nSee the [HTTP Server guide](../in-depth-guides/server/http-server) for full details on adapters and custom server setups.\n"
  },
  {
    "path": "teams.md/src/components/include/getting-started/running-in-teams/csharp.incl.md",
    "content": "<!-- terminal-output -->\n\n```sh\n[INFO] Microsoft.Hosting.Lifetime Now listening on: http://localhost:3978\n[WARN] Echo.Microsoft.Teams.Plugins.AspNetCore.DevTools ⚠️  Devtools are not secure and should not be used production environments ⚠️\n[INFO] Echo.Microsoft.Teams.Plugins.AspNetCore.DevTools Available at http://localhost:3979/devtools\n[INFO] Microsoft.Hosting.Lifetime Application started. Press Ctrl+C to shut down.\n[INFO] Microsoft.Hosting.Lifetime Hosting environment: Development\n```"
  },
  {
    "path": "teams.md/src/components/include/getting-started/running-in-teams/python.incl.md",
    "content": "<!-- terminal-output -->\n\n```sh\n[INFO] @teams/app Successfully initialized all plugins\n[INFO] @teams/app.HttpPlugin Starting HTTP server on port 3978\nINFO:     Started server process [6436]\nINFO:     Waiting for application startup.\n[INFO] @teams/app.HttpPlugin listening on port 3978 🚀\n[INFO] @teams/app Teams app started successfully\nINFO:     Application startup complete..\nINFO:     Uvicorn running on http://0.0.0.0:3979 (Press CTRL+C to quit)\n```"
  },
  {
    "path": "teams.md/src/components/include/getting-started/running-in-teams/typescript.incl.md",
    "content": "<!-- terminal-output -->\n\n```sh\n[nodemon] 3.1.9\n[nodemon] to restart at any time, enter `rs`\n[nodemon] watching path(s): src/**\n[nodemon] watching extensions: ts\n[nodemon] starting `node -r ts-node/register -r dotenv/config ./src/index.ts`\n[WARN] @teams/app/devtools ⚠️  Devtools are not secure and should not be used production environments ⚠️\n[INFO] @teams/app/http listening on port 3978 🚀\n[INFO] @teams/app/devtools available at http://localhost:3979/devtools\n```"
  },
  {
    "path": "teams.md/src/components/include/getting-started/typescript.incl.md",
    "content": "<!-- warning -->\n\nN/A\n\n<!-- language-name -->\n\nTypeScript\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/building-adaptive-cards/csharp.incl.md",
    "content": "<!-- package-name -->\n\n`Microsoft.Teams.Cards`\n\n<!-- intro-description -->\n\nWith `Microsoft.Teams.Cards` you can build these cards entirely in C# while enjoying full IntelliSense and compiler safety.\n\n<!-- builder-description -->\n\n`Microsoft.Teams.Cards` exposes small **builder helpers** including `AdaptiveCard`, `TextBlock`, `ToggleInput`, `ExecuteAction`, _etc._\n\n<!-- language-name -->\n\nC#\n\n<!-- builder-example -->\n\n```csharp\nusing Microsoft.Teams.Cards;\n\nvar card = new AdaptiveCard\n{\n    Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n    Body = new List<CardElement>\n    {\n        new TextBlock(\"Hello world\")\n        {\n            Wrap = true,\n            Weight = TextWeight.Bolder\n        },\n        new ToggleInput(\"Notify me\")\n        {\n            Id = \"notify\"\n        }\n    },\n    Actions = new List<Microsoft.Teams.Cards.Action>\n    {\n        new ExecuteAction\n        {\n            Title = \"Submit\",\n            Data = new Union<string, SubmitActionData>(new SubmitActionData\n            {\n                NonSchemaProperties = new Dictionary<string, object?>\n                {\n                    { \"action\", \"submit_basic\" }\n                }\n            }),\n            AssociatedInputs = AssociatedInputs.Auto\n        }\n    }\n};\n```\n\n<!-- source-code-note -->\n\n:::info\nThe builder helpers use strongly-typed interfaces. Use IntelliSense (Ctrl+Space) or \"Go to Definition\" (F12) in your IDE to explore available types and properties. Source code lives in the `Microsoft.Teams.Cards` namespace.\n:::\n\n<!-- type-safety-example -->\n\n```csharp\n// \"Huge\" is not a valid size for TextBlock - this will cause a compilation error\nvar textBlock = new TextBlock(\"Test\")\n{\n    Wrap = true,\n    Weight = TextWeight.Bolder,\n    Size = \"Huge\" // This is invalid - should be TextSize enum\n};\n```\n\n<!-- additional-type-info -->\n\n<!-- designer-example -->\n\n```csharp\nvar cardJson = \"\"\"\n{\n    \"type\": \"AdaptiveCard\",\n    \"body\": [\n        {\n            \"type\": \"ColumnSet\",\n            \"columns\": [\n                {\n                    \"type\": \"Column\",\n                    \"verticalContentAlignment\": \"center\",\n                    \"items\": [\n                        {\n                            \"type\": \"Image\",\n                            \"style\": \"Person\",\n                            \"url\": \"https://aka.ms/AAp9xo4\",\n                            \"size\": \"Small\",\n                            \"altText\": \"Portrait of David Claux\"\n                        }\n                    ],\n                    \"width\": \"auto\"\n                },\n                {\n                    \"type\": \"Column\",\n                    \"spacing\": \"medium\",\n                    \"verticalContentAlignment\": \"center\",\n                    \"items\": [\n                        {\n                            \"type\": \"TextBlock\",\n                            \"weight\": \"Bolder\",\n                            \"text\": \"David Claux\",\n                            \"wrap\": true\n                        }\n                    ],\n                    \"width\": \"auto\"\n                },\n                {\n                    \"type\": \"Column\",\n                    \"spacing\": \"medium\",\n                    \"verticalContentAlignment\": \"center\",\n                    \"items\": [\n                        {\n                            \"type\": \"TextBlock\",\n                            \"text\": \"Principal Platform Architect at Microsoft\",\n                            \"isSubtle\": true,\n                            \"wrap\": true\n                        }\n                    ],\n                    \"width\": \"stretch\"\n                }\n            ]\n        }\n    ],\n    \"version\": \"1.5\",\n    \"schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\"\n}\n\"\"\";\n\n// Deserialize the JSON into an AdaptiveCard object\nvar card = AdaptiveCard.Deserialize(cardJson);\n\n// Send the card\nawait client.Send(card);\n```\n\n<!-- card-interface -->\n\n`AdaptiveCard`\n\n<!-- example-intro -->\n\n<Tabs>\n  <TabItem label=\"Minimal\" value=\"minimal\">\n    ```csharp\n    teams.OnMessage(async (context, cancellationToken) =>\n    {\n        var text = context.Activity.Text?.ToLowerInvariant() ?? \"\";\n\n        if (text.Contains(\"form\"))\n        {\n            await context.Typing(cancellationToken);\n            var card = CreateTaskFormCard();\n            await context.Send(card, cancellationToken);\n        }\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\nThe definition for `CreateTaskFormCard` is as follows\n\n<!-- task-form-example -->\n\n```csharp\nprivate static AdaptiveCard CreateTaskFormCard()\n{\n    return new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Create New Task\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Large\n            },\n            new TextInput\n            {\n                Id = \"title\",\n                Label = \"Task Title\",\n                Placeholder = \"Enter task title\"\n            },\n            new TextInput\n            {\n                Id = \"description\",\n                Label = \"Description\",\n                Placeholder = \"Enter task details\",\n                IsMultiline = true\n            },\n            new ChoiceSetInput\n            {\n                Id = \"priority\",\n                Label = \"Priority\",\n                Value = \"medium\",\n                Choices = new List<Choice>\n                {\n                    new() { Title = \"High\", Value = \"high\" },\n                    new() { Title = \"Medium\", Value = \"medium\" },\n                    new() { Title = \"Low\", Value = \"low\" }\n                }\n            },\n            new DateInput\n            {\n                Id = \"due_date\",\n                Label = \"Due Date\",\n                Value = DateTime.Now.ToString(\"yyyy-MM-dd\")\n            }\n        },\n        Actions = new List<Microsoft.Teams.Cards.Action>\n        {\n            new ExecuteAction\n            {\n                Title = \"Create Task\",\n                Data = new Union<string, SubmitActionData>(new SubmitActionData\n                {\n                    NonSchemaProperties = new Dictionary<string, object?>\n                    {\n                        { \"action\", \"create_task\" }\n                    }\n                }),\n                AssociatedInputs = AssociatedInputs.Auto,\n                Style = ActionStyle.Positive\n            }\n        }\n    };\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/building-adaptive-cards/python.incl.md",
    "content": "<!-- package-name -->\n\n`microsoft-teams-cards`\n\n<!-- intro-description -->\n\nWith `microsoft-teams-cards` you can build these cards entirely in Python while enjoying full IntelliSense and compiler safety.\n\n<!-- builder-description -->\n\n`microsoft-teams-cards` exposes small **builder helpers** including `Card`, `TextBlock`, `ToggleInput`, `ExecuteAction`, _etc._\n\n<!-- language-name -->\n\nPython\n\n<!-- builder-example -->\n\n```python\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock, ToggleInput, ActionSet, ExecuteAction\n\ncard = AdaptiveCard(\n        schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n        body=[\n            TextBlock(text=\"Hello world\", wrap=True, weight=\"Bolder\"),\n            ToggleInput(label=\"Notify me\").with_id(\"notify\"),\n            ActionSet(\n                actions=[\n                    ExecuteAction(title=\"Submit\")\n                    .with_data({\"action\": \"submit_basic\"})\n                    .with_associated_inputs(\"auto\")\n                ]\n            ),\n        ],\n    )\n```\n\n<!-- source-code-note -->\n\n:::info\nThe builder helpers use typed dictionaries and type hints. Use your IDE's IntelliSense features to explore available properties. Source code lives in the `teams.cards` module.\n:::\n\n<!-- type-safety-example -->\n\n```python\n# \"huge\" is not a valid size for TextBlock\ntext_block = TextBlock(text=\"Test\", wrap=True, weight=\"Bolder\", size=\"huge\"),\n```\n\n<!-- additional-type-info -->\n\n<!-- designer-example -->\n\n```python\n\ncard = AdaptiveCard.model_validate(\n    {\n        \"type\": \"AdaptiveCard\",\n        \"body\": [\n            {\n                \"type\": \"ColumnSet\",\n                \"columns\": [\n                    {\n                        \"type\": \"Column\",\n                        \"verticalContentAlignment\": \"center\",\n                        \"items\": [\n                            {\n                                \"type\": \"Image\",\n                                \"style\": \"Person\",\n                                \"url\": \"https://aka.ms/AAp9xo4\",\n                                \"size\": \"Small\",\n                                \"altText\": \"Portrait of David Claux\",\n                            }\n                        ],\n                        \"width\": \"auto\",\n                    },\n                    {\n                        \"type\": \"Column\",\n                        \"spacing\": \"medium\",\n                        \"verticalContentAlignment\": \"center\",\n                        \"items\": [{\"type\": \"TextBlock\", \"weight\": \"Bolder\", \"text\": \"David Claux\", \"wrap\": True}],\n                        \"width\": \"auto\",\n                    },\n                    {\n                        \"type\": \"Column\",\n                        \"spacing\": \"medium\",\n                        \"verticalContentAlignment\": \"center\",\n                        \"items\": [\n                            {\n                                \"type\": \"TextBlock\",\n                                \"text\": \"Principal Platform Architect at Microsoft\",\n                                \"isSubtle\": True,\n                                \"wrap\": True,\n                            }\n                        ],\n                        \"width\": \"stretch\",\n                    },\n                ],\n            }\n        ],\n        \"version\": \"1.5\",\n    }\n)\n# Send the card as an attachment\nmessage = MessageActivityInput(text=\"Hello text!\").add_card(card)\n```\n\n<!-- card-interface -->\n\n`AdaptiveCard`\n\n<!-- example-intro -->\n\nNotice how the builder pattern keeps the file readable and maintainable:\n\n<!-- task-form-example -->\n\n```python\nfrom datetime import datetime\nfrom microsoft_teams.api import MessageActivity, TypingActivityInput\nfrom microsoft_teams.apps import ActivityContext\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock, ActionSet, ExecuteAction, Choice, ChoiceSetInput, DateInput, TextInput\n# ...\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    await ctx.reply(TypingActivityInput())\n\n    card = AdaptiveCard(\n        schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n        body=[\n            TextBlock(text=\"Create New Task\", weight=\"Bolder\", size=\"Large\"),\n            TextInput(id=\"title\").with_label(\"Task Title\").with_placeholder(\"Enter task title\"),\n            TextInput(id=\"description\").with_label(\"Description\").with_placeholder(\"Enter task details\").with_is_multiline(True),\n            ChoiceSetInput(choices=[\n                Choice(title=\"High\", value=\"high\"),\n                Choice(title=\"Medium\", value=\"medium\"),\n                Choice(title=\"Low\", value=\"low\"),\n            ]).with_id(\"priority\").with_label(\"Priority\").with_value(\"medium\"),\n            DateInput(id=\"due_date\").with_label(\"Due Date\").with_value(datetime.now().strftime(\"%Y-%m-%d\")),\n            ActionSet(\n                actions=[\n                    ExecuteAction(title=\"Create Task\")\n                    .with_data({\"action\": \"create_task\"})\n                    .with_associated_inputs(\"auto\")\n                    .with_style(\"positive\")\n                ]\n            ),\n        ],\n    )\n\n    await ctx.send(card)\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/building-adaptive-cards/typescript.incl.md",
    "content": "<!-- package-name -->\n\n`@microsoft/teams.cards`\n\n<!-- intro-description -->\n\nWith `@microsoft/teams.cards` you can build these cards entirely in TypeScript/JavaScript while enjoying full IntelliSense and compiler safety.\n\n<!-- builder-description -->\n\n`@microsoft/teams.cards` exposes small **builder helpers** including `Card`, `TextBlock`, `ToggleInput`, `ExecuteAction`, _etc._\n\n<!-- language-name -->\n\nTypeScript/JavaScript\n\n<!-- builder-example -->\n\n```ts\nimport {\n  AdaptiveCard,\n  TextBlock,\n  ToggleInput,\n  ExecuteAction,\n  ActionSet,\n} from '@microsoft/teams.cards';\n\nconst card = new AdaptiveCard(\n  new TextBlock('Hello world', { wrap: true, weight: 'Bolder' }),\n  new ToggleInput('Notify me').withId('notify'),\n  new ActionSet(\n    new ExecuteAction({ title: 'Submit' })\n      .withData({ action: 'submit_basic' })\n      .withAssociatedInputs('auto')\n  )\n);\n```\n\n<!-- source-code-note -->\n\n:::info\nSource code lives in `teams.ts/packages/cards/src/`. Feel free to inspect or extend the helpers for your own needs.\n:::\n\n<!-- type-safety-example -->\n\n```typescript\n// @ts-expect-error: \"huge\" is not a valid size for TextBlock\nconst textBlock = new TextBlock('Valid', { size: 'huge' });\n```\n\n<!-- additional-type-info -->\n\n<!-- designer-example -->\n\n```typescript\nconst cardJson = /* copied JSON */;\nconst card = new AdaptiveCard().withBody(cardJson);\n```\n\n```ts\nconst rawCard: IAdaptiveCard = {\n  type: 'AdaptiveCard',\n  body: [\n    {\n      text: 'Please fill out the below form to send a game purchase request.',\n      wrap: true,\n      type: 'TextBlock',\n      style: 'heading',\n    },\n    {\n      columns: [\n        {\n          width: 'stretch',\n          items: [\n            {\n              choices: [\n                { title: 'Call of Duty', value: 'call_of_duty' },\n                { title: \"Death's Door\", value: 'deaths_door' },\n                { title: 'Grand Theft Auto V', value: 'grand_theft' },\n                { title: 'Minecraft', value: 'minecraft' },\n              ],\n              style: 'filtered',\n              placeholder: 'Search for a game',\n              id: 'choiceGameSingle',\n              type: 'Input.ChoiceSet',\n              label: 'Game:',\n            },\n          ],\n          type: 'Column',\n        },\n      ],\n      type: 'ColumnSet',\n    },\n  ],\n  actions: [\n    {\n      title: 'Request purchase',\n      type: 'Action.Execute',\n      data: { action: 'purchase_item' },\n    },\n  ],\n  version: '1.5',\n};\n```\n\n<!-- card-interface -->\n\n`IAdaptiveCard`\n\n<!-- example-intro -->\n\nNotice how the builder pattern keeps the file readable and maintainable:\n\n<!-- task-form-example -->\n\n```ts\nimport {\n  AdaptiveCard,\n  TextBlock,\n  TextInput,\n  ChoiceSetInput,\n  DateInput,\n  ActionSet,\n  ExecuteAction,\n} from '@microsoft/teams.cards';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message', async ({ send, activity }) => {\n  await send({ type: 'typing' });\n  const card = new AdaptiveCard(\n    new TextBlock('Create New Task', {\n      size: 'Large',\n      weight: 'Bolder',\n    }),\n    new TextInput({ id: 'title' }).withLabel('Task Title').withPlaceholder('Enter task title'),\n    new TextInput({ id: 'description' })\n      .withLabel('Description')\n      .withPlaceholder('Enter task details')\n      .withIsMultiline(true),\n    new ChoiceSetInput(\n      { title: 'High', value: 'high' },\n      { title: 'Medium', value: 'medium' },\n      { title: 'Low', value: 'low' }\n    )\n      .withId('priority')\n      .withLabel('Priority')\n      .withValue('medium'),\n    new DateInput({ id: 'due_date' })\n      .withLabel('Due Date')\n      .withValue(new Date().toISOString().split('T')[0]),\n    new ActionSet(\n      new ExecuteAction({ title: 'Create Task' })\n        .withData({ action: 'create_task' })\n        .withAssociatedInputs('auto')\n        .withStyle('positive')\n    )\n  );\n  await send(card);\n  // Or build a complex activity out that includes the card:\n  // const message  = new MessageActivity('Enter this form').addCard('adaptive', card);\n  // await send(message);\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/csharp.incl.md",
    "content": "<!-- intro -->\n\nOverview of Adaptive Cards in C# Teams SDK for building rich, interactive user experiences in Teams applications.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/csharp.incl.md",
    "content": "<!-- single-action-example -->\n\n```csharp\nusing Microsoft.Teams.Cards;\n\nvar action = new ExecuteAction\n{\n    Title = \"Submit Feedback\",\n    Data = new Union<string, SubmitActionData>(new SubmitActionData\n    {\n        NonSchemaProperties = new Dictionary<string, object?>\n        {\n            { \"action\", \"submit_feedback\" }\n        }\n    }),\n    AssociatedInputs = AssociatedInputs.Auto\n};\n```\n\n<!-- action-set-example -->\n\n```csharp\nusing Microsoft.Teams.Cards;\n\nvar card = new AdaptiveCard\n{\n    Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n    Actions = new List<Microsoft.Teams.Cards.Action>\n    {\n        new ExecuteAction\n        {\n            Title = \"Submit Feedback\",\n            Data = new Union<string, SubmitActionData>(new SubmitActionData\n            {\n                NonSchemaProperties = new Dictionary<string, object?>\n                {\n                    { \"action\", \"submit_feedback\" }\n                }\n            })\n        },\n        new OpenUrlAction(\"https://adaptivecards.microsoft.com\")\n        {\n            Title = \"Learn More\"\n        }\n    }\n};\n```\n\n<!-- json-safety-note -->\n\nN/A\n\n<!-- raw-json-example -->\n\n```csharp\nvar actionJson = \"\"\"\n{\n  \"type\": \"Action.OpenUrl\",\n  \"url\": \"https://adaptivecards.microsoft.com\",\n  \"title\": \"Learn More\"\n}\n\"\"\";\nvar action = OpenUrlAction.Deserialize(actionJson);\n```\n\n<!-- input-association-example -->\n\n```csharp\nprivate static AdaptiveCard CreateProfileCard()\n{\n    return new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"User Profile\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Large\n            },\n            new TextInput\n            {\n                Id = \"name\",\n                Label = \"Name\",\n                Value = \"John Doe\"\n            },\n            new TextInput\n            {\n                Id = \"email\",\n                Label = \"Email\",\n                Value = \"john@contoso.com\"\n            },\n            new ToggleInput(\"Subscribe to newsletter\")\n            {\n                Id = \"subscribe\",\n                Value = \"false\"\n            }\n        },\n        Actions = new List<Microsoft.Teams.Cards.Action>\n        {\n            new ExecuteAction\n            {\n                Title = \"Save\",\n                // entity_id will come back after the user submits\n                Data = new Union<string, SubmitActionData>(new SubmitActionData\n                {\n                    NonSchemaProperties = new Dictionary<string, object?>\n                    {\n                        { \"action\", \"save_profile\" },\n                        { \"entity_id\", \"12345\" }\n                    }\n                }),\n                AssociatedInputs = AssociatedInputs.Auto\n            }\n        }\n    };\n}\n\n// Data received in handler (conceptual structure)\n/*\n{\n  \"action\": \"save_profile\",\n  \"entity_id\": \"12345\",     // From action data\n  \"name\": \"John Doe\",       // From name input\n  \"email\": \"john@doe.com\",  // From email input\n  \"subscribe\": \"true\"       // From toggle input (as string)\n}\n\nAccessed in C# as:\n- data[\"action\"] → \"save_profile\"\n- data[\"entity_id\"] → \"12345\"\n- data[\"name\"] → \"John Doe\"\n- data[\"email\"] → \"john@doe.com\"\n- data[\"subscribe\"] → \"true\"\n*/\n```\n\n<!-- input-validation-example -->\n\n```csharp\nprivate static AdaptiveCard CreateProfileCardWithValidation()\n{\n    return new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Profile with Validation\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Large\n            },\n            new NumberInput\n            {\n                Id = \"age\",\n                Label = \"Age\",\n                IsRequired = true,\n                Min = 0,\n                Max = 120\n            },\n            // Can configure custom error messages\n            new TextInput\n            {\n                Id = \"name\",\n                Label = \"Name\",\n                IsRequired = true,\n                ErrorMessage = \"Name is required\"\n            },\n            new TextInput\n            {\n                Id = \"location\",\n                Label = \"Location\"\n            }\n        },\n        Actions = new List<Microsoft.Teams.Cards.Action>\n        {\n            new ExecuteAction\n            {\n                Title = \"Save\",\n                // All inputs should be validated\n                Data = new Union<string, SubmitActionData>(new SubmitActionData\n                {\n                    NonSchemaProperties = new Dictionary<string, object?>\n                    {\n                        { \"action\", \"save_profile\" }\n                    }\n                }),\n                AssociatedInputs = AssociatedInputs.Auto\n            }\n        }\n    };\n}\n```\n\n<!-- server-handler-example -->\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api.Activities.Invokes.AdaptiveCards;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Common.Logging;\n\n//...\n\nteams.OnAdaptiveCardAction(async (context, cancellationToken) =>\n{\n    var activity = context.Activity;\n    context.Log.Info(\"[CARD_ACTION] Card action received\");\n\n    var data = activity.Value?.Action?.Data;\n\n    context.Log.Info($\"[CARD_ACTION] Raw data: {JsonSerializer.Serialize(data)}\");\n\n    if (data == null)\n    {\n        context.Log.Error(\"[CARD_ACTION] No data in card action\");\n        return new ActionResponse.Message(\"No data specified\") { StatusCode = 400 };\n    }\n\n    string? action = data.TryGetValue(\"action\", out var actionObj) ? actionObj?.ToString() : null;\n\n    if (string.IsNullOrEmpty(action))\n    {\n        context.Log.Error(\"[CARD_ACTION] No action specified in card data\");\n        return new ActionResponse.Message(\"No action specified\") { StatusCode = 400 };\n    }\n    context.Log.Info($\"[CARD_ACTION] Processing action: {action}\");\n\n    string? GetFormValue(string key)\n    {\n        if (data.TryGetValue(key, out var val))\n        {\n            if (val is JsonElement element)\n                return element.GetString();\n            return val?.ToString();\n        }\n        return null;\n    }\n\n    switch (action)\n    {\n        case \"submit_basic\":\n            var notifyValue = GetFormValue(\"notify\") ?? \"false\";\n            await context.Send($\"Basic card submitted! Notify setting: {notifyValue}\", cancellationToken);\n            break;\n\n        case \"submit_feedback\":\n            var feedbackText = GetFormValue(\"feedback\") ?? \"No feedback provided\";\n            await context.Send($\"Feedback received: {feedbackText}\", cancellationToken);\n            break;\n\n        case \"create_task\":\n            var title = GetFormValue(\"title\") ?? \"Untitled\";\n            var priority = GetFormValue(\"priority\") ?? \"medium\";\n            var dueDate = GetFormValue(\"due_date\") ?? \"No date\";\n            await context.Send($\"Task created!\\nTitle: {title}\\nPriority: {priority}\\nDue: {dueDate}\", cancellationToken);\n            break;\n\n        case \"save_profile\":\n            var name = GetFormValue(\"name\") ?? \"Unknown\";\n            var email = GetFormValue(\"email\") ?? \"No email\";\n            var subscribe = GetFormValue(\"subscribe\") ?? \"false\";\n            var age = GetFormValue(\"age\");\n            var location = GetFormValue(\"location\") ?? \"Not specified\";\n\n            var response = $\"Profile saved!\\nName: {name}\\nEmail: {email}\\nSubscribed: {subscribe}\";\n            if (!string.IsNullOrEmpty(age))\n                response += $\"\\nAge: {age}\";\n            if (location != \"Not specified\")\n                response += $\"\\nLocation: {location}\";\n\n            await context.Send(response, cancellationToken);\n            break;\n\n        case \"test_json\":\n            await context.Send(\"JSON deserialization test successful!\", cancellationToken);\n            break;\n\n        default:\n            context.Log.Error($\"[CARD_ACTION] Unknown action: {action}\");\n            return new ActionResponse.Message(\"Unknown action\") { StatusCode = 400 };\n    }\n\n    return new ActionResponse.Message(\"Action processed successfully\") { StatusCode = 200 };\n});\n```\n\n<!-- data-typing-note -->\n\n:::note\nThe `data` values come from JSON and need to be extracted using the helper method shown above to handle different JSON element types.\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/python.incl.md",
    "content": "<!-- single-action-example -->\n\n```python\nfrom microsoft_teams.cards.core import ExecuteAction\n# ...\n\naction = ExecuteAction(title=\"Submit Feedback\")\n                    .with_data({\"action\": \"submit_feedback\"})\n                    .with_associated_inputs(\"auto\")\n```\n\n<!-- action-set-example -->\n\n```python\nfrom microsoft_teams.cards.core import ActionSet, ExecuteAction, OpenUrlAction\n# ...\n\naction_set = ActionSet(\n                actions=[\n                    ExecuteAction(title=\"Submit Feedback\")\n                    .with_data({\"action\": \"submit_feedback\"}),\n                    OpenUrlAction(url=\"https://adaptivecards.microsoft.com\").with_title(\"Learn More\")\n                ]\n            ),\n```\n\n<!-- json-safety-note -->\n\nYou get type safety for free in Python.\n\n<!-- raw-json-example -->\n\n```python\njson = {\n  \"type\": \"Action.OpenUrl\",\n  \"url\": \"https://adaptivecards.microsoft.com\",\n  \"title\": \"Learn More\",\n}\n```\n\n<!-- input-association-example -->\n\n```python\nfrom microsoft_teams.cards import AdaptiveCard, ActionSet, ExecuteAction, OpenUrlAction\nfrom microsoft_teams.cards.core import TextInput, ToggleInput\n# ...\n\nprofile_card = AdaptiveCard(\n        schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n        body=[\n            TextInput(id=\"name\").with_label(\"Name\").with_value(\"John Doe\"),\n            TextInput(id=\"email\", label=\"Email\", value=\"john@contoso.com\"),\n            ToggleInput(title=\"Subscribe to newsletter\").with_id(\"subscribe\").with_value(\"false\"),\n            ActionSet(\n                actions=[\n                    ExecuteAction(title=\"Save\")\n                    # entity_id will come back after the user submits\n                    .with_data({\"action\": \"save_profile\", \"entity_id\": \"12345\"}),\n                ]\n            ),\n        ],\n    )\n\n# Data received in handler:\n\"\"\"\n{\n  \"action\": \"save_profile\",\n  \"entity_id\": \"12345\",     # From action data\n  \"name\": \"John Doe\",       # From name input\n  \"email\": \"john@doe.com\",  # From email input\n  \"subscribe\": \"true\"       # From toggle input (as string)\n}\n\"\"\"\n```\n\n<!-- input-validation-example -->\n\n```python\nfrom microsoft_teams.cards import AdaptiveCard, ActionSet, ExecuteAction, NumberInput, TextInput\n# ...\n\ndef create_profile_card_input_validation():\n    age_input = NumberInput(id=\"age\").with_label(\"age\").with_is_required(True).with_min(0).with_max(120)\n    # Can configure custom error messages\n    name_input = TextInput(id=\"name\").with_label(\"Name\").with_is_required(True).with_error_message(\"Name is required\")\n\n    card = AdaptiveCard(\n        schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n        body=[\n            age_input,\n            name_input,\n            TextInput(id=\"location\").with_label(\"Location\"),\n            ActionSet(\n                actions=[\n                    ExecuteAction(title=\"Save\")\n                    # All inputs should be validated\n                    .with_data({\"action\": \"save_profile\"})\n                    .with_associated_inputs(\"auto\")\n                ]\n            ),\n        ],\n    )\n    return card\n```\n\n<!-- server-handler-example -->\n\n```python\nfrom microsoft_teams.api import AdaptiveCardInvokeActivity, AdaptiveCardActionErrorResponse, AdaptiveCardActionMessageResponse, HttpError, InnerHttpError, AdaptiveCardInvokeResponse\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_card_action\nasync def handle_card_action(ctx: ActivityContext[AdaptiveCardInvokeActivity]) -> AdaptiveCardInvokeResponse:\n    data = ctx.activity.value.action.data\n    if not data.get(\"action\"):\n        return AdaptiveCardActionErrorResponse(\n            status_code=400,\n            type=\"application/vnd.microsoft.error\",\n            value=HttpError(\n                code=\"BadRequest\",\n                message=\"No action specified\",\n                inner_http_error=InnerHttpError(\n                    status_code=400,\n                    body={\"error\": \"No action specified\"},\n                ),\n            ),\n        )\n\n    print(\"Received action data:\", data)\n\n    if data[\"action\"] == \"submit_feedback\":\n        await ctx.send(f\"Feedback received: {data.get('feedback')}\")\n    elif data[\"action\"] == \"purchase_item\":\n        await ctx.send(f\"Purchase request received for game: {data.get('choiceGameSingle')}\")\n    elif data[\"action\"] == \"save_profile\":\n        await ctx.send(\n            f\"Profile saved!\\nName: {data.get('name')}\\nEmail: {data.get('email')}\\nSubscribed: {data.get('subscribe')}\"\n        )\n    else:\n        return AdaptiveCardActionErrorResponse(\n            status_code=400,\n            type=\"application/vnd.microsoft.error\",\n            value=HttpError(\n                code=\"BadRequest\",\n                message=\"Unknown action\",\n                inner_http_error=InnerHttpError(\n                    status_code=400,\n                    body={\"error\": \"Unknown action\"},\n                ),\n            ),\n        )\n\n    return AdaptiveCardActionMessageResponse(\n        status_code=200,\n        type=\"application/vnd.microsoft.activity.message\",\n        value=\"Action processed successfully\",\n    )\n```\n\n<!-- data-typing-note -->\n\n:::note\nThe `data` values are accessible as a dictionary and can be accessed using `.get()` method for safe access.\n:::"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/executing-actions/typescript.incl.md",
    "content": "<!-- single-action-example -->\n\n```typescript\nimport { ExecuteAction } from '@microsoft/teams.cards';\n// ...\n\nnew ExecuteAction({ title: 'Submit Feedback' })\n  .withData({ action: 'submit_feedback' })\n  .withAssociatedInputs('auto'),\n```\n\n<!-- action-set-example -->\n\n```typescript\nimport { ExecuteAction, OpenUrlAction, ActionSet } from '@microsoft/teams.cards';\n// ...\n\nnew ActionSet(\n  new ExecuteAction({ title: 'Submit Feedback' })\n    .withData({ action: 'submit_feedback' })\n    .withAssociatedInputs('auto'),\n  new OpenUrlAction('https://adaptivecards.microsoft.com').withTitle('Learn More')\n);\n```\n\n<!-- json-safety-note -->\n\nYou get type safety for free in TypeScript.\n\n<!-- raw-json-example -->\n\n```typescript\nimport { IOpenUrlAction } from '@microsoft/teams.cards';\n// ...\n\n{\n  type: 'Action.OpenUrl',\n  url: 'https://adaptivecards.microsoft.com',\n  title: 'Learn More',\n} as const satisfies IOpenUrlAction\n```\n\n<!-- input-association-example -->\n\n```typescript\nimport {\n  AdaptiveCard,\n  TextInput,\n  ToggleInput,\n  ActionSet,\n  ExecuteAction,\n} from '@microsoft/teams.cards';\n// ...\n\nfunction editProfileCard() {\n  const card = new AdaptiveCard(\n    new TextInput({ id: 'name' }).withLabel('Name').withValue('John Doe'),\n    new TextInput({ id: 'email', label: 'Email', value: 'john@contoso.com' }),\n    new ToggleInput('Subscribe to newsletter').withId('subscribe').withValue('false'),\n    new ActionSet(\n      new ExecuteAction({ title: 'Save' })\n        .withData({\n          action: 'save_profile',\n          entityId: '12345', // This will come back once the user submits\n        })\n        .withAssociatedInputs('auto')\n    )\n  );\n\n  // Data received in handler\n  /**\n  {\n    action: \"save_profile\",\n    entityId: \"12345\",     // From action data\n    name: \"John Doe\",      // From name input\n    email: \"john@doe.com\", // From email input\n    subscribe: \"true\"      // From toggle input (as string)\n  }\n  */\n\n  return card;\n}\n```\n\n<!-- input-validation-example -->\n\n```typescript\nimport {\n  AdaptiveCard,\n  NumberInput,\n  TextInput,\n  ActionSet,\n  ExecuteAction,\n} from '@microsoft/teams.cards';\n// ...\n\nfunction createProfileCardInputValidation() {\n  const ageInput = new NumberInput({ id: 'age' })\n    .withLabel('Age')\n    .withIsRequired(true)\n    .withMin(0)\n    .withMax(120);\n\n  const nameInput = new TextInput({ id: 'name' })\n    .withLabel('Name')\n    .withIsRequired()\n    .withErrorMessage('Name is required!'); // Custom error messages\n  const card = new AdaptiveCard(\n    nameInput,\n    ageInput,\n    new TextInput({ id: 'location' }).withLabel('Location'),\n    new ActionSet(\n      new ExecuteAction({ title: 'Save' })\n        .withData({\n          action: 'save_profile',\n        })\n        .withAssociatedInputs('auto') // All inputs should be validated\n    )\n  );\n\n  return card;\n}\n```\n\n<!-- server-handler-example -->\n\n```typescript\nimport {\n  AdaptiveCardActionErrorResponse,\n  AdaptiveCardActionMessageResponse,\n} from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('card.action', async ({ activity, send }) => {\n  const data = activity.value?.action?.data;\n  if (!data?.action) {\n    return {\n      statusCode: 400,\n      type: 'application/vnd.microsoft.error',\n      value: {\n        code: 'BadRequest',\n        message: 'No action specified',\n        innerHttpError: {\n          statusCode: 400,\n          body: { error: 'No action specified' },\n        },\n      },\n    } satisfies AdaptiveCardActionErrorResponse;\n  }\n\n  console.debug('Received action data:', data);\n\n  switch (data.action) {\n    case 'submit_feedback':\n      await send(`Feedback received: ${data.feedback}`);\n      break;\n\n    case 'purchase_item':\n      await send(`Purchase request received for game: ${data.choiceGameSingle}`);\n      break;\n\n    case 'save_profile':\n      await send(\n        `Profile saved!\\nName: ${data.name}\\nEmail: ${data.email}\\nSubscribed: ${data.subscribe}`\n      );\n      break;\n\n    default:\n      return {\n        statusCode: 400,\n        type: 'application/vnd.microsoft.error',\n        value: {\n          code: 'BadRequest',\n          message: 'Unknown action',\n          innerHttpError: {\n            statusCode: 400,\n            body: { error: 'Unknown action' },\n          },\n        },\n      } satisfies AdaptiveCardActionErrorResponse;\n  }\n\n  return {\n    statusCode: 200,\n    type: 'application/vnd.microsoft.activity.message',\n    value: 'Action processed successfully',\n  } satisfies AdaptiveCardActionMessageResponse;\n});\n```\n\n<!-- data-typing-note -->\n\n:::note\nThe `data` values are not typed and come as `any`, so you will need to cast them to the correct type in this case.\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/python.incl.md",
    "content": "<!-- intro -->\n\nOverview of Adaptive Cards in Python Teams SDK for building rich, interactive user experiences in Teams applications.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/adaptive-cards/typescript.incl.md",
    "content": "<!-- intro -->\n\nOverview of Adaptive Cards in TypeScript Teams SDK for building rich, interactive user experiences in Teams applications.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-client/python.incl.md",
    "content": "<!-- direct-client -->\n\n```python\nimport uuid\nfrom httpx import AsyncClient\nfrom a2a.types import AgentCard, Message, Part, Role, Task, TextPart\nfrom a2a.client import ClientFactory, Client\nfrom a2a.client.card_resolver import A2ACardResolver\nfrom a2a.client.client import ClientConfig\nfrom .agent_client_info import AgentClientInfo\nfrom .agent_config import AgentConfig\n\n# Retrive the agent card based on the URL\nasync def fetch_agent_card(self, base_url: str, card_url: str) -> AgentCard:\n    async with AsyncClient() as httpx_client:\n      resolver = A2ACardResolver(httpx_client, base_url)\n      card = await resolver.get_agent_card(card_url)\n      return card\n\nasync def _get_client(self, key: str, config: AgentConfig) -> Client:\n  try:\n    # Example base_url=f\"http://localhost:{PORT}/a2a\"\n    # Example card_url=\".well-known/agent-card.json\"\n    card = await self.fetch_agent_card(base_url=config.base_url, card_url=config.card_url)\n    client_config = ClientConfig()\n    # Create the client from the URL\n    client = ClientFactory(client_config).create(card=card)\n    client_info = AgentClientInfo(**asdict(config), client=client, agent_card=card)\n    self._clients.update({key: client_info})\n    return client\n  except Exception as e:\n    self.log.error(f\"Error creating client or fetching agent card for {key}: {e}\")\n    raise e\n\nasync def _send_message(self) -> None:\n  # Send a message directly\n  message = Message(\n              message_id=str(uuid.uuid4()),\n              role=Role(\"user\"),\n              parts=[Part(root=TextPart(kind=\"text\", text=\"What is the weather?\"))],\n          )\n\n  async for event in self._get_client().send_message(message):\n    # Handle the event\n```\n\n<!-- client-plugin -->\n\n```python\nfrom os import getenv\nfrom microsoft_teams.openai.completions_model import OpenAICompletionsAIModel\nfrom microsoft_teams.a2a import A2AClientPlugin, A2APluginUseParams\nfrom microsoft_teams.ai import ChatPrompt\n\nPORT = getenv(\"PORT\", \"4000\")\n\n# Setup AI\ndef get_required_env(key: str) -> str:\n    value = getenv(key)\n    if not value:\n        raise ValueError(f\"Required environment variable {key} is not set\")\n    return value\n\n\nAZURE_OPENAI_MODEL = get_required_env(\"AZURE_OPENAI_MODEL\")\ncompletions_model = OpenAICompletionsAIModel(model=AZURE_OPENAI_MODEL)\n\n# Setup A2A Client Plugin\nclient_plugin = A2AClientPlugin()\n# Specify the connection details for the agent we want to use\nclient_plugin.on_use_plugin(\n    A2APluginUseParams(\n        key=\"my-weather-agent\", base_url=f\"http://localhost:{PORT}/a2a\", card_url=\".well-known/agent-card.json\"\n    )\n)\nprompt = ChatPrompt(\n    model=completions_model,\n    plugins=[client_plugin],\n)\n```\n\n<!-- send-message -->\n\n```python\n# Now we can send the message to the prompt and it will decide if\n# the a2a agent should be used or not and also manages contacting the agent\nresult = await prompt.send(message)\n```\n\n<!-- advanced-config -->\n\n```python\n# Example with custom message builders and response processors\ndef build_function_metadata(card: AgentCard) -> FunctionMetadata:\n    return FunctionMetadata(\n        name=f\"ask{re.sub(r'\\s+', '', card.name)}\",\n        description=f\"Ask {card.name} about {card.description or 'anything'}\",\n    )\n\n\ndef build_message_for_agent(data: BuildMessageForAgentMetadata) -> Union[Message, str]:\n    # Return a string - will be automatically wrapped in a Message\n    return f\"[To {data.card.name}]: {data.input}\"\n\n    # Uncomment the following block to return a full Message object\n    # message = Message(\n    #                 kind='message',\n    #                 message_id=str(uuid4()),\n    #                 role=Role('user'),\n    #                 parts=[Part(root=TextPart(kind='text', text=f\"[To {data.card.name}]: {data.input}\"))],\n    #                 metadata={\"source\": \"chat-prompt\", **(data.metadata if data.metadata else {})}\n    #             )\n    # return message\n\n\ndef build_message_from_agent_response(data: BuildMessageFromAgentMetadata) -> str:\n    if isinstance(data.response, Message):\n        text_parts: List[str] = []\n        for part in data.response.parts:\n            if getattr(part.root, \"kind\", None) == \"text\":\n                text_part = cast(TextPart, part.root)\n                text_parts.append(text_part.text)\n        return f\"{data.card.name} says: {' '.join(text_parts)}\"\n    return f\"{data.card.name} sent a non-text response.\"\n\n\n## Advanced A2AClientPlugin\nadvanced_plugin = A2AClientPlugin(\n    # Custom function metadata builder\n    build_function_metadata=build_function_metadata,\n    # Custom message builder - can return either Message or string\n    build_message_for_agent=build_message_for_agent,\n    # Custom response processor\n    build_message_from_agent_response=build_message_from_agent_response,\n)\nadvanced_plugin.on_use_plugin(\n    A2APluginUseParams(\n        key=\"my-weather-agent\", base_url=f\"http://localhost:{PORT}/a2a\", card_url=\".well-known/agent-card.json\"\n    )\n)\nadvanced_prompt = ChatPrompt(model=completions_model, plugins=[advanced_plugin])\n```\n\n\n<!-- sequence-diagram -->\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant ChatPrompt\n    participant A2AClientPlugin\n    participant A2ACardResolver\n    participant Client\n    participant LLM\n    participant A2AServer\n\n    Note over User,A2AServer: Configuration\n    User->>A2AClientPlugin: on_use_plugin()\n\n    Note over User,A2AServer: Message Flow\n    User->>ChatPrompt: send(message)\n    ChatPrompt->>A2AClientPlugin: on_build_instructions()\n    A2AClientPlugin->>A2ACardResolver: fetch_agent_card()\n    A2ACardResolver->>A2AServer: GET /.well-known/agent-card.json\n    A2AServer-->>A2ACardResolver: AgentCard\n    A2ACardResolver-->>A2AClientPlugin: AgentCard\n    A2AClientPlugin-->>ChatPrompt: Enhanced system prompt\n\n    ChatPrompt->>A2AClientPlugin: on_build_functions()\n    A2AClientPlugin-->>ChatPrompt: Function tools for agents\n\n    ChatPrompt->>LLM: Enhanced prompt + tools\n    LLM-->>ChatPrompt: Function call (message_agent)\n    ChatPrompt->>A2AClientPlugin: Execute function handler\n    A2AClientPlugin->>Client: send_message()\n    Client->>A2AServer: POST /a2a/task/send\n    A2AServer-->>Client: Response\n    Client-->>A2AClientPlugin: Response\n    A2AClientPlugin-->>ChatPrompt: Processed response\n    ChatPrompt-->>User: Final response\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-client/typescript.incl.md",
    "content": "<!-- direct-client -->\n\n```typescript\nimport { A2AClient } from '@a2a-js/sdk/client';\n\n// Create client from agent card URL\nconst client = await A2AClient.fromCardUrl('http://localhost:4000/a2a/.well-known/agent-card.json');\n\n// Send a message directly\nconst response = await client.sendMessage({\n  message: {\n    messageId: 'unique-id',\n    role: 'user',\n    parts: [{ kind: 'text', text: 'What is the weather?' }],\n    kind: 'message',\n  },\n});\n```\n\n<!-- client-plugin -->\n\n```typescript\nimport { A2AClientPlugin } from '@microsoft/teams.a2a';\nimport { ChatPrompt } from '@microsoft/teams.ai';\nimport { OpenAIChatModel } from '@microsoft/teams.openai';\n\nconst prompt = new ChatPrompt(\n  {\n    model: new OpenAIChatModel({\n      apiKey: process.env.AZURE_OPENAI_API_KEY,\n      model: process.env.AZURE_OPENAI_MODEL!,\n      endpoint: process.env.AZURE_OPENAI_ENDPOINT,\n      apiVersion: process.env.AZURE_OPENAI_API_VERSION,\n    }),\n  },\n  // Add the A2AClientPlugin to the prompt\n  [new A2AClientPlugin()]\n)\n  // Provide the agent's card URL\n  .usePlugin('a2a', {\n    key: 'my-weather-agent',\n    cardUrl: 'http://localhost:4000/a2a/.well-known/agent-card.json',\n  });\n```\n\n<!-- send-message -->\n\n```typescript\n// Now we can send the message to the prompt and it will decide if\n// the a2a agent should be used or not and also manages contacting the agent\nconst result = await prompt.send(message);\n```\n\n<!-- advanced-config -->\n\n```typescript\n// Example with custom message builders and response processors\nexport const advancedPrompt = new ChatPrompt(\n  {\n    model: new OpenAIChatModel({\n      apiKey: process.env.AZURE_OPENAI_API_KEY,\n      model: process.env.AZURE_OPENAI_MODEL!,\n      endpoint: process.env.AZURE_OPENAI_ENDPOINT,\n      apiVersion: process.env.AZURE_OPENAI_API_VERSION,\n    }),\n  },\n  [\n    new A2AClientPlugin({\n      // Custom function metadata builder\n      buildFunctionMetadata: (card) => ({\n        name: `ask${card.name.replace(/\\s+/g, '')}`,\n        description: `Ask ${card.name} about ${card.description || 'anything'}`,\n      }),\n      // Custom message builder - can return either Message or string\n      buildMessageForAgent: (card, input) => {\n        // Return a string - will be automatically wrapped in a Message\n        return `[To ${card.name}]: ${input}`;\n      },\n      // Custom response processor\n      buildMessageFromAgentResponse: (card, response) => {\n        if (response.kind === 'message') {\n          const textParts = response.parts\n            .filter((part) => part.kind === 'text')\n            .map((part) => part.text);\n          return `${card.name} says: ${textParts.join(' ')}`;\n        }\n        return `${card.name} sent a non-text response.`;\n      },\n    }),\n  ]\n).usePlugin('a2a', {\n  key: 'weather-agent',\n  cardUrl: 'http://localhost:4000/a2a/.well-known/agent-card.json',\n});\n```\n\n<!-- sequence-diagram -->\n\n```mermaid\nsequenceDiagram\n    participant User\n    participant ChatPrompt\n    participant A2AClientPlugin\n    participant A2AClient\n    participant LLM\n    participant A2AServer\n\n    Note over User,A2AServer: Configuration\n    User->>ChatPrompt: usePlugin('a2a', {cardUrl})\n    ChatPrompt->>A2AClientPlugin: onUsePlugin()\n\n    Note over User,A2AServer: Message Flow\n    User->>ChatPrompt: send(message)\n    ChatPrompt->>A2AClientPlugin: onBuildPrompt()\n    A2AClientPlugin->>A2AClient: getAgentCard()\n    A2AClient->>A2AServer: GET /.well-known/agent-card.json\n    A2AServer-->>A2AClient: AgentCard\n    A2AClient-->>A2AClientPlugin: AgentCard\n    A2AClientPlugin-->>ChatPrompt: Enhanced system prompt\n\n    ChatPrompt->>A2AClientPlugin: onBuildFunctions()\n    A2AClientPlugin-->>ChatPrompt: Function tools for agents\n\n    ChatPrompt->>LLM: Enhanced prompt + tools\n    LLM-->>ChatPrompt: Function call (messageAgent)\n    ChatPrompt->>A2AClientPlugin: Execute function handler\n    A2AClientPlugin->>A2AClient: sendMessage()\n    A2AClient->>A2AServer: POST /a2a/task/send\n    A2AServer-->>A2AClient: Response\n    A2AClient-->>A2AClientPlugin: Response\n    A2AClientPlugin-->>ChatPrompt: Processed response\n    ChatPrompt-->>User: Final response\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-server/python.incl.md",
    "content": "<!-- agent-card -->\n\n`agent_card`\n\n<!-- plugin-example -->\n\n```python\nfrom os import getenv\nfrom a2a.types import AgentCard, AgentCapabilities, AgentSkill\nfrom microsoft_teams.a2a import A2APlugin, A2APluginOptions\nfrom microsoft_teams.apps import App, PluginBase\n\nPORT = getenv(\"PORT\", \"4000\")\n\nagent_card = AgentCard(\n    name=\"weather_agent\",\n    description=\"An agent that can tell you the weather\",\n    url=f\"http://localhost:{PORT}/a2a/\",\n    version=\"0.0.1\",\n    protocol_version=\"0.3.0\",\n    capabilities=AgentCapabilities(),\n    default_input_modes=[],\n    default_output_modes=[],\n    skills=[\n        AgentSkill(\n            # Expose various skills that this agent can perform\n            id=\"get_weather\",\n            name=\"Get Weather\",\n            description=\"Get the weather for a given location\",\n            tags=[\"weather\", \"get\", \"location\"],\n            examples=[\n                # Give concrete examples on how to contact the agent\n                \"Get the weather for London\",\n                \"What is the weather\",\n                \"What's the weather in Tokyo?\",\n                \"How is the current temperature in San Francisco?\",\n            ],\n        ),\n    ],\n)\n\nplugins: List[PluginBase] = [A2APlugin(A2APluginOptions(agent_card=agent_card))]\napp = App(logger=logger, plugins=plugins)\n```\n\n<!-- event-handler -->\n\n```python\nfrom microsoft_teams.a2a import A2AMessageEvent, A2AMessageEventKey\nfrom a2a.types import TextPart\n\n@app.event(A2AMessageEventKey)\nasync def handle_a2a_message(message: A2AMessageEvent) -> None:\n    request_context = message.get(\"request_context\")\n    respond = message.get(\"respond\")\n\n    logger.info(f\"Received message: {request_context.message}\")\n\n    if request_context.message:\n        text_input = None\n        for part in request_context.message.parts:\n            if getattr(part.root, \"kind\", None) == \"text\":\n                text_part = cast(TextPart, part.root)\n                text_input = text_part.text\n                break\n        if not text_input:\n            await respond(\"My agent currently only supports text input\")\n            return\n\n        result = await my_event_handler(text_input)\n        await respond(result)\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/a2a/a2a-server/typescript.incl.md",
    "content": "<!-- agent-card -->\n\n`agentCard`\n\n<!-- plugin-example -->\n\n```typescript\nimport { AgentCard } from '@a2a-js/sdk';\nimport { A2APlugin } from '@microsoft/teams.a2a';\nimport { App } from '@microsoft/teams.apps';\n\nconst agentCard: AgentCard = {\n  name: 'Weather Agent',\n  description: 'An agent that can tell you the weather',\n  url: `http://localhost:${PORT}/a2a`,\n  version: '0.0.1',\n  protocolVersion: '0.3.0',\n  capabilities: {},\n  defaultInputModes: [],\n  defaultOutputModes: [],\n  skills: [\n    {\n      id: 'get_weather',\n      name: 'Get Weather',\n      description: 'Get the weather for a given location',\n      tags: ['weather', 'get', 'location'],\n      examples: [\n        'Get the weather for London',\n        'What is the weather',\n        \"What's the weather in Tokyo?\",\n        'How is the current temperature in San Francisco?',\n      ],\n    },\n  ],\n};\n\nconst app = new App({\n  plugins: [\n    new A2APlugin({\n      agentCard,\n    }),\n  ],\n});\n```\n\n<!-- event-handler -->\n\n```typescript\napp.event('a2a:message', async ({ respond, requestContext }) => {\n  logger.info(`Received message: ${requestContext.userMessage}`);\n  const textInput = requestContext.userMessage.parts\n    .filter((p): p is TextPart => p.kind === 'text')\n    .at(0)?.text;\n  if (!textInput) {\n    await respond('My agent currently only supports text input');\n    return;\n  }\n  const result = await myEventHandler(textInput);\n  await respond(result);\n});\n```\n\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/a2a/python.incl.md",
    "content": "<!-- note -->\n\n:::note\nThis package wraps the official [A2A SDK](https://github.com/a2aproject/a2a-python) for both server and client.\n:::\n\n<!-- install -->\n\nInstall the package:\n\n```bash\npip install microsoft-teams-a2a\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/a2a/typescript.incl.md",
    "content": "<!-- note -->\n\n:::note\nThis package wraps the official [A2A SDK](https://github.com/a2aproject/a2a-js) for both server and client.\n:::\n\n<!-- install -->\n\nInstall the package:\n\n```bash\nnpm install @microsoft/teams.a2a\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/best-practices/csharp.incl.md",
    "content": "<!-- ai-generated-method -->\n\nThis can be done by calling the `.AddAIGenerated()` method on outgoing messages.\n\n<!-- ai-generated-code -->\n\n```csharp\nvar messageActivity = new MessageActivity\n{\n    Text = \"Hello!\",\n}.AddAIGenerated();\n```\n\n<!-- citations-method -->\n\nThis is easy to do by using the `AddCitation` method on the message.\n\n<!-- citations-code -->\n\n```csharp\nvar messageActivity = new MessageActivity\n{\n    Text = result.Content,\n}.AddAIGenerated();\n\nfor (int i = 0; i < citedDocs.Length; i++)\n{\n    messageActivity.Text += $\"[{i + 1}]\";\n    messageActivity.AddCitation(i + 1, new CitationAppearance\n    {\n        Name = citedDocs[i].Title,\n        Abstract = citedDocs[i].Content\n    });\n}\n```\n\n<!-- suggested-actions-method -->\n\nYou can do that by using the `WithSuggestedActions` method on the message.\n\n<!-- suggested-actions-code -->\n\n```csharp\nvar message = new MessageActivity\n{\n    Text = result.Content,\n}.WithSuggestedActions(\n    new Microsoft.Teams.Api.SuggestedActions() {\n        To = [context.Activity.From.Id],\n        Actions = [\n            new Microsoft.Teams.Api.Cards.Action(ActionType.IMBack) {\n                Title = \"Thank you!\",\n                Value = \"Thank you very much!\"\n                }\n        ]\n    }).AddAIGenerated();\nawait context.Send(message);\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/best-practices/python.incl.md",
    "content": "<!-- ai-generated-method -->\n\nThis can be done by adding a `addAiGenerated` property to outgoing message.\n\n<!-- ai-generated-code -->\n\n```python\nmessage_to_be_sent = MessageActivityInput(text=\"Hello!\").add_ai_generated()\n```\n\n<!-- citations-method -->\n\nThis is easy to do by simply using the `addCitations` method on the message. This will add a citation to the message, and the LLM will be able to use it to generate a citation for the user.\n\n<!-- citations-code -->\n\n```python\nfrom microsoft_teams.api import MessageActivityInput, CitationAppearance\n\nmessage_activity = MessageActivityInput(text=result.content).add_ai_generated()\nfor i, doc in enumerate(cited_docs):\n    message_activity.text += f\"[{i + 1}]\"\n    message_activity.add_citation(i + 1, CitationAppearance(name=doc[\"title\"], abstract=doc[\"content\"]))\n```\n\n<!-- suggested-actions-method -->\n\nYou can do that by using the `with_suggested_actions` method on the message.\n\n<!-- suggested-actions-code -->\n\n```python\nfrom microsoft_teams.api import CardAction, CardActionType, MessageActivityInput, SuggestedActions\n\nsuggested_actions = SuggestedActions(\n    to=[activity.from_.id],\n    actions=[CardAction(type=CardActionType.IM_BACK, title=\"Thanks!\", value=\"Thank you so much!\")],\n)\nmessage = (\n    MessageActivityInput(text=chat_result.response.content)\n    .add_ai_generated()\n    .with_suggested_actions(suggested_actions)\n)\nawait ctx.send(message)\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/best-practices/typescript.incl.md",
    "content": "<!-- ai-generated-method -->\n\nThis can be done by adding a `addAiGenerated` property to outgoing message.\n\n<!-- ai-generated-code -->\n\n```typescript\nconst messageToBeSent = new MessageActivity('Hello!').addAiGenerated();\n```\n\n<!-- citations-method -->\n\nThis is easy to do by simply using the `addCitations` method on the message. This will add a citation to the message, and the LLM will be able to use it to generate a citation for the user.\n\n<!-- citations-code -->\n\n```typescript\nimport { MessageActivity } from '@microsoft/teams.api';\n// ...\n\nconst messageActivity = new MessageActivity(result.content).addAiGenerated();\nfor (let i = 0; i < citedDocs.length; i++) {\n  const doc = citedDocs[i];\n  // The corresponding citation needs to be added in the message content\n  messageActivity.text += `[${i + 1}]`;\n  messageActivity.addCitation(i + 1, {\n    name: doc.title,\n    abstract: doc.content,\n  });\n}\n```\n\n<!-- suggested-actions-method -->\n\nYou can do that by using the `withSuggestedActions` method on the message.\n\n<!-- suggested-actions-code -->\n\n```typescript\nmessage.withSuggestedActions({\n  to: [activity.from.id],\n  actions: [\n    {\n      type: 'imBack',\n      title: 'Show pricing options',\n      value: 'Show the pricing options available to me',\n    },\n  ],\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/chat/csharp.incl.md",
    "content": "<!-- simple-chat-setup -->\n\nImport the relevant namespaces:\n\n```csharp\n// AI\nusing Microsoft.Teams.AI.Models.OpenAI;\nusing Microsoft.Teams.AI.Prompts;\n// Teams\nusing Microsoft.Teams.Api.Activities;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities;\nusing Microsoft.Teams.Apps.Annotations;\n```\n\nCreate a ChatModel, ChatPrompt, and handle user - LLM interactions:\n\n<!-- simple-chat-code -->\n\n```csharp\nusing Microsoft.Teams.AI.Models.OpenAI;\nusing Microsoft.Teams.AI.Prompts;\nusing Microsoft.Teams.AI.Templates;\nusing Microsoft.Teams.Api.Activities;\nusing Microsoft.Teams.Apps.Activities;\nusing Azure.AI.OpenAI;\nusing System.ClientModel;\n\n// Configuration\nvar azureOpenAIModel = configuration[\"AzureOpenAIModel\"]!;\nvar azureOpenAIEndpoint = configuration[\"AzureOpenAIEndpoint\"]!;\nvar azureOpenAIKey = configuration[\"AzureOpenAIKey\"]!;\n\nvar azureOpenAI = new AzureOpenAIClient(\n    new Uri(azureOpenAIEndpoint),\n    new ApiKeyCredential(azureOpenAIKey)\n);\n\n// AI Model\nvar aiModel = new OpenAIChatModel(azureOpenAIModel, azureOpenAI);\n\n// Simple chat handler\nteamsApp.OnMessage(async (context, cancellationToken) =>\n{\n    var prompt = new OpenAIChatPrompt(aiModel, new ChatPromptOptions\n    {\n        Instructions = new StringTemplate(\"You are a friendly assistant who talks like a pirate\")\n    });\n\n    var result = await prompt.Send(context.Activity.Text);\n    if (result.Content != null)\n    {\n        var messageActivity = new MessageActivity\n        {\n            Text = result.Content,\n        }.AddAIGenerated();\n        await context.Send(messageActivity, cancellationToken);\n        // Ahoy, matey! 🏴‍☠️ How be ye doin' this fine day on th' high seas? What can this ol' salty sea dog help ye with? 🚢☠️\n    }\n});\n```\n\n<!-- declarative-approach -->\n\n### Declarative Approach\n\nThis approach uses attributes to declare prompts, providing clean separation of concerns.\n\n**Create a Prompt Class:**\n\n```csharp\nusing Microsoft.Teams.AI.Annotations;\n\nnamespace Samples.AI.Prompts;\n\n[Prompt]\n[Prompt.Description(\"A friendly pirate assistant\")]\n[Prompt.Instructions(\"You are a friendly assistant who talks like a pirate\")]\npublic class PiratePrompt\n{\n}\n```\n\n**Usage in Program.cs:**\n\n```csharp\nusing Microsoft.Teams.AI.Models.OpenAI;\nusing Microsoft.Teams.Api.Activities;\n\n// Create the AI model\nvar aiModel = new OpenAIChatModel(azureOpenAIModel, azureOpenAI);\n\n// Use the prompt with OpenAIChatPrompt.From()\nteamsApp.OnMessage(async (context, cancellationToken) =>\n{\n    var prompt = OpenAIChatPrompt.From(aiModel, new Samples.AI.Prompts.PiratePrompt());\n\n    var result = await prompt.Send(context.Activity.Text);\n\n    if (!string.IsNullOrEmpty(result.Content))\n    {\n        await context.Send(new MessageActivity { Text = result.Content }.AddAIGenerated(), cancellationToken);\n        // Ahoy, matey! 🏴‍☠️ How be ye doin' this fine day on th' high seas?\n    }\n});\n```\n\n<!-- simple-chat-notes -->\n\n:::note\nThe current `OpenAIChatModel` implementation uses chat-completions API. The responses API is coming soon.\n:::\n\n<!-- additional-concepts -->\n\nN/A\n\n<!-- streaming-code -->\n\n```csharp\n// Streaming handler\nteamsApp.OnMessage(async (context, cancellationToken) =>\n{\n    var match = Regex.Match(context.Activity.Text ?? \"\", @\"^stream\\s+(.+)\", RegexOptions.IgnoreCase);\n    if (match.Success)\n    {\n        var query = match.Groups[1].Value.Trim();\n        var prompt = new OpenAIChatPrompt(aiModel, new ChatPromptOptions\n        {\n            Instructions = new StringTemplate(\"You are a friendly assistant who responds in extremely verbose language\")\n        });\n\n        var result = await prompt.Send(query, (chunk) =>\n        {\n            context.Stream.Emit(chunk);\n            return Task.CompletedTask;\n        });\n    }\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/chat/python.incl.md",
    "content": "<!-- simple-chat-setup -->\n\nImport the relevant objects:\n\n```python\nfrom microsoft_teams.ai import ChatPrompt\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\nfrom microsoft_teams.openai import OpenAICompletionsAIModel\n```\n\n<!-- simple-chat-code -->\n\n```python\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    openai_model = OpenAICompletionsAIModel(model=AZURE_OPENAI_MODEL)\n    agent = ChatPrompt(model=openai_model)\n\n    chat_result = await agent.send(\n        input=ctx.activity.text,\n        instructions=\"You are a friendly assistant who talks like a pirate.\"\n    )\n    result = chat_result.response\n    if result.content:\n        await ctx.send(MessageActivityInput(text=result.content).add_ai_generated())\n        # Ahoy, matey! 🏴‍☠️ How be ye doin' this fine day on th' high seas? What can this ol' salty sea dog help ye with? 🚢☠️\n```\n\n<!-- declarative-approach -->\n\nN/A\n\n<!-- simple-chat-notes -->\n\n:::note\nThe current `OpenAICompletionsAIModel` implementation uses Chat Completions API. The Responses API is also available.\n:::\n\n<!-- additional-concepts -->\n\n### Agent\n\nInstead of `ChatPrompt`, you may also use `Agent`. The `Agent` class is a derivation from `ChatPrompt` but it differs in that it's stateful. The `memory` object passed to the `Agent` object will be reused for subsequent calls to `send`, whereas for `ChatPrompt`, each call to `send` is independent.\n\n<!-- streaming-code -->\n\n```python\nfrom microsoft_teams.ai import ChatPrompt\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\nfrom microsoft_teams.openai import OpenAICompletionsAIModel\n# ...\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    openai_model = OpenAICompletionsAIModel(model=AZURE_OPENAI_MODEL)\n    agent = ChatPrompt(model=openai_model)\n\n    chat_result = await agent.send(\n        input=ctx.activity.text,\n        instructions=\"You are a friendly assistant who responds in terse language.\",\n        on_chunk=lambda chunk: ctx.stream.emit(chunk)\n    )\n    result = chat_result.response\n\n    if ctx.activity.conversation.is_group:\n        # If the conversation is a group chat, we need to send the final response\n        # back to the group chat\n        await ctx.send(MessageActivityInput(text=result.content).add_ai_generated())\n    else:\n        ctx.stream.emit(MessageActivityInput().add_ai_generated())\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/chat/typescript.incl.md",
    "content": "<!-- simple-chat-setup -->\n\nImport the relevant objects:\n\n```typescript\nimport { OpenAIChatModel } from '@microsoft/teams.openai';\n```\n\n<!-- simple-chat-code -->\n\n```typescript\nimport { ChatPrompt } from '@microsoft/teams.ai';\nimport { MessageActivity } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\nimport { OpenAIChatModel } from '@microsoft/teams.openai';\n// ...\n\napp.on('message', async ({ send, activity, next, log }) => {\n  const model = new OpenAIChatModel({\n    apiKey: process.env.AZURE_OPENAI_API_KEY || process.env.OPENAI_API_KEY,\n    endpoint: process.env.AZURE_OPENAI_ENDPOINT,\n    apiVersion: process.env.AZURE_OPENAI_API_VERSION,\n    model: process.env.AZURE_OPENAI_MODEL_DEPLOYMENT_NAME!,\n  });\n\n  const prompt = new ChatPrompt({\n    instructions: 'You are a friendly assistant who talks like a pirate',\n    model,\n  });\n\n  const response = await prompt.send(activity.text);\n  if (response.content) {\n    const activity = new MessageActivity(response.content).addAiGenerated();\n    await send(activity);\n    // Ahoy, matey! 🏴‍☠️ How be ye doin' this fine day on th' high seas? What can this ol' salty sea dog help ye with? 🚢☠️\n  }\n});\n```\n\n<!-- declarative-approach -->\n\nN/A\n\n<!-- simple-chat-notes -->\n\n:::note\nThe current `OpenAIChatModel` implementation uses chat-completions API. The responses API is coming soon.\n:::\n\n<!-- additional-concepts -->\n\nN/A\n\n<!-- streaming-code -->\n\n```typescript\nimport { ChatPrompt } from '@microsoft/teams.ai';\nimport { MessageActivity } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message', async ({ stream, send, activity, next, log }) => {\n  // const query = activity.text;\n\n  const prompt = new ChatPrompt({\n    instructions: 'You are a friendly assistant who responds in extremely verbose language',\n    model,\n  });\n\n  // Notice that we don't `send` the final response back, but\n  // `stream` the chunks as they come in\n  const response = await prompt.send(query, {\n    onChunk: (chunk) => {\n      stream.emit(chunk);\n    },\n  });\n\n  if (activity.conversation.isGroup) {\n    // If the conversation is a group chat, we need to send the final response\n    // back to the group chat\n    const activity = new MessageActivity(response.content).addAiGenerated();\n    await send(activity);\n  } else {\n    // We wrap the final response with an AI Generated indicator\n    stream.emit(new MessageActivity().addAiGenerated());\n  }\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/csharp.incl.md",
    "content": "<!-- package-name -->\n\n`Microsoft.Teams.AI`\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/function-calling/csharp.incl.md",
    "content": "<!-- adding-functions -->\n\nregistering functions with a `ChatPrompt` using the `.Function()` method\n\n<!-- sequence-diagram -->\n\n```mermaid\nsequenceDiagram\n  participant User\n  participant ChatPrompt\n  participant LLM\n  participant Function-PokemonSearch\n  participant ExternalAPI\n\n  User->>ChatPrompt: send(\"Tell me about Pikachu\")\n  ChatPrompt->>LLM: Provide instructions, message, and available functions\n    LLM->>ChatPrompt: Decide to call `pokemon_search` with pokemon_name=\"Pikachu\"\n    ChatPrompt->>Function-PokemonSearch: Execute with pokemon_name\n    Function-PokemonSearch->>ExternalAPI: fetch Pokemon data\n    ExternalAPI-->>Function-PokemonSearch: return Pokemon info\n    Function-PokemonSearch-->>ChatPrompt: return result\n  ChatPrompt->>LLM: Send function result(s)\n  LLM-->>ChatPrompt: Final user-facing response\n  ChatPrompt-->>User: send(result.content)\n```\n\n<!-- single-function-example -->\n\n## Single Function Example\n\nHere's a complete example showing how to create a Pokemon search function that the LLM can call.\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n<Tabs>\n  <TabItem label=\"Imperative\" value=\"imperative\" default>\n    ```csharp\n    using System.Text.Json;\n    using Microsoft.Teams.AI.Annotations;\n    using Microsoft.Teams.AI.Models.OpenAI;\n    using Microsoft.Teams.AI.Prompts;\n    using Microsoft.Teams.AI.Templates;\n    using Microsoft.Teams.Api.Activities;\n    using Microsoft.Teams.Apps;\n\n    /// <summary>\n    /// Handle Pokemon search using PokeAPI\n    /// </summary>\n    public static async Task<string> PokemonSearchFunction([Param(\"pokemon_name\")] string pokemonName)\n    {\n        try\n        {\n            using var client = new HttpClient();\n            var response = await client.GetAsync($\"https://pokeapi.co/api/v2/pokemon/{pokemonName.ToLower()}\");\n\n            if (!response.IsSuccessStatusCode)\n            {\n                return $\"Pokemon '{pokemonName}' not found\";\n            }\n\n            var json = await response.Content.ReadAsStringAsync();\n            var data = JsonDocument.Parse(json);\n            var root = data.RootElement;\n\n            var name = root.GetProperty(\"name\").GetString();\n            var height = root.GetProperty(\"height\").GetInt32();\n            var weight = root.GetProperty(\"weight\").GetInt32();\n            var types = root.GetProperty(\"types\")\n                .EnumerateArray()\n                .Select(t => t.GetProperty(\"type\").GetProperty(\"name\").GetString())\n                .ToList();\n\n            return $\"Pokemon {name}: height={height}, weight={weight}, types={string.Join(\", \", types)}\";\n        }\n        catch (Exception ex)\n        {\n            return $\"Error searching for Pokemon: {ex.Message}\";\n        }\n    }\n\n    /// <summary>\n    /// Handle single function calling - Pokemon search\n    /// </summary>\n    public static async Task HandlePokemonSearch(OpenAIChatModel model, IContext<MessageActivity> context)\n    {\n        var prompt = new OpenAIChatPrompt(model, new ChatPromptOptions\n        {\n            Instructions = new StringTemplate(\"You are a helpful assistant that can look up Pokemon for the user.\")\n        });\n\n        // Register the pokemon search function\n        prompt.Function(\n            \"pokemon_search\",\n            \"Search for pokemon information including height, weight, and types\",\n            PokemonSearchFunction\n        );\n\n        var result = await prompt.Send(context.Activity.Text);\n\n        if (result.Content != null)\n        {\n            var message = new MessageActivity\n            {\n                Text = result.Content,\n            }.AddAIGenerated();\n            await context.Send(message);\n        }\n        else\n        {\n            await context.Reply(\"Sorry I could not find that pokemon\");\n        }\n    }\n    ```\n\n  </TabItem>\n  <TabItem label=\"Declarative\" value=\"declarative\">\n    This approach uses attributes to declare prompts and functions, providing clean separation of concerns.\n\n    **Create a Prompt Class:**\n\n    ```csharp\n    using System.Text.Json;\n    using Microsoft.Teams.AI.Annotations;\n\n    namespace Samples.AI.Prompts;\n\n    [Prompt]\n    [Prompt.Description(\"Pokemon search assistant\")]\n    [Prompt.Instructions(\"You are a helpful assistant that can look up Pokemon for the user.\")]\n    public class PokemonPrompt\n    {\n        [Function]\n        [Function.Description(\"Search for pokemon information including height, weight, and types\")]\n        public async Task<string> PokemonSearch([Param(\"pokemon_name\")] string pokemonName)\n        {\n            try\n            {\n                using var httpClient = new HttpClient();\n                var response = await httpClient.GetAsync($\"https://pokeapi.co/api/v2/pokemon/{pokemonName.ToLower()}\");\n\n                if (!response.IsSuccessStatusCode)\n                {\n                    return $\"Pokemon '{pokemonName}' not found\";\n                }\n\n                var json = await response.Content.ReadAsStringAsync();\n                var data = JsonDocument.Parse(json);\n                var root = data.RootElement;\n\n                var name = root.GetProperty(\"name\").GetString();\n                var height = root.GetProperty(\"height\").GetInt32();\n                var weight = root.GetProperty(\"weight\").GetInt32();\n                var types = root.GetProperty(\"types\")\n                    .EnumerateArray()\n                    .Select(t => t.GetProperty(\"type\").GetProperty(\"name\").GetString())\n                    .ToList();\n\n                return $\"Pokemon {name}: height={height}, weight={weight}, types={string.Join(\", \", types)}\";\n            }\n            catch (Exception ex)\n            {\n                return $\"Error searching for Pokemon: {ex.Message}\";\n            }\n        }\n    }\n    ```\n\n    **Usage in Program.cs:**\n\n    ```csharp\n    using Microsoft.Teams.AI.Models.OpenAI;\n    using Microsoft.Teams.Api.Activities;\n\n    // Create the AI model\n    var aiModel = new OpenAIChatModel(azureOpenAIModel, azureOpenAI);\n\n    // Use the prompt with OpenAIChatPrompt.From()\n    teamsApp.OnMessage(async (context, cancellationToken) =>\n    {\n        var prompt = OpenAIChatPrompt.From(aiModel, new Samples.AI.Prompts.PokemonPrompt());\n\n        var result = await prompt.Send(context.Activity.Text);\n\n        if (!string.IsNullOrEmpty(result.Content))\n        {\n            await context.Send(new MessageActivity { Text = result.Content }.AddAIGenerated(), cancellationToken);\n        }\n        else\n        {\n            await context.Reply(\"Sorry I could not find that pokemon\", cancellationToken);\n        }\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\n### How It Works\n\n1. **Function Definition**: The function is defined as a regular C# method with parameters decorated with the `[Param]` attribute\n2. **Automatic Schema Generation**: The SDK automatically generates the JSON schema for the function parameters using reflection\n3. **Function Registration**:\n   - **Imperative Approach**: The `.Function()` method registers the function with the prompt, providing the name, description, and handler\n   - **Declarative Approach**: The `[Function]` attribute automatically registers methods when using `OpenAIChatPrompt.From()`\n4. **Automatic Invocation**: When the LLM decides to call the function, it automatically:\n   - Parses the function call arguments\n   - Validates them against the schema\n   - Invokes the handler\n   - Returns the result back to the LLM\n\n<!-- multiple-functions -->\n\n## Multiple Functions\n\nAdditionally, for complex scenarios, you can add multiple functions to the `ChatPrompt`. The LLM will then decide which function(s) to call based on the context of the conversation.\n\n<Tabs>\n  <TabItem label=\"Imperative\" value=\"imperative\" default>\n    ```csharp\n    /// <summary>\n    /// Get user location (mock)\n    /// </summary>\n    public static string GetLocationFunction()\n    {\n        var locations = new[] { \"Seattle\", \"San Francisco\", \"New York\" };\n        var random = new Random();\n        var location = locations[random.Next(locations.Length)];\n        return location;\n    }\n\n    /// <summary>\n    /// Get weather for location (mock)\n    /// </summary>\n    public static string GetWeatherFunction([Param] string location)\n    {\n        var weatherByLocation = new Dictionary<string, (int Temperature, string Condition)>\n        {\n            [\"Seattle\"] = (65, \"sunny\"),\n            [\"San Francisco\"] = (60, \"foggy\"),\n            [\"New York\"] = (75, \"rainy\")\n        };\n\n        if (!weatherByLocation.TryGetValue(location, out var weather))\n        {\n            return \"Sorry, I could not find the weather for that location\";\n        }\n\n        return $\"The weather in {location} is {weather.Condition} with a temperature of {weather.Temperature}°F\";\n    }\n\n    /// <summary>\n    /// Handle multiple function calling - location then weather\n    /// </summary>\n    public static async Task HandleMultipleFunctions(OpenAIChatModel model, IContext<MessageActivity> context)\n    {\n        var prompt = new OpenAIChatPrompt(model, new ChatPromptOptions\n        {\n            Instructions = new StringTemplate(\"You are a helpful assistant that can help the user get the weather. First get their location, then get the weather for that location.\")\n        });\n\n        // Register both functions\n        prompt.Function(\n            \"get_user_location\",\n            \"Gets the location of the user\",\n            GetLocationFunction\n        );\n\n        prompt.Function(\n            \"weather_search\",\n            \"Search for weather at a specific location\",\n            GetWeatherFunction\n        );\n\n        var result = await prompt.Send(context.Activity.Text);\n\n        if (result.Content != null)\n        {\n            var message = new MessageActivity\n            {\n                Text = result.Content,\n            }.AddAIGenerated();\n            await context.Send(message);\n        }\n        else\n        {\n            await context.Reply(\"Sorry I could not figure it out\");\n        }\n    }\n    ```\n\n  </TabItem>\n  <TabItem label=\"Declarative\" value=\"declarative\">\n    **Create a Prompt Class:**\n\n    ```csharp\n    using Microsoft.Teams.AI.Annotations;\n\n    namespace Samples.AI.Prompts;\n\n    [Prompt]\n    [Prompt.Description(\"Weather assistant\")]\n    [Prompt.Instructions(\"You are a helpful assistant that can help the user get the weather. First get their location, then get the weather for that location.\")]\n    public class WeatherPrompt\n    {\n        [Function]\n        [Function.Description(\"Gets the location of the user\")]\n        public string GetUserLocation()\n        {\n            var locations = new[] { \"Seattle\", \"San Francisco\", \"New York\" };\n            var random = new Random();\n            return locations[random.Next(locations.Length)];\n        }\n\n        [Function]\n        [Function.Description(\"Search for weather at a specific location\")]\n        public string WeatherSearch([Param] string location)\n        {\n            var weatherByLocation = new Dictionary<string, (int Temperature, string Condition)>\n            {\n                [\"Seattle\"] = (65, \"sunny\"),\n                [\"San Francisco\"] = (60, \"foggy\"),\n                [\"New York\"] = (75, \"rainy\")\n            };\n\n            if (!weatherByLocation.TryGetValue(location, out var weather))\n            {\n                return \"Sorry, I could not find the weather for that location\";\n            }\n\n            return $\"The weather in {location} is {weather.Condition} with a temperature of {weather.Temperature}°F\";\n        }\n    }\n    ```\n\n    **Usage in Program.cs:**\n\n    ```csharp\n    using Microsoft.Teams.AI.Models.OpenAI;\n    using Microsoft.Teams.Api.Activities;\n\n    // Create the AI model\n    var aiModel = new OpenAIChatModel(azureOpenAIModel, azureOpenAI);\n\n    // Use the prompt with OpenAIChatPrompt.From()\n    teamsApp.OnMessage(async (context, cancellationToken) =>\n    {\n        var prompt = OpenAIChatPrompt.From(aiModel, new Samples.AI.Prompts.WeatherPrompt());\n\n        var result = await prompt.Send(context.Activity.Text);\n\n        if (!string.IsNullOrEmpty(result.Content))\n        {\n            await context.Send(new MessageActivity { Text = result.Content }.AddAIGenerated(), cancellationToken);\n        }\n        else\n        {\n            await context.Reply(\"Sorry I could not figure it out\", cancellationToken);\n        }\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\n### Multiple Function Execution Flow\n\nWhen you register multiple functions:\n\n1. The LLM receives information about all available functions\n2. Based on the user's query, it decides which function(s) to call and in what order\n3. For example, asking \"What's the weather?\" might trigger:\n   - First: `get_user_location()` to determine where the user is\n   - Then: `weather_search(location)` to get the weather for that location\n4. The LLM combines all function results to generate the final response\n\n:::tip\nThe LLM can call functions sequentially - using the output of one function as input to another - without any additional configuration. This makes it powerful for complex, multi-step workflows.\n:::\n\n<!-- advanced-features -->\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/function-calling/python.incl.md",
    "content": "<!-- adding-functions -->\n\nadding a `function` to the `ChatPrompt`\n\n<!-- sequence-diagram -->\n\n```mermaid\nsequenceDiagram\n  participant User\n  participant ChatPrompt\n  participant LLM\n  participant Function-PokemonSearch\n  participant ExternalAPI\n\n  User->>ChatPrompt: send(activity.text)\n  ChatPrompt->>LLM: Provide instructions, message, and available functions\n    LLM->>ChatPrompt: Decide to call `pokemon_search` with pokemon_name\n    ChatPrompt->>Function-PokemonSearch: Execute with pokemon_name\n    Function-PokemonSearch->>ExternalAPI: fetch pokemon data\n    ExternalAPI-->>Function-PokemonSearch: return pokemon info\n    Function-PokemonSearch-->>ChatPrompt: return result\n  ChatPrompt->>LLM: Send function result(s)\n  LLM-->>ChatPrompt: Final user-facing response\n  ChatPrompt-->>User: send(result.content)\n```\n\n<!-- single-function-example -->\n\n```python\nimport aiohttp\nimport random\nfrom microsoft_teams.ai import Agent, Function\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\nfrom microsoft_teams.openai import OpenAICompletionsAIModel\nfrom pydantic import BaseModel\n\nclass SearchPokemonParams(BaseModel):\n    pokemon_name: str\n    \"\"\"The name of the pokemon.\"\"\"\n\nasync def pokemon_search_handler(params: SearchPokemonParams) -> str:\n    \"\"\"Search for Pokemon using PokeAPI - matches documentation example\"\"\"\n    try:\n        async with aiohttp.ClientSession() as session:\n            async with session.get(f\"https://pokeapi.co/api/v2/pokemon/{params.pokemon_name.lower()}\") as response:\n                if response.status != 200:\n                    raise ValueError(f\"Pokemon '{params.pokemon_name}' not found\")\n\n                data = await response.json()\n\n                result_data = {\n                    \"name\": data[\"name\"],\n                    \"height\": data[\"height\"],\n                    \"weight\": data[\"weight\"],\n                    \"types\": [type_info[\"type\"][\"name\"] for type_info in data[\"types\"]],\n                }\n\n                return f\"Pokemon {result_data['name']}: height={result_data['height']}, weight={result_data['weight']}, types={', '.join(result_data['types'])}\"\n    except Exception as e:\n        raise ValueError(f\"Error searching for Pokemon: {str(e)}\")\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    openai_model = OpenAICompletionsAIModel(model=AZURE_OPENAI_MODEL)\n    agent = Agent(model=openai_model)\n    agent.with_function(\n        Function(\n            name=\"pokemon_search\",\n            description=\"Search for pokemon information including height, weight, and types\",\n            # Include the schema of the parameters\n            # the LLM needs to return to call the function\n            parameter_schema=SearchPokemonParams,\n            handler=pokemon_search_handler,\n        )\n    )\n\n    chat_result = await agent.send(\n            input=ctx.activity.text,\n            instructions=\"You are a helpful assistant that can look up Pokemon for the user.\",\n        )\n\n    if chat_result.response.content:\n        message = MessageActivityInput(text=chat_result.response.content).add_ai_generated()\n        await ctx.send(message)\n    else:\n        await ctx.reply(\"Sorry I could not find that pokemon\")\n```\n\n<!-- multiple-functions -->\n\n## Multiple functions\n\nAdditionally, for complex scenarios, you can add multiple functions to the `ChatPrompt`. The LLM will then decide which function to call based on the context of the conversation. The LLM can pick one or more functions to call before returning the final response.\n\n```python\nimport random\nfrom microsoft_teams.ai import Agent, Function\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\nfrom pydantic import BaseModel\n# ...\n\nclass GetLocationParams(BaseModel):\n    \"\"\"No parameters needed for location\"\"\"\n    pass\n\nclass GetWeatherParams(BaseModel):\n    location: str\n    \"\"\"The location to get weather for\"\"\"\n\ndef get_location_handler(params: GetLocationParams) -> str:\n    \"\"\"Get user location (mock)\"\"\"\n    locations = [\"Seattle\", \"San Francisco\", \"New York\"]\n    location = random.choice(locations)\n    return location\n\ndef get_weather_handler(params: GetWeatherParams) -> str:\n    \"\"\"Get weather for location (mock)\"\"\"\n    weather_by_location = {\n        \"Seattle\": {\"temperature\": 65, \"condition\": \"sunny\"},\n        \"San Francisco\": {\"temperature\": 60, \"condition\": \"foggy\"},\n        \"New York\": {\"temperature\": 75, \"condition\": \"rainy\"},\n    }\n\n    weather = weather_by_location.get(params.location)\n    if not weather:\n        return \"Sorry, I could not find the weather for that location\"\n\n    return f\"The weather in {params.location} is {weather['condition']} with a temperature of {weather['temperature']}°F\"\n\n@app.on_message\nasync def handle_multiple_functions(ctx: ActivityContext[MessageActivity]):\n    agent = Agent(model)\n\n    agent.with_function(\n        Function(\n            name=\"get_user_location\",\n            description=\"Gets the location of the user\",\n            parameter_schema=GetLocationParams,\n            handler=get_location_handler,\n        )\n    ).with_function(\n        Function(\n            name=\"weather_search\",\n            description=\"Search for weather at a specific location\",\n            parameter_schema=GetWeatherParams,\n            handler=get_weather_handler,\n        )\n    )\n\n    chat_result = await agent.send(\n        input=ctx.activity.text,\n        instructions=\"You are a helpful assistant that can help the user get the weather. First get their location, then get the weather for that location.\",\n    )\n\n    if chat_result.response.content:\n        message = MessageActivityInput(text=chat_result.response.content).add_ai_generated()\n        await ctx.send(message)\n    else:\n        await ctx.reply(\"Sorry I could not figure it out\")\n```\n\n<!-- advanced-features -->\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/function-calling/typescript.incl.md",
    "content": "<!-- adding-functions -->\n\nadding a `function` to the `ChatPrompt`\n\n<!-- sequence-diagram -->\n\n```mermaid\nsequenceDiagram\n  participant User\n  participant ChatPrompt\n  participant LLM\n  participant Function-PokemonSearch\n  participant ExternalAPI\n\n  User->>ChatPrompt: send(activity.text)\n  ChatPrompt->>LLM: Provide instructions, message, and available functions\n    LLM->>ChatPrompt: Decide to call `pokemonSearch` with parameters\n    ChatPrompt->>Function-PokemonSearch: Execute with pokemonName\n    Function-PokemonSearch->>ExternalAPI: fetch pokemon data\n    ExternalAPI-->>Function-PokemonSearch: return pokemon info\n    Function-PokemonSearch-->>ChatPrompt: return result\n  ChatPrompt->>LLM: Send function result(s)\n  LLM-->>ChatPrompt: Final user-facing response\n  ChatPrompt-->>User: send(result.content)\n```\n\n<!-- single-function-example -->\n\n```typescript\nimport { ChatPrompt, IChatModel } from '@microsoft/teams.ai';\nimport { ActivityLike, IMessageActivity } from '@microsoft/teams.api';\n// ...\n\nconst prompt = new ChatPrompt({\n  instructions: 'You are a helpful assistant that can look up Pokemon for the user.',\n  model,\n})\n  // Include `function` as part of the prompt\n  .function(\n    'pokemonSearch',\n    'search for pokemon',\n    // Include the schema of the parameters\n    // the LLM needs to return to call the function\n    {\n      type: 'object',\n      properties: {\n        pokemonName: {\n          type: 'string',\n          description: 'the name of the pokemon',\n        },\n      },\n      required: ['text'],\n    },\n    // The cooresponding function will be called\n    // automatically if the LLM decides to call this function\n    async ({ pokemonName }: IPokemonSearch) => {\n      log.info('Searching for pokemon', pokemonName);\n      const response = await fetch(`https://pokeapi.co/api/v2/pokemon/${pokemonName}`);\n      if (!response.ok) {\n        throw new Error('Pokemon not found');\n      }\n      const data = await response.json();\n      // The result of the function call is sent back to the LLM\n      return {\n        name: data.name,\n        height: data.height,\n        weight: data.weight,\n        types: data.types.map((type: { type: { name: string } }) => type.type.name),\n      };\n    }\n  );\n\n// The LLM will then produce a final response to be sent back to the user\n// activity.text could have text like 'pikachu'\nconst result = await prompt.send(activity.text);\nawait send(result.content ?? 'Sorry I could not find that pokemon');\n```\n\n<!-- multiple-functions -->\n\n## Multiple functions\n\nAdditionally, for complex scenarios, you can add multiple functions to the `ChatPrompt`. The LLM will then decide which function to call based on the context of the conversation. The LLM can pick one or more functions to call before returning the final response.\n\n```typescript\nimport { ChatPrompt, IChatModel } from '@microsoft/teams.ai';\nimport { ActivityLike, IMessageActivity } from '@microsoft/teams.api';\n// ...\n\n// activity.text could be something like \"what's my weather?\"\n// The LLM will need to first figure out the user's location\n// Then pass that in to the weatherSearch\nconst prompt = new ChatPrompt({\n  instructions: 'You are a helpful assistant that can help the user get the weather',\n  model,\n})\n  // Include multiple `function`s as part of the prompt\n  .function(\n    'getUserLocation',\n    'gets the location of the user',\n    // This function doesn't need any parameters,\n    // so we do not need to provide a schema\n    async () => {\n      const locations = ['Seattle', 'San Francisco', 'New York'];\n      const randomIndex = Math.floor(Math.random() * locations.length);\n      const location = locations[randomIndex];\n      log.info('Found user location', location);\n      return location;\n    }\n  )\n  .function(\n    'weatherSearch',\n    'search for weather',\n    {\n      type: 'object',\n      properties: {\n        location: {\n          type: 'string',\n          description: 'the name of the location',\n        },\n      },\n      required: ['location'],\n    },\n    async ({ location }: { location: string }) => {\n      const weatherByLocation: Record<string, {}> = {\n        Seattle: { temperature: 65, condition: 'sunny' },\n        'San Francisco': { temperature: 60, condition: 'foggy' },\n        'New York': { temperature: 75, condition: 'rainy' },\n      };\n\n      const weather = weatherByLocation[location];\n      if (!weather) {\n        return 'Sorry, I could not find the weather for that location';\n      }\n\n      log.info('Found weather', weather);\n      return weather;\n    }\n  );\n\n// The LLM will then produce a final response to be sent back to the user\nconst result = await prompt.send(activity.text);\nawait send(result.content ?? 'Sorry I could not figure it out');\n```\n\n<!-- advanced-features -->\n\n## Stopping Functions early\n\nYou'll notice that after the function responds, `ChatPrompt` re-sends the response from the function invocation back to the LLM which responds back with the user-facing message. It's possible to prevent this \"automatic\" function calling by passing in a flag\n\n```typescript\nimport { ChatPrompt, IChatModel, Message } from '@microsoft/teams.ai';\nimport { ActivityLike, IMessageActivity } from '@microsoft/teams.api';\n// ...\n\nconst result = await prompt.send(activity.text, {\n  autoFunctionCalling: false, // Disable automatic function calling\n});\n// Extract the function call arguments from the result\nconst functionCallArgs = result.function_calls?.[0].arguments;\n\nconst firstCall = result.function_calls?.[0];\nconst fnResult = actualFunction(firstCall.arguments);\nmessages.push({\n  role: 'function',\n  function_id: firstCall.id,\n  content: fnResult,\n});\n\n// Optionally, you can call the chat prompt again after updating the messages with the results\nconst result = await prompt.send('What should we do next?', {\n  messages,\n  autoFunctionCalling: true, // You can enable it here if you want\n});\nconst functionCallArgs = result.function_calls?.[0].arguments; // Extract the function call arguments\nawait send(\n  `The LLM responed with the following structured output: ${JSON.stringify(functionCallArgs, undefined, 2)}.`\n);\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/keeping-state/csharp.incl.md",
    "content": "<!-- state-initialization -->\n\n```csharp\nusing Microsoft.Teams.AI;\nusing Microsoft.Teams.AI.Messages;\nusing Microsoft.Teams.AI.Models.OpenAI;\nusing Microsoft.Teams.AI.Prompts;\nusing Microsoft.Teams.AI.Templates;\nusing Microsoft.Teams.Api.Activities;\nusing Microsoft.Teams.Apps;\n\n// Simple in-memory store for conversation histories\n// In your application, it may be a good idea to use a more\n// persistent store backed by a database or other storage solution\nprivate static readonly Dictionary<string, List<IMessage>> ConversationStore = new();\n\n/// <summary>\n/// Get or create conversation memory for a specific conversation\n/// </summary>\npublic static List<IMessage> GetOrCreateConversationMemory(string conversationId)\n{\n    if (!ConversationStore.ContainsKey(conversationId))\n    {\n        ConversationStore[conversationId] = new List<IMessage>();\n    }\n\n    return ConversationStore[conversationId];\n}\n\n/// <summary>\n/// Clear memory for a specific conversation\n/// </summary>\npublic static Task ClearConversationMemory(string conversationId)\n{\n    if (ConversationStore.TryGetValue(conversationId, out var messages))\n    {\n        var messageCount = messages.Count;\n        messages.Clear();\n    }\n\n    return Task.CompletedTask;\n}\n```\n\n<!-- usage-example -->\n\n```csharp\n/// <summary>\n/// Example of stateful conversation handler that maintains conversation history\n/// </summary>\npublic static async Task HandleStatefulConversation(OpenAIChatModel model, IContext<MessageActivity> context)\n{\n    // Retrieve existing conversation memory or initialize new one\n    var messages = GetOrCreateConversationMemory(context.Activity.Conversation.Id);\n\n    // Create prompt with conversation-specific memory\n    var prompt = new OpenAIChatPrompt(model, new ChatPromptOptions\n    {\n        Instructions = new StringTemplate(\"You are a helpful assistant that remembers our previous conversation.\")\n    });\n\n    // Send with existing messages as context\n    var options = new IChatPrompt<OpenAI.Chat.ChatCompletionOptions>.RequestOptions\n    {\n        Messages = messages\n    };\n    var result = await prompt.Send(context.Activity.Text, options);\n\n    if (result.Content != null)\n    {\n        var message = new MessageActivity\n        {\n            Text = result.Content,\n        }.AddAIGenerated();\n        await context.Send(message);\n\n        // Update conversation history\n        messages.Add(UserMessage.Text(context.Activity.Text));\n        messages.Add(new ModelMessage<string>(result.Content));\n    }\n    else\n    {\n        await context.Reply(\"I did not generate a response.\");\n    }\n}\n```\n\n<!-- additional-info -->\n\n### Usage in your application\n\n```csharp\nteamsApp.OnMessage(async (context, cancellationToken) =>\n{\n    await HandleStatefulConversation(aiModel, context);\n});\n```\n\n#### How It Works\n\n1. **Conversation Store**: A dictionary maps conversation IDs to their message histories\n2. **Per-Conversation Memory**: Each conversation gets its own isolated message list\n3. **Request Options**: Pass the message history via `RequestOptions.Messages` when calling `Send()`\n4. **Automatic Updates**: After receiving a response, manually add both the user message and AI response to the store\n5. **Persistence**: The conversation history persists across multiple user interactions within the same conversation\n\n:::tip\nThe `ChatPrompt.Send()` method does **not** automatically update the messages you pass in via `RequestOptions`. You must manually add the user message and AI response to your conversation store after each interaction.\n:::\n\n:::note\nIn a production application, consider using a more robust storage solution like Azure Cosmos DB, SQL Server, or Redis instead of an in-memory dictionary. This ensures conversation history persists across application restarts and scales across multiple instances.\n:::\n\n![Stateful Chat Example](/screenshots/stateful-chat-example.png)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/keeping-state/python.incl.md",
    "content": "<!-- state-initialization -->\n\n```python\nfrom microsoft_teams.ai import ChatPrompt, ListMemory, AIModel\nfrom microsoft_teams.openai import OpenAICompletionsAIModel\n\n# Simple in-memory store for conversation histories\n# In your application, it may be a good idea to use a more\n# persistent store backed by a database or other storage solution\nconversation_store: dict[str, ListMemory] = {}\n\n# Initialize AI model\nai_model = OpenAICompletionsAIModel(model=\"gpt-4\")\n\ndef get_or_create_conversation_memory(conversation_id: str) -> ListMemory:\n    \"\"\"Get or create conversation memory for a specific conversation\"\"\"\n    if conversation_id not in conversation_store:\n        conversation_store[conversation_id] = ListMemory()\n    return conversation_store[conversation_id]\n\nasync def clear_conversation_memory(conversation_id: str) -> None:\n    \"\"\"Clear memory for a specific conversation\"\"\"\n    if conversation_id in conversation_store:\n        memory = conversation_store[conversation_id]\n        await memory.set_all([])\n        print(f\"Cleared memory for conversation {conversation_id}\")\n```\n\n<!-- usage-example -->\n\n```python\nfrom microsoft_teams.ai import ChatPrompt, ListMemory, AIModel\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\nasync def handle_stateful_conversation(model: AIModel, ctx: ActivityContext[MessageActivity]) -> None:\n    \"\"\"Example of stateful conversation handler that maintains conversation history\"\"\"\n    print(f\"Received message: {ctx.activity.text}\")\n\n    # Retrieve existing conversation memory or initialize new one\n    memory = get_or_create_conversation_memory(ctx.activity.conversation.id)\n\n    # Get existing messages for logging\n    existing_messages = await memory.get_all()\n    print(f\"Existing messages before sending to prompt: {len(existing_messages)} messages\")\n\n    # Create ChatPrompt with conversation-specific memory\n    chat_prompt = ChatPrompt(model, memory=memory)\n\n    chat_result = await chat_prompt.send(\n        input=ctx.activity.text,\n        instructions=\"You are a helpful assistant that remembers our previous conversation.\"\n    )\n\n    if chat_result.response.content:\n        message = MessageActivityInput(text=chat_result.response.content).add_ai_generated()\n        await ctx.send(message)\n    else:\n        await ctx.reply(\"I did not generate a response.\")\n\n    # Log final message count\n    final_messages = await memory.get_all()\n    print(f\"Messages after sending to prompt: {len(final_messages)} messages\")\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle messages using stateful conversation\"\"\"\n    await handle_stateful_conversation(ai_model, ctx)\n```\n\n<!-- additional-info -->\n\n![Screenshot of chat between user and agent, user first states 'My dinosaur's name is Barnie' and later asks What's my pet's name and the agent responds correctly with 'Barnie'.](/screenshots/stateful-chat-example.png)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/keeping-state/typescript.incl.md",
    "content": "<!-- state-initialization -->\n\n```typescript\nimport { ChatPrompt, IChatModel, Message } from '@microsoft/teams.ai';\nimport { ActivityLike, IMessageActivity, MessageActivity } from '@microsoft/teams.api';\n// ...\n\n// Simple in-memory store for conversation histories\n// In your application, it may be a good idea to use a more\n// persistent store backed by a database or other storage solution\nconst conversationStore = new Map<string, Message[]>();\n\nconst getOrCreateConversationHistory = (conversationId: string) => {\n  // Check if conversation history exists\n  const existingMessages = conversationStore.get(conversationId);\n  if (existingMessages) {\n    return existingMessages;\n  }\n  // If not, create a new conversation history\n  const newMessages: Message[] = [];\n  conversationStore.set(conversationId, newMessages);\n  return newMessages;\n};\n```\n\n<!-- usage-example -->\n\n```typescript\n/**\n * Example of a stateful conversation handler that maintains conversation history\n * using an in-memory store keyed by conversation ID.\n * @param model The chat model to use\n * @param activity The incoming activity\n * @param send Function to send an activity\n */\nexport const handleStatefulConversation = async (\n  model: IChatModel,\n  activity: IMessageActivity,\n  send: (activity: ActivityLike) => Promise<any>,\n  log: ILogger\n) => {\n  log.info('Received message', activity.text);\n\n  // Retrieve existing conversation history or initialize new one\n  const existingMessages = getOrCreateConversationHistory(activity.conversation.id);\n\n  log.info('Existing messages before sending to prompt', existingMessages);\n\n  // Create prompt with existing messages\n  const prompt = new ChatPrompt({\n    instructions: 'You are a helpful assistant.',\n    model,\n    messages: existingMessages, // Pass in existing conversation history\n  });\n\n  const result = await prompt.send(activity.text);\n\n  if (result) {\n    await send(\n      result.content != null\n        ? new MessageActivity(result.content).addAiGenerated()\n        : 'I did not generate a response.'\n    );\n  }\n\n  log.info('Messages after sending to prompt:', existingMessages);\n};\n```\n\n<!-- additional-info -->\n\n![Stateful Chat Example](/screenshots/stateful-chat-example.png)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/mcp/mcp-client/csharp.incl.md",
    "content": "<!-- protocol-name -->\n\nSSE protocol\n\n<!-- install -->\n\nInstall it to your application:\n\n```bash\ndotnet add package Microsoft.Teams.Plugins.External.McpClient --prerelease\n```\n\n<!-- remote-protocol -->\n\nSSE\n\n<!-- server-setup -->\n\na valid SSE\n\n<!-- auth-requirements -->\n\nand keys\n\n<!-- plugin-class -->\n\n`MCPClientPlugin` (from `Microsoft.Teams.Plugins.External.McpClient` package)\n\n<!-- integration-method -->\n\nobject as a plugin\n\n<!-- send-method -->\n\n`send`\n\n<!-- basic-example -->\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n<Tabs>\n  <TabItem label=\"Minimal\" value=\"minimal\">\n    ```csharp\n    using Microsoft.Teams.AI.Models.OpenAI;\n    using Microsoft.Teams.AI.Prompts;\n    using Microsoft.Teams.Api.Activities;\n    using Microsoft.Teams.Apps;\n    using Microsoft.Teams.Apps.Activities;\n    using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n    using Microsoft.Teams.Plugins.External.McpClient;\n\n    WebApplicationBuilder builder = WebApplication.CreateBuilder(args);\n    builder.AddTeams();\n    WebApplication webApp = builder.Build();\n\n    OpenAIChatPrompt prompt = new(\n            new OpenAIChatModel(\n                model: \"gpt-4o\",\n                apiKey: Environment.GetEnvironmentVariable(\"OPENAI_API_KEY\")!),\n                new ChatPromptOptions()\n                    .WithDescription(\"helpful assistant\")\n                    .WithInstructions(\n                        \"You are a helpful assistant that can help answer questions using Microsoft docs.\",\n                        \"You MUST use tool calls to do all your work.\")\n                    );\n    prompt.Plugin(new McpClientPlugin().UseMcpServer(\"https://learn.microsoft.com/api/mcp\"));\n\n    App app = webApp.UseTeams();\n    app.OnMessage(async (context, cancellationToken) =>\n    {\n        await context.Send(new TypingActivity(), cancellationToken);\n        var result = await prompt.Send(context.Activity.Text);\n        await context.Send(result.Content, cancellationToken);\n    });\n    webApp.Run();\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- multiple-servers -->\n\nIn this example, we augment the `ChatPrompt` with a remote MCP Server.\n\n<!-- mcp-server-note -->\n\n:::note\nYou can quickly set up an MCP server using [Azure Functions](https://techcommunity.microsoft.com/blog/appsonazureblog/build-ai-agent-tools-using-remote-mcp-with-azure-functions/4401059).\n:::\n\n<!-- custom-headers -->\n\n### Custom Headers\n\nSome MCP servers may require custom headers to be sent as part of the request. You can customize the headers when calling the `UseMcpServer` function:\n\n```csharp\nnew McpClientPlugin()\n    .UseMcpServer(\"https://learn.microsoft.com/api/mcp\",\n        new McpClientPluginParams()\n        {\n               HeadersFactory = () => new Dictionary<string, string>()\n               { { \"HEADER_KEY\", \"HEADER_VALUE\" } }\n        }\n    );\n```\n\n<!-- example-gif -->\n\n![Animated image of user typing a prompt ('Tell me about Charizard') to DevTools Chat window and multiple paragraphs of information being returned.](/screenshots/mcp-client-pokemon.gif)\n\n<!-- pokemon-example -->\n\nIn this example, our MCP server is a Pokemon API and our client knows how to call it. The LLM is able to call the `getPokemon` function exposed by the server and return the result back to the user.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/mcp/mcp-client/python.incl.md",
    "content": "<!-- protocol-name -->\n\nSSE protocol\n\n<!-- install -->\n\nInstall it to your application:\n\n```bash\npip install microsoft-teams-mcpplugin\n```\n\n<!-- remote-protocol -->\n\nStreamableHttp/SSE\n\n<!-- server-setup -->\n\na valid SSE\n\n<!-- auth-requirements -->\n\nany keys\n\n<!-- plugin-class -->\n\n`McpClientPlugin`\n\n<!-- integration-method -->\n\nas a plugin\n\n<!-- send-method -->\n\n`send`\n\n<!-- basic-example -->\n\n```python\nfrom microsoft_teams.ai import ChatPrompt\nfrom microsoft_teams.mcpplugin import McpClientPlugin\nfrom microsoft_teams.openai import OpenAICompletionsAIModel\n# ...\n\n# Set up AI model\ncompletions_model = OpenAICompletionsAIModel(model=\"gpt-4\")\n\n# Configure MCP Client Plugin with multiple remote servers\nmcp_plugin = McpClientPlugin()\n\n# Add multiple MCP servers\nmcp_plugin.use_mcp_server(\"https://learn.microsoft.com/api/mcp\")\nmcp_plugin.use_mcp_server(\"https://example.com/mcp/weather\")\nmcp_plugin.use_mcp_server(\"https://example.com/mcp/pokemon\")\n\n# ChatPrompt with MCP tools\nchat_prompt = ChatPrompt(\n    completions_model,\n    plugins=[mcp_plugin]\n)\n```\n\n<!-- multiple-servers -->\n\nIn this example, we augment the `ChatPrompt` with multiple remote MCP Servers.\n\n## Using MCP Client in Message Handlers\n\n```python\nfrom microsoft_teams.ai import ChatPrompt\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle messages using ChatPrompt with MCP tools\"\"\"\n\n    result = await chat_prompt.send(\n        input=ctx.activity.text,\n        instructions=\"You are a helpful assistant with access to remote MCP tools.\"\n    )\n\n    if result.response.content:\n        message = MessageActivityInput(text=result.response.content).add_ai_generated()\n        await ctx.send(message)\n```\n\n<!-- mcp-server-note -->\n\n:::note\nFeel free to build an MCP Server in a different agent using the [MCP Server Guide](./mcp-server). Or you can quickly set up an MCP server using [Azure Functions](https://techcommunity.microsoft.com/blog/appsonazureblog/build-ai-agent-tools-using-remote-mcp-with-azure-functions/4401059).\n:::\n\n<!-- custom-headers -->\n\n### Customize Headers\n\nSome MCP servers may require custom headers to be sent as part of the request. You can customize the headers when calling the `use_mcp_server` function:\n\n```python\nfrom os import getenv\nfrom microsoft_teams.mcpplugin import McpClientPlugin, McpClientPluginParams\n# ...\n\n# Example with Bearer token authentication\nGITHUB_PAT = getenv(\"GITHUB_PAT\")\n\nif GITHUB_PAT:\n    mcp_plugin.use_mcp_server(\n        \"https://api.githubcopilot.com/mcp/\",\n        McpClientPluginParams(headers={\n            \"Authorization\": f\"Bearer {GITHUB_PAT}\",\n        })\n    )\n\n# Example with API key\nmcp_plugin.use_mcp_server(\n    \"https://example.com/api/mcp\",\n    McpClientPluginParams(headers={\n        \"X-API-Key\": getenv('API_KEY'),\n        \"Custom-Header\": \"custom-value\"\n    })\n)\n```\n\n<!-- example-gif -->\n\n![Animated image of user typing a prompt ('Tell me about Charizard') to DevTools Chat window and multiple paragraphs of information being returned.](/screenshots/mcp-client-pokemon.gif)\n\n<!-- pokemon-example -->\n\nIn this example, our MCP server is a Pokemon API and our client knows how to call it. The LLM is able to call the `getPokemon` function exposed by the server and return the result back to the user.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/mcp/mcp-client/typescript.incl.md",
    "content": "<!-- protocol-name -->\n\nStreamable HTTP protocol\n\n<!-- install -->\n\nInstall it to your application:\n\n```bash\nnpm install @microsoft/teams.mcpclient\n```\n\n<!-- remote-protocol -->\n\nStreamable HTTP/SSE\n\n<!-- server-setup -->\n\nvalid remote\n\n<!-- auth-requirements -->\n\nand keys\n\n<!-- plugin-class -->\n\n`MCPClientPlugin` (from `@microsoft/teams.mcpclient` package)\n\n<!-- integration-method -->\n\nobject as a plugin\n\n<!-- send-method -->\n\n`send`\n\n<!-- basic-example -->\n\n```typescript\nimport { ChatPrompt } from '@microsoft/teams.ai';\nimport { App } from '@microsoft/teams.apps';\nimport { ConsoleLogger } from '@microsoft/teams.common';\nimport { McpClientPlugin } from '@microsoft/teams.mcpclient';\nimport { OpenAIChatModel } from '@microsoft/teams.openai';\n// ...\n\nconst logger = new ConsoleLogger('mcp-client', { level: 'debug' });\nconst prompt = new ChatPrompt(\n  {\n    instructions: 'You are a helpful assistant. You MUST use tool calls to do all your work.',\n    model: new OpenAIChatModel({\n      model: 'gpt-4o-mini',\n      apiKey: process.env.OPENAI_API_KEY,\n    }),\n  },\n  [new McpClientPlugin({ logger })]\n).usePlugin('mcpClient', {\n  url: 'https://learn.microsoft.com/api/mcp',\n});\n\nconst app = new App();\n\napp.on('message', async ({ send, activity }) => {\n  await send({ type: 'typing' });\n\n  const result = await prompt.send(activity.text);\n  if (result.content) {\n    await send(result.content);\n  }\n});\napp.start().catch(console.error);\n```\n\n<!-- multiple-servers -->\n\nIn this example, we augment the `ChatPrompt` with a few remote MCP Servers.\n\n<!-- custom-headers -->\n\n### Customize Headers\n\nSome MCP servers may require custom headers to be sent as part of the request. You can customize the headers when calling the `usePlugin` function:\n\n```typescript\nimport { ChatPrompt } from '@microsoft/teams.ai';\nimport { McpClientPlugin } from '@microsoft/teams.mcpclient';\n// ...\n\n.usePlugin('mcpClient', {\n    url: 'https://<your-mcp-server>/mcp'\n    params: {\n      headers: {\n        'x-header-functions-key': '<custom-headers>',\n      }\n    }\n});\n```\n\n<!-- mcp-server-note -->\n\n:::note\nFeel free to build an MCP Server in a different agent using the [MCP Server Guide](./mcp-server). Or you can quickly set up an MCP server using [Azure Functions](https://techcommunity.microsoft.com/blog/appsonazureblog/build-ai-agent-tools-using-remote-mcp-with-azure-functions/4401059).\n:::\n\n<!-- example-gif -->\n\n![Animated image of user typing a prompt ('Tell me about Charizard') to DevTools Chat window and multiple paragraphs of information being returned.](/screenshots/mcp-client-pokemon.gif)\n\n<!-- pokemon-example -->\n\nIn this example, our MCP server is a Pokemon API and our client knows how to call it. The LLM is able to call the `getPokemon` function exposed by the server and return the result back to the user.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/mcp/mcp-server/csharp.incl.md",
    "content": "<!-- intro -->\n\nWIP\n\n<!-- install -->\n\n<!-- configuration -->\n\n<!-- path-note -->\n\n<!-- app-integration -->\n\n<!-- devtools-tip -->\n\n<!-- devtools-gif -->\n\n<!-- proactive-messaging -->\n\n<!-- message-handler -->\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/mcp/mcp-server/python.incl.md",
    "content": "<!-- intro -->\n\nYou are able to convert any `App` into an MCP server by using the `McpPlugin` from the `microsoft-teams-mcp` package. This plugin adds the necessary endpoints to your application to serve as an MCP server. The plugin allows you to define tools, resources, and prompts that can be exposed to other MCP applications.\n\n<!-- install -->\n\nInstall it to your application:\n\n```bash\npip install microsoft-teams-mcpplugin\n```\n\n<!-- configuration -->\n\nYour plugin can be configured as follows:\n\n```python\nfrom microsoft_teams.ai import Function\nfrom microsoft_teams.mcpplugin import McpServerPlugin\nfrom pydantic import BaseModel\n# ...\n\n# Configure MCP server with custom name\nmcp_server_plugin = McpServerPlugin(\n    name=\"test-mcp\",\n)\n\nclass EchoParams(BaseModel):\n    input: str\n\nasync def echo_handler(params: EchoParams) -> str:\n    return f\"You said {params.input}\"\n\n# Register the echo tool\nmcp_server_plugin.use_tool(\n    Function(\n        name=\"echo\",\n        description=\"echo back whatever you said\",\n        parameter_schema=EchoParams,\n        handler=echo_handler,\n    )\n)\n```\n\n<!-- path-note -->\n\n:::note\nBy default, the MCP server will be available at `/mcp` on your application. You can change this by setting the `path` property in the plugin configuration.\n:::\n\n<!-- app-integration -->\n\nAnd included in the app like any other plugin:\n\n```python\nfrom microsoft_teams.apps import App\nfrom microsoft_teams.devtools import DevToolsPlugin\n# ...\n\napp = App(plugins=[mcp_server_plugin, DevToolsPlugin()])\n```\n\n<!-- devtools-tip -->\n\n:::tip\nYou may use the [MCP-Inspector](https://modelcontextprotocol.io/legacy/tools/inspector) to test functionality with your server.\n:::\n\n<!-- devtools-gif -->\n\n![MCP Server in Devtools](/screenshots/mcp-inspector.gif)\n\n<!-- proactive-messaging -->\n\n**Alert Tool for Proactive Messaging:**\n\n```python\nfrom typing import Dict\nfrom microsoft_teams.ai import Function\nfrom microsoft_teams.mcpplugin import McpServerPlugin\nfrom pydantic import BaseModel\n# ...\n\n# Storage for conversation IDs (for proactive messaging)\nconversation_storage: Dict[str, str] = {}\n\nclass AlertParams(BaseModel):\n    user_id: str\n    message: str\n\nasync def alert_handler(params: AlertParams) -> str:\n    \"\"\"\n    Send proactive message to user via Teams.\n    This demonstrates the \"piping messages to user\" feature.\n    \"\"\"\n    # 1. Validate if the incoming request is allowed to send messages\n    if not params.user_id or not params.message:\n        return \"Invalid parameters: user_id and message are required\"\n\n    # 2. Fetch the correct conversation ID for the given user\n    conversation_id = conversation_storage.get(params.user_id)\n    if not conversation_id:\n        return f\"No conversation found for user {params.user_id}. User needs to message the bot first.\"\n\n    # 3. Send proactive message to the user\n    await app.send(conversation_id=conversation_id, activity=params.message)\n    return f\"Alert sent to user {params.user_id}: {params.message}\"\n\n# Register the alert tool\nmcp_server_plugin.use_tool(\n    Function(\n        name=\"alert\",\n        description=\"Send proactive message to a Teams user\",\n        parameter_schema=AlertParams,\n        handler=alert_handler,\n    )\n)\n```\n\n<!-- message-handler -->\n\n**Store Conversation IDs in Message Handler:**\n\n```python\nfrom microsoft_teams.api import MessageActivity\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"\n    Handle incoming messages and store conversation IDs for proactive messaging.\n    \"\"\"\n    # Store conversation ID for this user (for proactive messaging)\n    user_id = ctx.activity.from_.id\n    conversation_id = ctx.activity.conversation.id\n    conversation_storage[user_id] = conversation_id\n\n    # Echo back the message with info about stored conversation\n    await ctx.reply(\n        f\"You said: {ctx.activity.text}\\n\\n\"\n        f\"📝 Stored conversation ID `{conversation_id}` for user `{user_id}` \"\n        f\"(for proactive messaging via MCP alert tool)\"\n    )\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/mcp/mcp-server/typescript.incl.md",
    "content": "<!-- intro -->\n\nYou are able to convert any `App` into an MCP server by using the `McpPlugin`. This plugin adds the necessary endpoints to your application to serve as an MCP server. The plugin allows you to define tools, resources, and prompts that can be exposed to other MCP applications.\n\n<!-- install -->\n\nInstall it to your application:\n\n```bash\nnpm install @microsoft/teams.mcp\n```\n\n<!-- configuration -->\n\nYour plugin can be configured as follows:\n\n```typescript\nimport { z } from 'zod';\nimport { App } from '@microsoft/teams.apps';\nimport { McpPlugin } from '@microsoft/teams.mcp';\n// ...\n\nconst mcpServerPlugin = new McpPlugin({\n  // Describe the MCP server with a helpful name and description\n  // for MCP clients to discover and use it.\n  name: 'test-mcp',\n  description: 'Allows you to test the mcp server',\n  // Optionally, you can provide a URL to the mcp dev-tools\n  // during development\n  inspector: 'http://localhost:5173?proxyPort=9000',\n}).tool(\n  // Describe the tools with helpful names and descriptions\n  'echo',\n  'echos back whatever you said',\n  {\n    input: z.string().describe('the text to echo back'),\n  },\n  {\n    readOnlyHint: true,\n    idempotentHint: true,\n  },\n  async ({ input }) => {\n    return {\n      content: [\n        {\n          type: 'text',\n          text: `you said \"${input}\"`,\n        },\n      ],\n    };\n  }\n);\n```\n\n<!-- path-note -->\n\n:::note\nBy default, the MCP server will be available at `/mcp` on your application. You can change this by setting the `transport.path` property in the plugin configuration.\n:::\n\n<!-- app-integration -->\n\nAnd included in the app like any other plugin:\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { DevtoolsPlugin } from '@microsoft/teams.dev';\nimport { McpPlugin } from '@microsoft/teams.mcp';\n// ...\n\nconst app = new App({\n  plugins: [\n    new DevtoolsPlugin(),\n    // Add this plugin\n    mcpServerPlugin,\n  ],\n});\n```\n\n<!-- devtools-tip -->\n\n:::tip\nEnabling mcp request inspection and the `DevtoolsPlugin` allows you to see all the requests and responses to and from your MCP server (similar to how the **Activities** tab works).\n:::\n\n<!-- devtools-gif -->\n\n![MCP Server in Devtools](/screenshots/mcp-devtools.gif)\n\n<!-- proactive-messaging -->\n\n```typescript\nimport { z } from 'zod';\nimport { App } from '@microsoft/teams.apps';\nimport { McpPlugin } from '@microsoft/teams.mcp';\n// ...\n\n// Keep a store of the user to the conversation id\n// In a production app, you probably would want to use a\n// persistent store like a database\nconst userToConversationId = new Map<string, string>();\n\n// Add a an MCP server tool\nmcpServerPlugin.tool(\n  'alertUser',\n  'alerts the user about something important',\n  {\n    input: z.string().describe('the text to echo back'),\n    userAadObjectId: z.string().describe('the user to alert'),\n  },\n  {\n    readOnlyHint: true,\n    idempotentHint: true,\n  },\n  async ({ input, userAadObjectId }, { authInfo }) => {\n    if (!isAuthValid(authInfo)) {\n      throw new Error('Not allowed to call this tool');\n    }\n\n    const conversationId = userToConversationId.get(userAadObjectId);\n    if (!conversationId) {\n      console.log('Current conversation map', userToConversationId);\n      return {\n        content: [\n          {\n            type: 'text',\n            text: `user ${userAadObjectId} is not in a conversation`,\n          },\n        ],\n      };\n    }\n\n    // Leverage the app's proactive messaging capabilities to send a mesage to\n    // correct conversation id.\n    await app.send(conversationId, `Notification: ${input}`);\n    return {\n      content: [\n        {\n          type: 'text',\n          text: 'User was notified',\n        },\n      ],\n    };\n  }\n);\n```\n\n<!-- message-handler -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message', async ({ send, activity }) => {\n  await send({ type: 'typing' });\n  await send(`you said \"${activity.text}\"`);\n  if (activity.from.aadObjectId && !userToConversationId.has(activity.from.aadObjectId)) {\n    userToConversationId.set(activity.from.aadObjectId, activity.conversation.id);\n    app.log.info(\n      `Just added user ${activity.from.aadObjectId} to conversation ${activity.conversation.id}`\n    );\n  }\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/python.incl.md",
    "content": "<!-- package-name -->\n\n`microsoft-teams-ai` package\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/csharp.incl.md",
    "content": "<!-- package-install -->\n\n**NuGet Package** - Install the Microsoft Teams SDK:\n\n```bash\ndotnet add package Microsoft.Teams.AI\n```\n\n<!-- config-method -->\n\nYou should include your keys securely using `appsettings.json` or environment variables\n\n<!-- project-structure -->\n\nN/A\n\n<!-- azure-openai-config -->\n\nOnce you have deployed a model, configure your application using `appsettings.json` or `appsettings.Development.json`:\n\n**appsettings.Development.json**\n\n```json\n{\n  \"AzureOpenAIKey\": \"your-azure-openai-api-key\",\n  \"AzureOpenAIModel\": \"your-azure-openai-model-deployment-name\",\n  \"AzureOpenAIEndpoint\": \"https://your-resource.openai.azure.com/\"\n}\n```\n\n**Using configuration in your code:**\n\n```csharp\nvar azureOpenAIModel = configuration[\"AzureOpenAIModel\"] ??\n    throw new InvalidOperationException(\"AzureOpenAIModel not configured\");\nvar azureOpenAIEndpoint = configuration[\"AzureOpenAIEndpoint\"] ??\n    throw new InvalidOperationException(\"AzureOpenAIEndpoint not configured\");\nvar azureOpenAIKey = configuration[\"AzureOpenAIKey\"] ??\n    throw new InvalidOperationException(\"AzureOpenAIKey not configured\");\n\nvar azureOpenAI = new AzureOpenAIClient(\n    new Uri(azureOpenAIEndpoint),\n    new ApiKeyCredential(azureOpenAIKey)\n);\n\nvar aiModel = new OpenAIChatModel(azureOpenAIModel, azureOpenAI);\n```\n\n:::tip\nUse `appsettings.Development.json` for local development and keep it in `.gitignore`. For production, use environment variables or Azure Key Vault.\n:::\n\n<!-- azure-openai-info -->\n\n:::info\nThe Azure OpenAI SDK handles API versioning automatically. You don't need to specify an API version manually.\n:::\n\n<!-- openai-config -->\n\nOnce you have your API key, configure your application:\n\n**appsettings.Development.json**\n\n```json\n{\n  \"OpenAIKey\": \"sk-your-openai-api-key\",\n  \"OpenAIModel\": \"gpt-4o\"\n}\n```\n\n**Using configuration in your code:**\n\n```csharp\nvar openAIKey = configuration[\"OpenAIKey\"] ??\n    throw new InvalidOperationException(\"OpenAIKey not configured\");\nvar openAIModel = configuration[\"OpenAIModel\"] ?? \"gpt-4o\";\n\nvar aiModel = new OpenAIChatModel(openAIModel, openAIKey);\n```\n\n:::tip\nUse `appsettings.Development.json` for local development and keep it in `.gitignore`. For production, use environment variables or Azure Key Vault.\n:::\n\n<!-- additional-notes -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/python.incl.md",
    "content": "<!-- package-install -->\n\nN/A\n\n<!-- config-method -->\n\nWe recommend putting it in an .env file at the root level of your project\n\n<!-- project-structure -->\n\n```\nmy-app/\n|── appPackage/       # Teams app package files\n├── src/\n│   └── main.py      # Main application code\n|── .env              # Environment variables\n```\n\n<!-- azure-openai-config -->\n\nOnce you have deployed a model, include the following key/values in your `.env` file:\n\n```env\nAZURE_OPENAI_API_KEY=your-azure-openai-api-key\nAZURE_OPENAI_MODEL=your-azure-openai-model-deployment-name\nAZURE_OPENAI_ENDPOINT=your-azure-openai-endpoint\nAZURE_OPENAI_API_VERSION=your-azure-openai-api-version\n```\n\n<!-- azure-openai-info -->\n\n:::info\nThe `AZURE_OPENAI_API_VERSION` is different from the model version. This is a common point of confusion. Look for the API Version [here](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference?WT.mc_id=AZ-MVP-5004796 'Azure OpenAI API Reference')\n:::\n\n<!-- openai-config -->\n\nOnce you have your API key, include the following key/values in your `.env` file:\n\n```env\nOPENAI_API_KEY=sk-your-openai-api-key\nOPENAI_MODEL=gpt-4  # Optional: defaults to gpt-4o if not specified\n```\n\n<!-- additional-notes -->\n\n:::note\n**Automatic Environment Variable Loading**: The AI models automatically read these environment variables when initialized. You can also pass these values explicitly as constructor parameters if needed for advanced configurations.\n\n```python\n# Automatic (recommended)\nmodel = OpenAICompletionsAIModel(model=\"your-model-name\")\n\n# Explicit (for advanced use cases)\nmodel = OpenAICompletionsAIModel(\n    key=\"your-api-key\",\n    model=\"your-model-name\",\n    azure_endpoint=\"your-endpoint\",  # Azure only\n    api_version=\"your-api-version\"   # Azure only\n)\n```\n\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/setup-and-prereqs/typescript.incl.md",
    "content": "<!-- package-install -->\n\nInstall the required AI packages to your application:\n\n```bash\nnpm install @microsoft/teams.apps @microsoft/teams.ai @microsoft/teams.openai\n```\n\nFor development, you may also want to install the DevTools plugin:\n\n```bash\nnpm install @microsoft/teams.dev --save-dev\n```\n\n<!-- config-method -->\n\nWe recommend putting it in an .env file at the root level of your project\n\n<!-- project-structure -->\n\n```\nmy-app/\n|── appPackage/       # Teams app package files\n├── src/\n│   └── index.ts      # Main application code\n|── .env              # Environment variables\n```\n\n<!-- azure-openai-config -->\n\nOnce you have deployed a model, include the following key/values in your `.env` file:\n\n```env\nAZURE_OPENAI_API_KEY=your-azure-openai-api-key\nAZURE_OPENAI_MODEL_DEPLOYMENT_NAME=your-azure-openai-model\nAZURE_OPENAI_ENDPOINT=your-azure-openai-endpoint\nAZURE_OPENAI_API_VERSION=your-azure-openai-api-version\n```\n\n<!-- azure-openai-info -->\n\n:::info\nThe `AZURE_OPENAI_API_VERSION` is different from the model version. This is a common point of confusion. Look for the API Version [here](https://learn.microsoft.com/en-us/azure/ai-services/openai/reference?WT.mc_id=AZ-MVP-5004796 'Azure OpenAI API Reference')\n:::\n\n<!-- openai-config -->\n\nOnce you have your API key, include the following key/values in your `.env` file:\n\n```env\nOPENAI_API_KEY=sk-your-openai-api-key\n```\n\n<!-- additional-notes -->\n\n:::note\n**Automatic Environment Variable Loading**: The OpenAI model automatically reads environment variables when options are not explicitly provided. You can pass values explicitly as constructor parameters if needed for advanced configurations.\n\n```typescript\n// Automatic (recommended) - uses environment variables\nconst model = new OpenAIChatModel({\n  model: 'gpt-4o',\n});\n\n// Explicit (for advanced use cases)\nconst model = new OpenAIChatModel({\n  apiKey: 'your-api-key',\n  model: 'gpt-4o',\n  endpoint: 'your-endpoint',      // Azure only\n  apiVersion: 'your-api-version', // Azure only\n  baseUrl: 'your-base-url',       // Custom base URL\n  organization: 'your-org-id',    // Optional\n  project: 'your-project-id',     // Optional\n});\n```\n\n**Environment variables automatically loaded:**\n- `OPENAI_API_KEY` or `AZURE_OPENAI_API_KEY`\n- `AZURE_OPENAI_ENDPOINT` (Azure only)\n- `OPENAI_API_VERSION` (Azure only)\n\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/ai/typescript.incl.md",
    "content": "<!-- package-name -->\n\n`@microsoft/teams.ai` package\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/csharp.incl.md",
    "content": "<!-- intro -->\n\nThis documentation covers advanced features and capabilities of the Teams SDK in C#.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/creating-dialogs/csharp.incl.md",
    "content": "<!-- entry-point-intro -->\n\nTo open a dialog, you need to supply a special type of action to the Adaptive Card. The `TaskFetchAction` is specifically designed for this purpose - it automatically sets up the proper Teams data structure to trigger a dialog. Once this button is clicked, the dialog will open and ask the application what to show.\n\n<!-- entry-point-code -->\n\n```csharp\nusing Microsoft.Teams.Api.Activities;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Cards;\nusing Microsoft.Teams.Common.Logging;\n\n//...\n\n[Message]\npublic async Task OnMessage([Context] MessageActivity activity, [Context] IContext.Client client, [Context] ILogger log)\n{\n    // Create the launcher adaptive card\n    var card = CreateDialogLauncherCard();\n    await client.Send(card);\n}\n\nprivate static AdaptiveCard CreateDialogLauncherCard()\n{\n    var card = new AdaptiveCard\n    {\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Select the examples you want to see!\")\n            {\n                Size = TextSize.Large,\n                Weight = TextWeight.Bolder\n            }\n        },\n        Actions = new List<Action>\n        {\n            new TaskFetchAction(new { opendialogtype = \"simple_form\" })\n            {\n                Title = \"Simple form test\"\n            },\n            new TaskFetchAction(new { opendialogtype = \"webpage_dialog\" })\n            {\n                Title = \"Webpage Dialog\"\n            },\n            new TaskFetchAction(new { opendialogtype = \"multi_step_form\" })\n            {\n                Title = \"Multi-step Form\"\n            }\n        }\n    };\n\n    return card;\n}\n```\n\n<!-- dialog-open-intro -->\n\nOnce an action is executed to open a dialog, the Teams client will send an event to the agent to request what the content of the dialog should be. When using `TaskFetchAction`, the data is nested inside an `MsTeams` property structure.\n\n<!-- dialog-open-code -->\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities.Invokes;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Common.Logging;\n\n//...\n\n[TaskFetch]\npublic Microsoft.Teams.Api.TaskModules.Response OnTaskFetch([Context] Tasks.FetchActivity activity, [Context] IContext.Client client, [Context] ILogger log)\n{\n    var data = activity.Value?.Data as JsonElement?;\n    if (data == null)\n    {\n        log.Info(\"[TASK_FETCH] No data found in the activity value\");\n        return new Microsoft.Teams.Api.TaskModules.Response(\n            new Microsoft.Teams.Api.TaskModules.MessageTask(\"No data found in the activity value\"));\n    }\n\n    var dialogType = data.Value.TryGetProperty(\"opendialogtype\", out var dialogTypeElement) && dialogTypeElement.ValueKind == JsonValueKind.String\n        ? dialogTypeElement.GetString()\n        : null;\n\n    log.Info($\"[TASK_FETCH] Dialog type: {dialogType}\");\n\n    return dialogType switch\n    {\n        \"simple_form\" => CreateSimpleFormDialog(),\n        \"webpage_dialog\" => CreateWebpageDialog(_configuration, log),\n        \"multi_step_form\" => CreateMultiStepFormDialog(),\n        \"mixed_example\" => CreateMixedExampleDialog(),\n        _ => new Microsoft.Teams.Api.TaskModules.Response(\n            new Microsoft.Teams.Api.TaskModules.MessageTask(\"Unknown dialog type\"))\n    };\n}\n```\n\n<!-- rendering-card-code -->\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Cards;\n\n//...\n\nprivate static Microsoft.Teams.Api.TaskModules.Response CreateSimpleFormDialog()\n{\n    var choices = new List<Choice>\n    {\n        new Choice { Title = \"Option 1\", Value = \"opt1\" },\n        new Choice { Title = \"Option 2\", Value = \"opt2\" },\n        new Choice { Title = \"Option 3\", Value = \"opt3\" }\n    };\n\n    var dialogCard = new AdaptiveCard\n    {\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"This is a simple form\")\n            {\n                Size = TextSize.Large,\n                Weight = TextWeight.Bolder\n            },\n            new TextInput\n            {\n                Id = \"name\",\n                Label = \"Name\",\n                Placeholder = \"Enter your name\",\n                IsRequired = true\n            },\n            new ChoiceSetInput\n            {\n                Id = \"preference\",\n                Label = \"Select your preference\",\n                Choices = choices,\n                Style = StyleEnum.Compact\n            }\n        },\n        Actions = new List<Action>\n        {\n            new SubmitAction\n            {\n                Title = \"Submit\",\n                Data = new { submissiondialogtype = \"simple_form\" }\n            }\n        }\n    };\n\n    var taskInfo = new TaskInfo\n    {\n        Title = \"Simple Form Dialog\",\n        Card = new Attachment\n        {\n            ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n            Content = dialogCard\n        }\n    };\n\n    return new Microsoft.Teams.Api.TaskModules.Response(\n        new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo));\n}\n```\n\n<!-- rendering-webpage-code -->\n\n```csharp\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Common;\n\n//...\n\nprivate static Microsoft.Teams.Api.TaskModules.Response CreateWebpageDialog(IConfiguration configuration, ILogger log)\n{\n    var botEndpoint = configuration[\"BotEndpoint\"];\n    if (string.IsNullOrEmpty(botEndpoint))\n    {\n        log.Warn(\"No remote endpoint detected. Using webpages for dialog will not work as expected\");\n        botEndpoint = \"http://localhost:3978\"; // Fallback for local development\n    }\n    else\n    {\n        log.Info($\"Using BotEndpoint: {botEndpoint}/tabs/dialog-form\");\n    }\n\n    var taskInfo = new TaskInfo\n    {\n        Title = \"Webpage Dialog\",\n        Width = new Union<int, Size>(1000),\n        Height = new Union<int, Size>(800),\n        // Here we are using a webpage that is hosted in the same\n        // server as the agent. This server needs to be publicly accessible,\n        // needs to set up teams.js client library (https://www.npmjs.com/package/@microsoft/teams-js)\n        // and needs to be registered in the manifest.\n        Url = $\"{botEndpoint}/tabs/dialog-form\"\n    };\n\n    return new Microsoft.Teams.Api.TaskModules.Response(\n        new Microsoft.Teams.Api.TaskModules.ContinueTask(taskInfo));\n}\n```\n\n<!-- embedded-web-content -->\n\n### Setting up Embedded Web Content\n\nTo serve web content for dialogs, you can use the `AddTab` functionality to embed HTML files as resources:\n\n```csharp\n// In Program.cs when building your app\napp.UseTeams();\napp.AddTab(\"dialog-form\", \"Web/dialog-form\");\n\n// Configure project file to embed web resources\n// In .csproj:\n// <GenerateEmbeddedFilesManifest>true</GenerateEmbeddedFilesManifest>\n// <EmbeddedResource Include=\"Web/**\" />\n// <Content Remove=\"Web/**\" />\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/creating-dialogs/python.incl.md",
    "content": "<!-- entry-point-intro -->\n\nTo open a dialog, you need to supply a special type of action as to the Adaptive Card. Once this button is clicked, the dialog will open and ask the application what to show.\n\n<!-- entry-point-code -->\n\n```python\nfrom microsoft_teams.api import MessageActivity, MessageActivityInput, TypingActivityInput\nfrom microsoft_teams.apps import ActivityContext\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock, TaskFetchAction\n# ...\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    await ctx.reply(TypingActivityInput())\n\n    card = AdaptiveCard(\n        schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n        body=[\n            TextBlock(\n                text=\"Select the examples you want to see!\",\n                size=\"Large\",\n                weight=\"Bolder\",\n            )\n        ]\n    ).with_actions([\n        # Special type of action to open a dialog\n        TaskFetchAction(value={\"OpenDialogType\": \"webpage_dialog\"}).with_title(\"Webpage Dialog\"),\n        # This data will be passed back in an event, so we can handle what to show in the dialog\n        TaskFetchAction(value={\"OpenDialogType\": \"multi_step_form\"}).with_title(\"Multi-step Form\"),\n        TaskFetchAction(value={\"OpenDialogType\": \"mixed_example\"}).with_title(\"Mixed Example\")\n    ])\n    # Send the card as an attachment\n    message = MessageActivityInput(text=\"Enter this form\").add_card(card)\n    await ctx.send(message)\n```\n\n<!-- dialog-open-intro -->\n\nOnce an action is executed to open a dialog, the Teams client will send an event to the agent to request what the content of the dialog should be. Here is how to handle this event:\n\n<!-- dialog-open-code -->\n\n```python\n@app.on_dialog_open\nasync def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):\n    \"\"\"Handle dialog open events for all dialog types.\"\"\"\n    card = AdaptiveCard(...)\n\n    # Return an object with the task value that renders a card\n    return InvokeResponse(\n                body=TaskModuleResponse(\n                    task=TaskModuleContinueResponse(\n                        value=CardTaskModuleTaskInfo(\n                            title=\"Title of Dialog\",\n                            card=card_attachment(AdaptiveCardAttachment(content=card)),\n                        )\n                    )\n                )\n            )\n```\n\n<!-- rendering-card-code -->\n\n```python\nfrom microsoft_teams.api import AdaptiveCardAttachment, TaskFetchInvokeActivity, InvokeResponse, card_attachment\nfrom microsoft_teams.api import CardTaskModuleTaskInfo, TaskModuleContinueResponse, TaskModuleResponse\nfrom microsoft_teams.apps import ActivityContext\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock, TextInput, SubmitAction, SubmitActionData\n# ...\n\n@app.on_dialog_open\nasync def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):\n    \"\"\"Handle dialog open events for all dialog types.\"\"\"\n    # Return an object with the task value that renders a card\n    dialog_card = AdaptiveCard(\n        schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n        body=[\n            TextBlock(text=\"This is a simple form\", size=\"Large\", weight=\"Bolder\"),\n            TextInput().with_label(\"Name\").with_is_required(True).with_id(\"name\").with_placeholder(\"Enter your name\"),\n        ],\n        actions=[\n            SubmitAction().with_title(\"Submit\").with_data(SubmitActionData(ms_teams={\"SubmissionDialogType\": \"simple_form\"}))\n        ]\n    )\n\n\n    # Return an object with the task value that renders a card\n    return InvokeResponse(\n                body=TaskModuleResponse(\n                    task=TaskModuleContinueResponse(\n                        value=CardTaskModuleTaskInfo(\n                            title=\"Simple Form Dialog\",\n                            card=card_attachment(AdaptiveCardAttachment(content=dialog_card)),\n                        )\n                    )\n                )\n            )\n```\n\n<!-- rendering-webpage-code -->\n\n```python\nimport os\nfrom microsoft_teams.api import InvokeResponse, TaskModuleContinueResponse, TaskModuleResponse, UrlTaskModuleTaskInfo\n# ...\n\nreturn InvokeResponse(\n                body=TaskModuleResponse(\n                    task=TaskModuleContinueResponse(\n                        value=UrlTaskModuleTaskInfo(\n                            title=\"Webpage Dialog\",\n                            # Here we are using a webpage that is hosted in the same\n                            # server as the agent. This server needs to be publicly accessible,\n                            # needs to set up teams.js client library (https://www.npmjs.com/package/@microsoft/teams-js)\n                            # and needs to be registered in the manifest.\n                            url=f\"{os.getenv('BOT_ENDPOINT')}/tabs/dialog-webpage\",\n                            width=1000,\n                            height=800,\n                        )\n                    )\n                )\n            )\n```\n\n<!-- embedded-web-content -->\n\n### Setting up Embedded Web Content\n\nTo serve web content for dialogs, you can use the `page` method to host static webpages:\n\n```python\nimport os\n\n# In your app setup (e.g., main.py)\n# Hosts a static webpage at /tabs/dialog-form\napp.page(\"customform\", os.path.join(os.path.dirname(__file__), \"views\", \"customform\"), \"/tabs/dialog-form\")\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/creating-dialogs/typescript.incl.md",
    "content": "<!-- entry-point-intro -->\n\nTo open a dialog, you need to supply a special type of action as to the Adaptive Card. Once this button is clicked, the dialog will open and ask the application what to show.\n\n<!-- entry-point-code -->\n\n```typescript\nimport { cardAttachment, MessageActivity } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\nimport {\n  AdaptiveCard,\n  IAdaptiveCard,\n  TaskFetchAction,\n  TaskFetchData,\n} from '@microsoft/teams.cards';\n// ...\n\napp.on('message', async ({ send }) => {\n  await send({ type: 'typing' });\n\n  // Create the launcher adaptive card\n  const card: IAdaptiveCard = new AdaptiveCard({\n    type: 'TextBlock',\n    text: 'Select the examples you want to see!',\n    size: 'Large',\n    weight: 'Bolder',\n  }).withActions(\n    // raw action\n    {\n      type: 'Action.Submit',\n      title: 'Simple form test',\n      data: {\n        msteams: {\n          type: 'task/fetch',\n        },\n        opendialogtype: 'simple_form',\n      },\n    },\n    // Special type of action to open a dialog\n    new TaskFetchAction({})\n      .withTitle('Webpage Dialog')\n      // This data will be passed back in an event so we can\n      // handle what to show in the dialog\n      .withValue(new TaskFetchData({ opendialogtype: 'webpage_dialog' })),\n    new TaskFetchAction({})\n      .withTitle('Multi-step Form')\n      .withValue(new TaskFetchData({ opendialogtype: 'multi_step_form' })),\n    new TaskFetchAction({})\n      .withTitle('Mixed Example')\n      .withValue(new TaskFetchData({ opendialogtype: 'mixed_example' }))\n  );\n\n  // Send the card as an attachment\n  await send(new MessageActivity('Enter this form').addCard('adaptive', card));\n});\n```\n\n<!-- dialog-open-intro -->\n\nOnce an action is executed to open a dialog, the Teams client will send an event to the agent to request what the content of the dialog should be. Here is how to handle this event:\n\n<!-- dialog-open-code -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\nimport { AdaptiveCard, IAdaptiveCard } from '@microsoft/teams.cards';\n// ...\n\napp.on('dialog.open', async ({ activity }) => {\n  const card: IAdaptiveCard = new AdaptiveCard()...\n\n  // Return an object with the task value that renders a card\n  return {\n    task: {\n      type: 'continue',\n      value: {\n        title: 'Title of Dialog',\n        card: cardAttachment('adaptive', card),\n      },\n    },\n  };\n}\n```\n\n<!-- rendering-card-code -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { AdaptiveCard, TextInput, SubmitAction } from '@microsoft/teams.cards';\n// ...\n\nif (dialogType === 'simple_form') {\n  const dialogCard = new AdaptiveCard(\n    {\n      type: 'TextBlock',\n      text: 'This is a simple form',\n      size: 'Large',\n      weight: 'Bolder',\n    },\n    new TextInput()\n      .withLabel('Name')\n      .withIsRequired()\n      .withId('name')\n      .withPlaceholder('Enter your name')\n  )\n    // Inside the dialog, the card actions for submitting the card must be\n    // of type Action.Submit\n    .withActions(\n      new SubmitAction().withTitle('Submit').withData({ submissiondialogtype: 'simple_form' })\n    );\n\n  // Return an object with the task value that renders a card\n  return {\n    task: {\n      type: 'continue',\n      value: {\n        title: 'Simple Form Dialog',\n        card: cardAttachment('adaptive', dialogCard),\n      },\n    },\n  };\n}\n```\n\n<!-- rendering-webpage-code -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\nreturn {\n  task: {\n    type: 'continue',\n    value: {\n      title: 'Webpage Dialog',\n      // Here we are using a webpage that is hosted in the same\n      // server as the agent. This server needs to be publicly accessible,\n      // needs to set up teams.js client library (https://www.npmjs.com/package/@microsoft/teams-js)\n      // and needs to be registered in the manifest.\n      url: `${process.env['BOT_ENDPOINT']}/tabs/dialog-form`,\n      width: 1000,\n      height: 800,\n    },\n  },\n};\n```\n\n<!-- embedded-web-content -->\n\n### Setting up Embedded Web Content\n\nTo serve web content for dialogs, you can use the `tab` method to host static webpages:\n\n```typescript\nimport path from 'path';\n\n// In your app setup (e.g., index.ts)\n// Hosts a static webpage at /tabs/dialog-form\napp.tab('dialog-form', path.join(__dirname, 'views', 'customform'));\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/handling-dialog-submissions/csharp.incl.md",
    "content": "<!-- event-intro -->\n\nDialogs have a specific `TaskSubmit` event to handle submissions. When a user submits a form inside a dialog, the app is notified via this event, which is then handled to process the submission values, and can either send a response or proceed to more steps in the dialogs (see [Multi-step Dialogs](./handling-multi-step-forms)).\n\n:::warning Return Type Requirement\nMethods decorated with `[TaskSubmit]` **must** return `Task<Microsoft.Teams.Api.TaskModules.Response>`. Every code path must return a Response object containing either a `MessageTask` (to show a message and close the dialog) or a `ContinueTask` (to show another dialog). Using just `Task` or `void` will compile but fail at runtime when the Teams client expects a Response object.\n:::\n\n## Basic Example\n\n<!-- adaptive-card-example -->\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities.Invokes;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Common.Logging;\n\n//...\n\n[TaskSubmit]\npublic async Task<Microsoft.Teams.Api.TaskModules.Response> OnTaskSubmit([Context] Tasks.SubmitActivity activity, [Context] IContext.Client client, [Context] ILogger log)\n{\n    var data = activity.Value?.Data as JsonElement?;\n    if (data == null)\n    {\n        log.Info(\"[TASK_SUBMIT] No data found in the activity value\");\n        return new Microsoft.Teams.Api.TaskModules.Response(\n            new Microsoft.Teams.Api.TaskModules.MessageTask(\"No data found in the activity value\"));\n    }\n\n    var submissionType = data.Value.TryGetProperty(\"submissiondialogtype\", out var submissionTypeObj) && submissionTypeObj.ValueKind == JsonValueKind.String\n        ? submissionTypeObj.ToString()\n        : null;\n\n\n    string? GetFormValue(string key)\n    {\n        if (data.Value.TryGetProperty(key, out var val))\n        {\n            if (val is JsonElement element)\n                return element.GetString();\n            return val.ToString();\n        }\n        return null;\n    }\n\n    switch (submissionType)\n    {\n        case \"simple_form\":\n            var name = GetFormValue(\"name\") ?? \"Unknown\";\n            await client.Send($\"Hi {name}, thanks for submitting the form!\");\n            return new Microsoft.Teams.Api.TaskModules.Response(\n                new Microsoft.Teams.Api.TaskModules.MessageTask(\"Form was submitted\"));\n        // More examples below\n        default:\n            return new Microsoft.Teams.Api.TaskModules.Response(\n                new Microsoft.Teams.Api.TaskModules.MessageTask(\"Unknown submission type\"));\n    }\n}\n```\n\n<!-- webpage-example -->\n\n```csharp\n// Add this case to the switch statement in OnTaskSubmit method\ncase \"webpage_dialog\":\n    var webName = GetFormValue(\"name\") ?? \"Unknown\";\n    var email = GetFormValue(\"email\") ?? \"No email\";\n    await client.Send($\"Hi {webName}, thanks for submitting the form! We got that your email is {email}\");\n    return new Microsoft.Teams.Api.TaskModules.Response(\n        new Microsoft.Teams.Api.TaskModules.MessageTask(\"Form submitted successfully\"));\n```\n\n<!-- complete-example -->\n\n### Complete TaskSubmit Handler Example\n\nHere's the complete example showing how to handle multiple submission types:\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities.Invokes;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Common.Logging;\n\n//...\n\n[TaskSubmit]\npublic async Task<Microsoft.Teams.Api.TaskModules.Response> OnTaskSubmit([Context] Tasks.SubmitActivity activity, [Context] IContext.Client client, [Context] ILogger log)\n{\n    var data = activity.Value?.Data as JsonElement?;\n    if (data == null)\n    {\n        log.Info(\"[TASK_SUBMIT] No data found in the activity value\");\n        return new Microsoft.Teams.Api.TaskModules.Response(\n            new Microsoft.Teams.Api.TaskModules.MessageTask(\"No data found in the activity value\"));\n    }\n\n    var submissionType = data.Value.TryGetProperty(\"submissiondialogtype\", out var submissionTypeObj) && submissionTypeObj.ValueKind == JsonValueKind.String\n        ? submissionTypeObj.ToString()\n        : null;\n\n    string? GetFormValue(string key)\n    {\n        if (data.Value.TryGetProperty(key, out var val))\n        {\n            if (val is JsonElement element)\n                return element.GetString();\n            return val.ToString();\n        }\n        return null;\n    }\n\n    switch (submissionType)\n    {\n        case \"simple_form\":\n            var name = GetFormValue(\"name\") ?? \"Unknown\";\n            await client.Send($\"Hi {name}, thanks for submitting the form!\");\n            return new Microsoft.Teams.Api.TaskModules.Response(\n                new Microsoft.Teams.Api.TaskModules.MessageTask(\"Form was submitted\"));\n\n        case \"webpage_dialog\":\n            var webName = GetFormValue(\"name\") ?? \"Unknown\";\n            var email = GetFormValue(\"email\") ?? \"No email\";\n            await client.Send($\"Hi {webName}, thanks for submitting the form! We got that your email is {email}\");\n            return new Microsoft.Teams.Api.TaskModules.Response(\n                new Microsoft.Teams.Api.TaskModules.MessageTask(\"Form submitted successfully\"));\n\n        default:\n            return new Microsoft.Teams.Api.TaskModules.Response(\n                new Microsoft.Teams.Api.TaskModules.MessageTask(\"Unknown submission type\"));\n    }\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/handling-dialog-submissions/python.incl.md",
    "content": "<!-- event-intro -->\n\nDialogs have a specific `dialog_submit` event to handle submissions. When a user submits a form inside a dialog, the app is notified via this event, which is then handled to process the submission values, and can either send a response or proceed to more steps in the dialogs (see [Multi-step Dialogs](./handling-multi-step-forms)).\n\n<!-- adaptive-card-example -->\n\n```python\nfrom typing import Optional, Any\nfrom microsoft_teams.api import TaskSubmitInvokeActivity, TaskModuleResponse, TaskModuleMessageResponse\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_dialog_submit\nasync def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):\n    \"\"\"Handle dialog submit events for all dialog types.\"\"\"\n    data: Optional[Any] = ctx.activity.value.data\n    dialog_type = data.get(\"submissiondialogtype\") if data else None\n\n    if dialog_type == \"simple_form\":\n        name = data.get(\"name\") if data else None\n        await ctx.send(f\"Hi {name}, thanks for submitting the form!\")\n        return TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Form was submitted\"))\n```\n\n<!-- webpage-example -->\n\n```python\nfrom typing import Optional, Any\nfrom microsoft_teams.api import TaskSubmitInvokeActivity, InvokeResponse, TaskModuleResponse, TaskModuleMessageResponse\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_dialog_submit\nasync def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):\n    \"\"\"Handle dialog submit events for all dialog types.\"\"\"\n    data: Optional[Any] = ctx.activity.value.data\n    dialog_type = data.get(\"submissiondialogtype\") if data else None\n\n    if dialog_type == \"webpage_dialog\":\n        name = data.get(\"name\") if data else None\n        email = data.get(\"email\") if data else None\n        await ctx.send(f\"Hi {name}, thanks for submitting the form! We got that your email is {email}\")\n        return InvokeResponse(\n            body=TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Form submitted successfully\"))\n        )\n```\n\n<!-- complete-example -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/handling-dialog-submissions/typescript.incl.md",
    "content": "<!-- event-intro -->\n\nDialogs have a specific `dialog.submit` event to handle submissions. When a user submits a form inside a dialog, the app is notified via this event, which is then handled to process the submission values, and can either send a response or proceed to more steps in the dialogs (see [Multi-step Dialogs](./handling-multi-step-forms)).\n\n<!-- adaptive-card-example -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('dialog.submit', async ({ activity, send, next }) => {\n  const dialogType = activity.value.data?.submissiondialogtype;\n\n  if (dialogType === 'simple_form') {\n    // This is data from the form that was submitted\n    const name = activity.value.data.name;\n    await send(`Hi ${name}, thanks for submitting the form!`);\n    return {\n      task: {\n        type: 'message',\n        // This appears as a final message in the dialog\n        value: 'Form was submitted',\n      },\n    };\n  }\n});\n```\n\n<!-- webpage-example -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\n// The submission from a webpage happens via the microsoftTeams.tasks.submitTask(formData)\n// call.\napp.on('dialog.submit', async ({ activity, send, next }) => {\n  const dialogType = activity.value.data.submissiondialogtype;\n\n  if (dialogType === 'webpage_dialog') {\n    // This is data from the form that was submitted\n    const name = activity.value.data.name;\n    const email = activity.value.data.email;\n    await send(`Hi ${name}, thanks for submitting the form! We got that your email is ${email}`);\n    // You can also return a blank response\n    return {\n      status: 200,\n    };\n  }\n});\n```\n\n<!-- complete-example -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/handling-multi-step-forms/csharp.incl.md",
    "content": "<!-- initial-setup -->\n\n## Creating the Initial Dialog\n\nStart off by sending an initial card in the `TaskFetch` event.\n\n<!-- initial-card -->\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Cards;\n\n//...\n\nprivate static Response CreateMultiStepFormDialog()\n{\n    var cardJson = \"\"\"\n    {\n        \"type\": \"AdaptiveCard\",\n        \"version\": \"1.4\",\n        \"body\": [\n            {\n                \"type\": \"TextBlock\",\n                \"text\": \"This is a multi-step form\",\n                \"size\": \"Large\",\n                \"weight\": \"Bolder\"\n            },\n            {\n                \"type\": \"Input.Text\",\n                \"id\": \"name\",\n                \"label\": \"Name\",\n                \"placeholder\": \"Enter your name\",\n                \"isRequired\": true\n            }\n        ],\n        \"actions\": [\n            {\n                \"type\": \"Action.Submit\",\n                \"title\": \"Submit\",\n                \"data\": {\"submissiondialogtype\": \"webpage_dialog_step_1\"}\n            }\n        ]\n    }\n    \"\"\";\n\n    var dialogCard = JsonSerializer.Deserialize<AdaptiveCard>(cardJson)\n        ?? throw new InvalidOperationException(\"Failed to deserialize multi-step form card\");\n\n    var taskInfo = new TaskInfo\n    {\n        Title = \"Multi-step Form Dialog\",\n        Card = new Attachment\n        {\n            ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n            Content = dialogCard\n        }\n    };\n\n    return new Response(new ContinueTask(taskInfo));\n}\n```\n\n<!-- submission-handler -->\n\nThen in the submission handler, you can choose to `continue` the dialog with a different card.\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Cards;\n\n//...\n\n// Add these cases to your OnTaskSubmit method\ncase \"webpage_dialog_step_1\":\n    var nameStep1 = GetFormValue(\"name\") ?? \"Unknown\";\n    var nextStepCardJson = $$\"\"\"\n    {\n        \"type\": \"AdaptiveCard\",\n        \"version\": \"1.4\",\n        \"body\": [\n            {\n                \"type\": \"TextBlock\",\n                \"text\": \"Email\",\n                \"size\": \"Large\",\n                \"weight\": \"Bolder\"\n            },\n            {\n                \"type\": \"Input.Text\",\n                \"id\": \"email\",\n                \"label\": \"Email\",\n                \"placeholder\": \"Enter your email\",\n                \"isRequired\": true\n            }\n        ],\n        \"actions\": [\n            {\n                \"type\": \"Action.Submit\",\n                \"title\": \"Submit\",\n                \"data\": {\"submissiondialogtype\": \"webpage_dialog_step_2\", \"name\": \"{{nameStep1}}\"}\n            }\n        ]\n    }\n    \"\"\";\n\n    var nextStepCard = JsonSerializer.Deserialize<AdaptiveCard>(nextStepCardJson)\n        ?? throw new InvalidOperationException(\"Failed to deserialize next step card\");\n\n    var nextStepTaskInfo = new TaskInfo\n    {\n        Title = $\"Thanks {nameStep1} - Get Email\",\n        Card = new Attachment\n        {\n            ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n            Content = nextStepCard\n        }\n    };\n\n    return new Response(new ContinueTask(nextStepTaskInfo));\n\ncase \"webpage_dialog_step_2\":\n    var nameStep2 = GetFormValue(\"name\") ?? \"Unknown\";\n    var emailStep2 = GetFormValue(\"email\") ?? \"No email\";\n    await client.Send($\"Hi {nameStep2}, thanks for submitting the form! We got that your email is {emailStep2}\");\n    return new Response(new MessageTask(\"Multi-step form completed successfully\"));\n```\n\n<!-- complete-example -->\n\n### Complete Multi-Step Form Handler\n\nHere's the complete example showing how to handle a multi-step form:\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities.Invokes;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Cards;\nusing Microsoft.Teams.Common.Logging;\n\n//...\n\n[TaskSubmit]\npublic async Task<Response> OnTaskSubmit([Context] Tasks.SubmitActivity activity, [Context] IContext.Client client, [Context] ILogger log)\n{\n    log.Info(\"[TASK_SUBMIT] Task submit request received\");\n\n    var data = activity.Value?.Data as JsonElement?;\n    if (data == null)\n    {\n        log.Info(\"[TASK_SUBMIT] No data found in the activity value\");\n        return new Response(new MessageTask(\"No data found in the activity value\"));\n    }\n\n    var submissionType = data.Value.TryGetProperty(\"submissiondialogtype\", out var submissionTypeObj) && submissionTypeObj.ValueKind == JsonValueKind.String\n        ? submissionTypeObj.ToString()\n        : null;\n\n    log.Info($\"[TASK_SUBMIT] Submission type: {submissionType}\");\n\n    string? GetFormValue(string key)\n    {\n        if (data.Value.TryGetProperty(key, out var val))\n        {\n            if (val is JsonElement element)\n                return element.GetString();\n            return val.ToString();\n        }\n        return null;\n    }\n\n    switch (submissionType)\n    {\n        case \"webpage_dialog_step_1\":\n            var nameStep1 = GetFormValue(\"name\") ?? \"Unknown\";\n            var nextStepCardJson = $$\"\"\"\n            {\n                \"type\": \"AdaptiveCard\",\n                \"version\": \"1.4\",\n                \"body\": [\n                    {\n                        \"type\": \"TextBlock\",\n                        \"text\": \"Email\",\n                        \"size\": \"Large\",\n                        \"weight\": \"Bolder\"\n                    },\n                    {\n                        \"type\": \"Input.Text\",\n                        \"id\": \"email\",\n                        \"label\": \"Email\",\n                        \"placeholder\": \"Enter your email\",\n                        \"isRequired\": true\n                    }\n                ],\n                \"actions\": [\n                    {\n                        \"type\": \"Action.Submit\",\n                        \"title\": \"Submit\",\n                        \"data\": {\"submissiondialogtype\": \"webpage_dialog_step_2\", \"name\": \"{{nameStep1}}\"}\n                    }\n                ]\n            }\n            \"\"\";\n\n            var nextStepCard = JsonSerializer.Deserialize<AdaptiveCard>(nextStepCardJson)\n                ?? throw new InvalidOperationException(\"Failed to deserialize next step card\");\n\n            var nextStepTaskInfo = new TaskInfo\n            {\n                Title = $\"Thanks {nameStep1} - Get Email\",\n                Card = new Attachment\n                {\n                    ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n                    Content = nextStepCard\n                }\n            };\n\n            return new Response(new ContinueTask(nextStepTaskInfo));\n\n        case \"webpage_dialog_step_2\":\n            var nameStep2 = GetFormValue(\"name\") ?? \"Unknown\";\n            var emailStep2 = GetFormValue(\"email\") ?? \"No email\";\n            await client.Send($\"Hi {nameStep2}, thanks for submitting the form! We got that your email is {emailStep2}\");\n            return new Response(new MessageTask(\"Multi-step form completed successfully\"));\n\n        default:\n            return new Response(new MessageTask(\"Unknown submission type\"));\n    }\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/handling-multi-step-forms/python.incl.md",
    "content": "<!-- initial-setup -->\n\nStart off by sending an initial card in the `dialog_open` event.\n\n<!-- initial-card -->\n\n```python\ndialog_card = AdaptiveCard.model_validate(\n            {\n                \"type\": \"AdaptiveCard\",\n                \"version\": \"1.4\",\n                \"body\": [\n                    {\"type\": \"TextBlock\", \"text\": \"This is a multi-step form\", \"size\": \"Large\", \"weight\": \"Bolder\"},\n                    {\n                        \"type\": \"Input.Text\",\n                        \"id\": \"name\",\n                        \"label\": \"Name\",\n                        \"placeholder\": \"Enter your name\",\n                        \"isRequired\": True,\n                    },\n                ],\n                \"actions\": [\n                    {\n                        \"type\": \"Action.Submit\",\n                        \"title\": \"Submit\",\n                        \"data\": {\"submissiondialogtype\": \"webpage_dialog_step_1\"},\n                    }\n                ],\n            }\n        )\n```\n\n<!-- submission-handler -->\n\nThen in the submission handler, you can choose to `continue` the dialog with a different card.\n\n```python\n\n@app.on_dialog_submit\nasync def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):\n    \"\"\"Handle dialog submit events for all dialog types.\"\"\"\n    data: Optional[Any] = ctx.activity.value.data\n    dialog_type = data.get(\"submissiondialogtype\") if data else None\n\n    if dialog_type == \"webpage_dialog\":\n        name = data.get(\"name\") if data else None\n        email = data.get(\"email\") if data else None\n        await ctx.send(f\"Hi {name}, thanks for submitting the form! We got that your email is {email}\")\n        return InvokeResponse(\n            body=TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Form submitted successfully\"))\n        )\n\n    elif dialog_type == \"webpage_dialog_step_1\":\n        name = data.get(\"name\") if data else None\n        next_step_card = AdaptiveCard.model_validate(\n            {\n                \"type\": \"AdaptiveCard\",\n                \"version\": \"1.4\",\n                \"body\": [\n                    {\"type\": \"TextBlock\", \"text\": \"Email\", \"size\": \"Large\", \"weight\": \"Bolder\"},\n                    {\n                        \"type\": \"Input.Text\",\n                        \"id\": \"email\",\n                        \"label\": \"Email\",\n                        \"placeholder\": \"Enter your email\",\n                        \"isRequired\": True,\n                    },\n                ],\n                \"actions\": [\n                    {\n                        \"type\": \"Action.Submit\",\n                        \"title\": \"Submit\",\n                        \"data\": {\"submissiondialogtype\": \"webpage_dialog_step_2\", \"name\": name},\n                    }\n                ],\n            }\n        )\n\n        return InvokeResponse(\n            body=TaskModuleResponse(\n                task=TaskModuleContinueResponse(\n                    value=CardTaskModuleTaskInfo(\n                        title=f\"Thanks {name} - Get Email\",\n                        card=card_attachment(AdaptiveCardAttachment(content=next_step_card)),\n                    )\n                )\n            )\n        )\n\n    elif dialog_type == \"webpage_dialog_step_2\":\n        name = data.get(\"name\") if data else None\n        email = data.get(\"email\") if data else None\n        await ctx.send(f\"Hi {name}, thanks for submitting the form! We got that your email is {email}\")\n        return InvokeResponse(\n            body=TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Multi-step form completed successfully\"))\n        )\n\n    return TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Unknown submission type\"))\n\n```\n\n<!-- complete-example -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/dialogs/handling-multi-step-forms/typescript.incl.md",
    "content": "<!-- initial-setup -->\n\nStart off by sending an initial card in the `dialog.open` event.\n\n<!-- initial-card -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { AdaptiveCard, TextInput, SubmitAction } from '@microsoft/teams.cards';\n// ...\n\nconst dialogCard = new AdaptiveCard(\n  {\n    type: 'TextBlock',\n    text: 'This is a multi-step form',\n    size: 'Large',\n    weight: 'Bolder',\n  },\n  new TextInput()\n    .withLabel('Name')\n    .withIsRequired()\n    .withId('name')\n    .withPlaceholder('Enter your name')\n)\n  // Inside the dialog, the card actions for submitting the card must be\n  // of type Action.Submit\n  .withActions(\n    new SubmitAction()\n      .withTitle('Submit')\n      .withData({ submissiondialogtype: 'webpage_dialog_step_1' })\n  );\n\n// Return an object with the task value that renders a card\nreturn {\n  task: {\n    type: 'continue',\n    value: {\n      title: 'Multi-step Form Dialog',\n      card: cardAttachment('adaptive', dialogCard),\n    },\n  },\n};\n```\n\n<!-- submission-handler -->\n\nThen in the submission handler, you can choose to `continue` the dialog with a different card.\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\nimport { AdaptiveCard, TextInput, SubmitAction } from '@microsoft/teams.cards';\n// ...\n\napp.on('dialog.submit', async ({ activity, send, next }) => {\n  const dialogType = activity.value.data.submissiondialogtype;\n\n  if (dialogType === 'webpage_dialog_step_1') {\n    // This is data from the form that was submitted\n    const name = activity.value.data.name;\n    const nextStepCard = new AdaptiveCard(\n      {\n        type: 'TextBlock',\n        text: 'Email',\n        size: 'Large',\n        weight: 'Bolder',\n      },\n      new TextInput()\n        .withLabel('Email')\n        .withIsRequired()\n        .withId('email')\n        .withPlaceholder('Enter your email')\n    ).withActions(\n      new SubmitAction().withTitle('Submit').withData({\n        // This same handler will get called, so we need to identify the step\n        // in the returned data\n        submissiondialogtype: 'webpage_dialog_step_2',\n        // Carry forward data from previous step\n        name,\n      })\n    );\n    return {\n      task: {\n        // This indicates that the dialog flow should continue\n        type: 'continue',\n        value: {\n          // Here we customize the title based on the previous response\n          title: `Thanks ${name} - Get Email`,\n          card: cardAttachment('adaptive', nextStepCard),\n        },\n      },\n    };\n  } else if (dialogType === 'webpage_dialog_step_2') {\n    const name = activity.value.data.name;\n    const email = activity.value.data.email;\n    await send(`Hi ${name}, thanks for submitting the form! We got that your email is ${email}`);\n    // You can also return a blank response\n    return {\n      status: 200,\n    };\n  }\n});\n```\n\n<!-- complete-example -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/feedback/csharp.incl.md",
    "content": "<!-- storage -->\n\n```csharp\n// This store would ideally be persisted in a database\npublic static class FeedbackStore\n{\n    public static readonly Dictionary<string, FeedbackData> StoredFeedbackByMessageId = new();\n\n    public class FeedbackData\n    {\n        public string IncomingMessage { get; set; } = string.Empty;\n        public string OutgoingMessage { get; set; } = string.Empty;\n        public int Likes { get; set; }\n        public int Dislikes { get; set; }\n        public List<string> Feedbacks { get; set; } = new();\n    }\n}\n```\n\n<!-- including-feedback -->\n\n```csharp\nvar sentMessageId = await context.Send(\n    result.Content != null\n        ? new MessageActivity(result.Content)\n            .AddAiGenerated()\n            /** Add feedback buttons via this method */\n            .AddFeedback()\n        : \"I did not generate a response.\"\n);\n\nFeedbackStore.StoredFeedbackByMessageId[sentMessageId.Id] = new FeedbackStore.FeedbackData\n{\n    IncomingMessage = context.Activity.Text,\n    OutgoingMessage = result.Content ?? string.Empty,\n    Likes = 0,\n    Dislikes = 0,\n    Feedbacks = new List<string>()\n};\n```\n\n<!-- handling-feedback -->\n\n```csharp\n[Microsoft.Teams.Apps.Activities.Invokes.Message.Feedback]\npublic Task OnFeedbackReceived([Context] Microsoft.Teams.Api.Activities.Invokes.Messages.SubmitActionActivity activity)\n{\n    var reaction = activity.Value?.ActionValue?.GetType().GetProperty(\"reaction\")?.GetValue(activity.Value?.ActionValue)?.ToString();\n    var feedbackJson = activity.Value?.ActionValue?.GetType().GetProperty(\"feedback\")?.GetValue(activity.Value?.ActionValue)?.ToString();\n\n    if (activity.ReplyToId == null)\n    {\n        _log.LogWarning(\"No replyToId found for messageId {ActivityId}\", activity.Id);\n        return Task.CompletedTask;\n    }\n\n    var existingFeedback = FeedbackStore.StoredFeedbackByMessageId.GetValueOrDefault(activity.ReplyToId);\n    /**\n        * feedbackJson looks like:\n        * {\"feedbackText\":\"Nice!\"}\n        */\n    if (existingFeedback == null)\n    {\n        _log.LogWarning(\"No feedback found for messageId {ActivityId}\", activity.Id);\n    }\n    else\n    {\n        var updatedFeedback = new FeedbackStore.FeedbackData\n        {\n            IncomingMessage = existingFeedback.IncomingMessage,\n            OutgoingMessage = existingFeedback.OutgoingMessage,\n            Likes = existingFeedback.Likes + (reaction == \"like\" ? 1 : 0),\n            Dislikes = existingFeedback.Dislikes + (reaction == \"dislike\" ? 1 : 0),\n            Feedbacks = existingFeedback.Feedbacks.Concat(new[] { feedbackJson ?? string.Empty }).ToList()\n        };\n\n        FeedbackStore.StoredFeedbackByMessageId[activity.Id] = updatedFeedback;\n    }\n\n    return Task.CompletedTask;\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/feedback/python.incl.md",
    "content": "<!-- storage -->\n\nOnce you receive a feedback event, you can choose to store it in some persistent storage. You'll need to implement storage for tracking:\n\n- Like/dislike counts per message\n- Text feedback comments\n- Message ID associations\n\nFor production applications, consider using databases, file systems, or cloud storage. The examples below use in-memory storage for simplicity.\n\n<!-- including-feedback -->\n\n```python\nfrom microsoft_teams.ai import Agent\nfrom microsoft_teams.api import MessageActivityInput\nfrom microsoft_teams.apps import ActivityContext, MessageActivity\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle 'feedback demo' command to demonstrate feedback collection\"\"\"\n    agent = Agent(current_model)\n    chat_result = await agent.send(\n        input=\"Tell me a short joke\",\n        instructions=\"You are a comedian. Keep responses brief and funny.\"\n    )\n\n    if chat_result.response.content:\n        message = MessageActivityInput(text=chat_result.response.content)\n                    .add_ai_generated()\n                    # Create message with feedback enabled\n                    .add_feedback()\n        await ctx.send(message)\n```\n\n<!-- handling-feedback -->\n\n```python\nimport json\nfrom typing import Dict, Any\nfrom microsoft_teams.api import MessageSubmitActionInvokeActivity\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n# Handle feedback submission events\n@app.on_message_submit_feedback\nasync def handle_message_feedback(ctx: ActivityContext[MessageSubmitActionInvokeActivity]):\n    \"\"\"Handle feedback submission events\"\"\"\n    activity = ctx.activity\n\n    # Extract feedback data from activity value\n    if not hasattr(activity, \"value\") or not activity.value:\n        logger.warning(f\"No value found in activity {activity.id}\")\n        return\n\n    # Access feedback data directly from invoke value\n    invoke_value = activity.value\n    assert invoke_value.action_name == \"feedback\"\n    feedback_str = invoke_value.action_value.feedback\n    reaction = invoke_value.action_value.reaction\n    feedback_json: Dict[str, Any] = json.loads(feedback_str)\n    # { 'feedbackText': 'the ai response was great!' }\n\n    if not activity.reply_to_id:\n        logger.warning(f\"No replyToId found for messageId {activity.id}\")\n        return\n\n    # Store the feedback (implement your own storage logic)\n    upsert_feedback_storage(activity.reply_to_id, reaction, feedback_json.get('feedbackText', ''))\n\n    # Optionally Send confirmation response\n    feedback_text: str = feedback_json.get(\"feedbackText\", \"\")\n    reaction_text: str = f\" and {reaction}\" if reaction else \"\"\n    text_part: str = f\" with comment: '{feedback_text}'\" if feedback_text else \"\"\n\n    await ctx.reply(f\"✅ Thank you for your feedback{reaction_text}{text_part}!\")\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/feedback/typescript.incl.md",
    "content": "<!-- storage -->\n\n```typescript\nimport { ChatPrompt, IChatModel } from '@microsoft/teams.ai';\nimport { ActivityLike, IMessageActivity, MessageActivity } from '@microsoft/teams.api';\n// ...\n\n// This store would ideally be persisted in a database\nexport const storedFeedbackByMessageId = new Map<\n  string,\n  {\n    incomingMessage: string;\n    outgoingMessage: string;\n    likes: number;\n    dislikes: number;\n    feedbacks: string[];\n  }\n>();\n```\n\n<!-- including-feedback -->\n\n```typescript\nimport { ChatPrompt, IChatModel } from '@microsoft/teams.ai';\nimport {\n  ActivityLike,\n  IMessageActivity,\n  MessageActivity,\n  SentActivity,\n} from '@microsoft/teams.api';\n// ...\n\nconst { id: sentMessageId } = await send(\n  result.content != null\n    ? new MessageActivity(result.content)\n        .addAiGenerated()\n        /** Add feedback buttons via this method */\n        .addFeedback()\n    : 'I did not generate a response.'\n);\n\nstoredFeedbackByMessageId.set(sentMessageId, {\n  incomingMessage: activity.text,\n  outgoingMessage: result.content ?? '',\n  likes: 0,\n  dislikes: 0,\n  feedbacks: [],\n});\n```\n\n<!-- handling-feedback -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message.submit.feedback', async ({ activity, log }) => {\n  const { reaction, feedback: feedbackJson } = activity.value.actionValue;\n  if (activity.replyToId == null) {\n    log.warn(`No replyToId found for messageId ${activity.id}`);\n    return;\n  }\n  const existingFeedback = storedFeedbackByMessageId.get(activity.replyToId);\n  /**\n   * feedbackJson looks like:\n   * {\"feedbackText\":\"Nice!\"}\n   */\n  if (!existingFeedback) {\n    log.warn(`No feedback found for messageId ${activity.id}`);\n  } else {\n    storedFeedbackByMessageId.set(activity.id, {\n      ...existingFeedback,\n      likes: existingFeedback.likes + (reaction === 'like' ? 1 : 0),\n      dislikes: existingFeedback.dislikes + (reaction === 'dislike' ? 1 : 0),\n      feedbacks: [...existingFeedback.feedbacks, feedbackJson],\n    });\n  }\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/meeting-events/csharp.incl.md",
    "content": "<!-- meeting-start -->\n\n```csharp\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities;\nusing Microsoft.Teams.Apps.Activities.Events;\nusing Microsoft.Teams.Cards;\n\n// Register meeting start handler\nteamsApp.OnMeetingStart(async (context, cancellationToken) =>\n{\n    var activity = context.Activity.Value;\n    var startTime = activity.StartTime.ToLocalTime();\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock($\"'{activity.Title}' has started at {startTime}.\")\n            {\n                Wrap = true,\n                Weight = TextWeight.Bolder\n            }\n        },\n        Actions = new List<Microsoft.Teams.Cards.Action>\n        {\n            new OpenUrlAction(activity.JoinUrl)\n            {\n                Title = \"Join the meeting\",\n            }\n        }\n    };\n\n    await context.Send(card, cancellationToken);\n});\n```\n\n<!-- meeting-end -->\n\n```csharp\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities;\nusing Microsoft.Teams.Apps.Activities.Events;\nusing Microsoft.Teams.Cards;\n\n// Register meeting end handler\nteamsApp.OnMeetingEnd(async (context, cancellationToken) =>\n{\n    var activity = context.Activity.Value;\n    var endTime = activity.EndTime.ToLocalTime();\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock($\"'{activity.Title}' has ended at {endTime}.\")\n            {\n                Wrap = true,\n                Weight = TextWeight.Bolder\n            }\n        }\n    };\n\n    await context.Send(card, cancellationToken);\n});\n```\n\n<!-- participant-join -->\n\n```csharp\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities;\nusing Microsoft.Teams.Apps.Activities.Events;\nusing Microsoft.Teams.Cards;\n\n// Register participant join handler\nteamsApp.OnMeetingJoin(async (context, cancellationToken) =>\n{\n    var activity = context.Activity.Value;\n    var member = activity.Members[0].User.Name;\n    var role = activity.Members[0].Meeting.Role;\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock($\"{member} has joined the meeting as {role}.\")\n            {\n                Wrap = true,\n                Weight = TextWeight.Bolder\n            }\n        }\n    };\n\n    await context.Send(card, cancellationToken);\n});\n```\n\n<!-- participant-leave -->\n\n```csharp\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Apps.Activities;\nusing Microsoft.Teams.Apps.Activities.Events;\nusing Microsoft.Teams.Cards;\n\n// Register participant leave handler\nteamsApp.OnMeetingLeave(async (context, cancellationToken) =>\n{\n    var activity = context.Activity.Value;\n    var member = activity.Members[0].User.Name;\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock($\"{member} has left the meeting.\")\n            {\n                Wrap = true,\n                Weight = TextWeight.Bolder\n            }\n        }\n    };\n\n    await context.Send(card, cancellationToken);\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/meeting-events/python.incl.md",
    "content": "<!-- meeting-start -->\n\n```python\nfrom microsoft_teams.api.activities.event import MeetingStartEventActivity\nfrom microsoft_teams.apps import ActivityContext, App\nfrom microsoft_teams.cards import AdaptiveCard, OpenUrlAction, TextBlock\n\napp = App()\n\n@app.on_meeting_start\nasync def handle_meeting_start(ctx: ActivityContext[MeetingStartEventActivity]):\n    meeting_data = ctx.activity.value\n    start_time = meeting_data.start_time.strftime(\"%c\")\n\n    card = AdaptiveCard(\n        body=[\n            TextBlock(\n                text=f\"'{meeting_data.title}' has started at {start_time}.\",\n                wrap=True,\n                weight=\"Bolder\",\n            )\n        ],\n        actions=[OpenUrlAction(url=meeting_data.join_url, title=\"Join the meeting\")],\n    )\n\n    await ctx.send(card)\n```\n\n<!-- meeting-end -->\n\n```python\nfrom microsoft_teams.api.activities.event import MeetingEndEventActivity\nfrom microsoft_teams.apps import ActivityContext, App\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock\n\napp = App()\n\n@app.on_meeting_end\nasync def handle_meeting_end(ctx: ActivityContext[MeetingEndEventActivity]):\n    meeting_data = ctx.activity.value\n    end_time = meeting_data.end_time.strftime(\"%c\")\n\n    card = AdaptiveCard(\n        body=[\n            TextBlock(\n                text=f\"'{meeting_data.title}' has ended at {end_time}.\",\n                wrap=True,\n                weight=\"Bolder\",\n            )\n        ]\n    )\n\n    await ctx.send(card)\n```\n\n<!-- participant-join -->\n\n```python\nfrom microsoft_teams.api.activities.event import MeetingParticipantJoinEventActivity\nfrom microsoft_teams.apps import ActivityContext, App\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock\n\napp = App()\n\n@app.on_meeting_participant_join\nasync def handle_meeting_participant_join(ctx: ActivityContext[MeetingParticipantJoinEventActivity]):\n    meeting_data = ctx.activity.value\n    member = meeting_data.members[0].user.name\n    role = meeting_data.members[0].meeting.role if hasattr(meeting_data.members[0].meeting, \"role\") else \"a participant\"\n\n    card = AdaptiveCard(\n        body=[\n            TextBlock(\n                text=f\"{member} has joined the meeting as {role}.\",\n                wrap=True,\n                weight=\"Bolder\",\n            )\n        ]\n    )\n\n    await ctx.send(card)\n```\n\n<!-- participant-leave -->\n\n```python\nfrom microsoft_teams.api.activities.event import MeetingParticipantLeaveEventActivity\nfrom microsoft_teams.apps import ActivityContext, App\nfrom microsoft_teams.cards import AdaptiveCard, TextBlock\n\napp = App()\n\n@app.on_meeting_participant_leave\nasync def handle_meeting_participant_leave(ctx: ActivityContext[MeetingParticipantLeaveEventActivity]):\n    meeting_data = ctx.activity.value\n    member = meeting_data.members[0].user.name\n\n    card = AdaptiveCard(\n        body=[\n            TextBlock(\n                text=f\"{member} has left the meeting.\",\n                wrap=True,\n                weight=\"Bolder\",\n            )\n        ]\n    )\n\n    await ctx.send(card)\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/meeting-events/typescript.incl.md",
    "content": "<!-- meeting-start -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { AdaptiveCard, TextBlock, OpenUrlAction, ActionSet } from '@microsoft/teams.cards';\n\nconst app = new App();\n\napp.on('meetingStart', async ({ activity, send }) => {\n  const meetingData = activity.value;\n  const startTime = new Date(meetingData.StartTime).toLocaleString();\n\n  const card = new AdaptiveCard(\n    new TextBlock(`'${meetingData.Title}' has started at ${startTime}.`, {\n      wrap: true,\n      weight: 'Bolder'\n    }),\n    new ActionSet(\n      new OpenUrlAction(meetingData.JoinUrl).withTitle('Join the meeting')\n    )\n  );\n\n  await send(card);\n});\n```\n\n<!-- meeting-end -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n\nconst app = new App();\n\napp.on('meetingEnd', async ({ activity, send }) => {\n  const meetingData = activity.value;\n  const endTime = new Date(meetingData.EndTime).toLocaleString();\n\n  const card = new AdaptiveCard(\n    new TextBlock(`'${meetingData.Title}' has ended at ${endTime}.`, {\n      wrap: true,\n      weight: 'Bolder'\n    })\n  );\n\n  await send(card);\n});\n```\n\n<!-- participant-join -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n\nconst app = new App();\n\napp.on('meetingParticipantJoin', async ({ activity, send }) => {\n  const meetingData = activity.value;\n  const member = meetingData.members[0].user.name;\n  const role = meetingData.members[0].meeting.role;\n\n  const card = new AdaptiveCard(\n    new TextBlock(`${member} has joined the meeting as ${role}.`, {\n      wrap: true,\n      weight: 'Bolder'\n    })\n  );\n\n  await send(card);\n});\n```\n\n<!-- participant-leave -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n\nconst app = new App();\n\napp.on('meetingParticipantLeave', async ({ activity, send }) => {\n  const meetingData = activity.value;\n  const member = meetingData.members[0].user.name;\n\n  const card = new AdaptiveCard(\n    new TextBlock(`${member} has left the meeting.`, {\n      wrap: true,\n      weight: 'Bolder'\n    })\n  );\n\n  await send(card);\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/action-commands/csharp.incl.md",
    "content": "<!-- handle-submission-intro -->\n\nHandle submission when the `createCard` or `getMessageDetails` actions commands are invoked.\n\n<!-- handle-submission-code -->\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api.Activities.Invokes.MessageExtensions;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Apps.Annotations;\n\n//...\n\n[MessageExtension.SubmitAction]\npublic Response OnMessageExtensionSubmit(\n    [Context] SubmitActionActivity activity,\n    [Context] IContext.Client client,\n    [Context] ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_SUBMIT] Action submit received\");\n\n    var commandId = activity.Value?.CommandId;\n    var data = activity.Value?.Data as JsonElement?;\n\n    log.Info($\"[MESSAGE_EXT_SUBMIT] Command: {commandId}\");\n    log.Info($\"[MESSAGE_EXT_SUBMIT] Data: {JsonSerializer.Serialize(data)}\");\n\n    switch (commandId)\n    {\n        case \"createCard\":\n            return HandleCreateCard(data, log);\n\n        case \"getMessageDetails\":\n            return HandleGetMessageDetails(activity, log);\n\n        default:\n            log.Error($\"[MESSAGE_EXT_SUBMIT] Unknown command: {commandId}\");\n            return CreateErrorActionResponse(\"Unknown command\");\n    }\n}\n```\n\n<!-- create-card-function -->\n\n`HandleCreateCard()` method\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Cards;\nusing Microsoft.Teams.Common;\n\n//...\n\nprivate static Response HandleCreateCard(JsonElement? data, ILogger log)\n{\n    var title = GetJsonValue(data, \"title\") ?? \"Default Title\";\n    var description = GetJsonValue(data, \"description\") ?? \"Default Description\";\n\n    log.Info($\"[CREATE_CARD] Title: {title}, Description: {description}\");\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Custom Card Created\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Large,\n                Color = TextColor.Good\n            },\n            new TextBlock(title)\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Medium\n            },\n            new TextBlock(description)\n            {\n                Wrap = true,\n                IsSubtle = true\n            }\n        }\n    };\n\n    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment\n    {\n        ContentType = ContentType.AdaptiveCard,\n        Content = card\n    };\n\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Result,\n            AttachmentLayout = Layout.List,\n            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }\n        }\n    };\n}\n```\n\n<!-- create-message-details-function -->\n\n`HandleGetMessageDetails()` method\n\n```csharp\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.Activities.Invokes.MessageExtensions;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Cards;\n\n//...\n\nprivate static Response HandleGetMessageDetails(SubmitActionActivity activity, ILogger log)\n{\n    var messageText = activity.Value?.MessagePayload?.Body?.Content ?? \"No message content\";\n    var messageId = activity.Value?.MessagePayload?.Id ?? \"Unknown\";\n\n    log.Info($\"[GET_MESSAGE_DETAILS] Message ID: {messageId}\");\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Message Details\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Large,\n                Color = TextColor.Accent\n            },\n            new TextBlock($\"Message ID: {messageId}\")\n            {\n                Wrap = true\n            },\n            new TextBlock($\"Content: {messageText}\")\n            {\n                Wrap = true\n            }\n        }\n    };\n\n    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment\n    {\n        ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n        Content = card\n    };\n\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Result,\n            AttachmentLayout = Layout.List,\n            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }\n        }\n    };\n}\n```\n\n<!-- handle-dialog-intro -->\n\nHandle opening adaptive card dialog when the `fetchConversationMembers` command is invoked.\n\n<!-- handle-dialog-code -->\n\n```csharp\nusing Microsoft.Teams.Api.Activities.Invokes.MessageExtensions;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Apps.Annotations;\n\n//...\n\n[MessageExtension.FetchTask]\npublic async Task<ActionResponse> OnMessageExtensionFetchTask(\n    [Context] FetchTaskActivity activity,\n    [Context] ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_FETCH_TASK] Fetch task received\");\n\n    var commandId = activity.Value?.CommandId;\n    log.Info($\"[MESSAGE_EXT_FETCH_TASK] Command: {commandId}\");\n\n    return CreateFetchTaskResponse(commandId, log);\n}\n```\n\n<!-- create-conversation-members-function -->\n\n`CreateFetchTaskResponse()` method\n\n```csharp\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Api.TaskModules;\nusing Microsoft.Teams.Cards;\nusing Microsoft.Teams.Common;\n\n//...\n\nprivate static ActionResponse CreateFetchTaskResponse(string? commandId, ILogger log)\n{\n    log.Info($\"[CREATE_FETCH_TASK] Creating task for command: {commandId}\");\n\n    // Create an adaptive card for the task module\n    var card = new AdaptiveCard\n    {\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Conversation Members is not implemented in C# yet :(\")\n            {\n                Weight = TextWeight.Bolder,\n                Color = TextColor.Accent\n            },\n        }\n    };\n\n    return new ActionResponse\n    {\n        Task = new ContinueTask(new TaskInfo\n        {\n            Title = \"Fetch Task Dialog\",\n            Height = new Union<int, Size>(Size.Small),\n            Width = new Union<int, Size>(Size.Small),\n            Card = new Microsoft.Teams.Api.Attachment(card)\n        })\n    };\n}\n\n// Helper method to extract JSON values\nprivate static string? GetJsonValue(JsonElement? data, string key)\n{\n    if (data?.ValueKind == JsonValueKind.Object && data.Value.TryGetProperty(key, out var value))\n    {\n        return value.GetString();\n    }\n    return null;\n}\n\n// Helper method to create error responses\nprivate static Response CreateErrorActionResponse(string message)\n{\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Message,\n            Text = message\n        }\n    };\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/action-commands/python.incl.md",
    "content": "<!-- handle-submission-intro -->\n\nHandle submission when the `createCard` or `getMessageDetails` actions commands are invoked.\n\n<!-- handle-submission-code -->\n\n```python\nfrom microsoft_teams.api import AdaptiveCardAttachment, MessageExtensionSubmitActionInvokeActivity, card_attachment\nfrom microsoft_teams.api.models import AttachmentLayout, MessagingExtensionActionInvokeResponse, MessagingExtensionAttachment, MessagingExtensionResult, MessagingExtensionResultType\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_message_ext_submit\nasync def handle_message_ext_submit(ctx: ActivityContext[MessageExtensionSubmitActionInvokeActivity]):\n    command_id = ctx.activity.value.command_id\n\n    if command_id == \"createCard\":\n        card = create_card(ctx.activity.value.data or {})\n    elif command_id == \"getMessageDetails\" and ctx.activity.value.message_payload:\n        card = create_message_details_card(ctx.activity.value.message_payload)\n    else:\n        raise Exception(f\"Unknown commandId: {command_id}\")\n\n    main_attachment = card_attachment(AdaptiveCardAttachment(content=card))\n    attachment = MessagingExtensionAttachment(\n        content_type=main_attachment.content_type, content=main_attachment.content\n    )\n\n    result = MessagingExtensionResult(\n        type=MessagingExtensionResultType.RESULT, attachment_layout=AttachmentLayout.LIST, attachments=[attachment]\n    )\n\n    return MessagingExtensionActionInvokeResponse(compose_extension=result)\n```\n\n<!-- create-card-function -->\n\n`create_card()` method\n\n```py\nfrom typing import Dict\nfrom microsoft_teams.cards import AdaptiveCard\n# ...\n\ndef create_card(data: Dict[str, str]) -> AdaptiveCard:\n    \"\"\"Create an adaptive card from form data.\"\"\"\n    return AdaptiveCard.model_validate(\n        {\n            \"type\": \"AdaptiveCard\",\n            \"version\": \"1.4\",\n            \"body\": [\n                {\"type\": \"Image\", \"url\": IMAGE_URL},\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": data.get(\"title\", \"\"),\n                    \"size\": \"Large\",\n                    \"weight\": \"Bolder\",\n                    \"color\": \"Accent\",\n                    \"style\": \"heading\",\n                },\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": data.get(\"subTitle\", \"\"),\n                    \"size\": \"Small\",\n                    \"weight\": \"Lighter\",\n                    \"color\": \"Good\",\n                },\n                {\"type\": \"TextBlock\", \"text\": data.get(\"text\", \"\"), \"wrap\": True, \"spacing\": \"Medium\"},\n            ],\n        }\n    )\n\n```\n\n<!-- create-message-details-function -->\n\n`create_message_details_card()` method\n\n```python\nfrom typing import Dict, List, Union\nfrom microsoft_teams.api.models.message import Message\nfrom microsoft_teams.cards import AdaptiveCard\n# ...\n\ndef create_message_details_card(message_payload: Message) -> AdaptiveCard:\n    \"\"\"Create a card showing message details.\"\"\"\n    body: List[Dict[str, Union[str, bool]]] = [\n        {\n            \"type\": \"TextBlock\",\n            \"text\": \"Message Details\",\n            \"size\": \"Large\",\n            \"weight\": \"Bolder\",\n            \"color\": \"Accent\",\n            \"style\": \"heading\",\n        }\n    ]\n\n    if message_payload.body and message_payload.body.content:\n        content_blocks: List[Dict[str, Union[str, bool]]] = [\n            {\"type\": \"TextBlock\", \"text\": \"Content\", \"size\": \"Medium\", \"weight\": \"Bolder\", \"spacing\": \"Medium\"},\n            {\"type\": \"TextBlock\", \"text\": message_payload.body.content},\n        ]\n        body.extend(content_blocks)\n\n    if message_payload.attachments:\n        attachment_blocks: List[Dict[str, Union[str, bool]]] = [\n            {\"type\": \"TextBlock\", \"text\": \"Attachments\", \"size\": \"Medium\", \"weight\": \"Bolder\", \"spacing\": \"Medium\"},\n            {\n                \"type\": \"TextBlock\",\n                \"text\": f\"Number of attachments: {len(message_payload.attachments)}\",\n                \"wrap\": True,\n                \"spacing\": \"Small\",\n            },\n        ]\n        body.extend(attachment_blocks)\n\n    if message_payload.created_date_time:\n        date_blocks: List[Dict[str, Union[str, bool]]] = [\n            {\"type\": \"TextBlock\", \"text\": \"Created Date\", \"size\": \"Medium\", \"weight\": \"Bolder\", \"spacing\": \"Medium\"},\n            {\"type\": \"TextBlock\", \"text\": message_payload.created_date_time, \"wrap\": True, \"spacing\": \"Small\"},\n        ]\n        body.extend(date_blocks)\n\n    if message_payload.link_to_message:\n        link_blocks: List[Dict[str, Union[str, bool]]] = [\n            {\"type\": \"TextBlock\", \"text\": \"Message Link\", \"size\": \"Medium\", \"weight\": \"Bolder\", \"spacing\": \"Medium\"}\n        ]\n        body.extend(link_blocks)\n\n        actions = [{\"type\": \"Action.OpenUrl\", \"title\": \"Go to message\", \"url\": message_payload.link_to_message}]\n    else:\n        actions = []\n\n    return AdaptiveCard.model_validate({\"type\": \"AdaptiveCard\", \"version\": \"1.4\", \"body\": body, \"actions\": actions})\n```\n\n<!-- handle-dialog-intro -->\n\nHandle opening adaptive card dialog when the `fetchConversationMembers` command is invoked.\n\n<!-- handle-dialog-code -->\n\n```python\nfrom microsoft_teams.api import AdaptiveCardAttachment, MessageExtensionFetchTaskInvokeActivity, card_attachment\nfrom microsoft_teams.api.models import CardTaskModuleTaskInfo, MessagingExtensionActionInvokeResponse, TaskModuleContinueResponse\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_message_ext_open\nasync def handle_message_ext_open(ctx: ActivityContext[MessageExtensionFetchTaskInvokeActivity]):\n    conversation_id = ctx.activity.conversation.id\n    members = await ctx.api.conversations.members(conversation_id).get_all()\n    card = create_conversation_members_card(members)\n\n    card_info = CardTaskModuleTaskInfo(\n        title=\"Conversation members\",\n        height=\"small\",\n        width=\"small\",\n        card=card_attachment(AdaptiveCardAttachment(content=card)),\n    )\n\n    task = TaskModuleContinueResponse(value=card_info)\n\n    return MessagingExtensionActionInvokeResponse(task=task)\n```\n\n<!-- create-conversation-members-function -->\n\n`create_conversation_members_card()` method\n\n```python\nfrom typing import List\nfrom microsoft_teams.api import Account\nfrom microsoft_teams.cards import AdaptiveCard\n# ...\n\ndef create_conversation_members_card(members: List[Account]) -> AdaptiveCard:\n    \"\"\"Create a card showing conversation members.\"\"\"\n    members_list = \", \".join(member.name for member in members if member.name)\n\n    return AdaptiveCard.model_validate(\n        {\n            \"type\": \"AdaptiveCard\",\n            \"version\": \"1.4\",\n            \"body\": [\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": \"Conversation members\",\n                    \"size\": \"Medium\",\n                    \"weight\": \"Bolder\",\n                    \"color\": \"Accent\",\n                    \"style\": \"heading\",\n                },\n                {\"type\": \"TextBlock\", \"text\": members_list, \"wrap\": True, \"spacing\": \"Small\"},\n            ],\n        }\n    )\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/action-commands/typescript.incl.md",
    "content": "<!-- handle-submission-intro -->\n\nHandle submission when the `createCard` or `getMessageDetails` action commands are invoked.\n\n<!-- handle-submission-code -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\nimport { IAdaptiveCard } from '@microsoft/teams.cards';\n// ...\n\napp.on('message.ext.submit', async ({ activity }) => {\n  const { commandId } = activity.value;\n  let card: IAdaptiveCard;\n\n  if (commandId === 'createCard') {\n    // The activity.value.commandContext == \"compose\" here because it was from\n    // the compose box\n    card = createCard(activity.value.data);\n  } else if (commandId === 'getMessageDetails' && activity.value.messagePayload) {\n    // The activity.value.commandContext == \"message\" here because it was from\n    // the message context\n    card = createMessageDetailsCard(activity.value.messagePayload);\n  } else {\n    throw new Error(`Unknown commandId: ${commandId}`);\n  }\n\n  return {\n    composeExtension: {\n      type: 'result',\n      attachmentLayout: 'list',\n      attachments: [cardAttachment('adaptive', card)],\n    },\n  };\n});\n```\n\n<!-- create-card-function -->\n\n`createCard()` function\n\n```typescript\nimport { AdaptiveCard, TextBlock, Image } from '@microsoft/teams.cards';\n// ...\n\ninterface IFormData {\n  title: string;\n  subtitle: string;\n  text: string;\n}\n\nexport function createCard(data: IFormData) {\n  return new AdaptiveCard(\n    new Image(IMAGE_URL),\n    new TextBlock(data.title, {\n      size: 'Large',\n      weight: 'Bolder',\n      color: 'Accent',\n      style: 'heading',\n    }),\n    new TextBlock(data.subtitle, {\n      size: 'Small',\n      weight: 'Lighter',\n      color: 'Good',\n    }),\n    new TextBlock(data.text, {\n      wrap: true,\n      spacing: 'Medium',\n    })\n  );\n}\n```\n\n<!-- create-message-details-function -->\n\n`createMessageDetailsCard()` function\n\n```typescript\nimport { Message } from '@microsoft/teams.api';\nimport {\n  AdaptiveCard,\n  CardElement,\n  TextBlock,\n  ActionSet,\n  OpenUrlAction,\n} from '@microsoft/teams.cards';\n// ...\n\nexport function createMessageDetailsCard(messagePayload: Message) {\n  const cardElements: CardElement[] = [\n    new TextBlock('Message Details', {\n      size: 'Large',\n      weight: 'Bolder',\n      color: 'Accent',\n      style: 'heading',\n    }),\n  ];\n\n  if (messagePayload?.body?.content) {\n    cardElements.push(\n      new TextBlock('Content', {\n        size: 'Medium',\n        weight: 'Bolder',\n        spacing: 'Medium',\n      }),\n      new TextBlock(messagePayload.body.content)\n    );\n  }\n\n  if (messagePayload?.attachments?.length) {\n    cardElements.push(\n      new TextBlock('Attachments', {\n        size: 'Medium',\n        weight: 'Bolder',\n        spacing: 'Medium',\n      }),\n      new TextBlock(`Number of attachments: ${messagePayload.attachments.length}`, {\n        wrap: true,\n        spacing: 'Small',\n      })\n    );\n  }\n\n  if (messagePayload?.createdDateTime) {\n    cardElements.push(\n      new TextBlock('Created Date', {\n        size: 'Medium',\n        weight: 'Bolder',\n        spacing: 'Medium',\n      }),\n      new TextBlock(messagePayload.createdDateTime, {\n        wrap: true,\n        spacing: 'Small',\n      })\n    );\n  }\n\n  if (messagePayload?.linkToMessage) {\n    cardElements.push(\n      new TextBlock('Message Link', {\n        size: 'Medium',\n        weight: 'Bolder',\n        spacing: 'Medium',\n      }),\n      new ActionSet(\n        new OpenUrlAction(messagePayload.linkToMessage, {\n          title: 'Go to message',\n        })\n      )\n    );\n  }\n\n  return new AdaptiveCard(...cardElements);\n}\n```\n\n<!-- handle-dialog-intro -->\n\nHandle opening adaptive card dialog when the `fetchConversationMembers` command is invoked.\n\n<!-- handle-dialog-code -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message.ext.open', async ({ activity, api }) => {\n  const conversationId = activity.conversation.id;\n  const members = await api.conversations.members(conversationId).get();\n  const card = createConversationMembersCard(members);\n\n  return {\n    task: {\n      type: 'continue',\n      value: {\n        title: 'Conversation members',\n        height: 'small',\n        width: 'small',\n        card: cardAttachment('adaptive', card),\n      },\n    },\n  };\n});\n```\n\n<!-- create-conversation-members-function -->\n\n`createConversationMembersCard()` function\n\n```typescript\nimport { Account } from '@microsoft/teams.api';\nimport { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n// ...\n\nexport function createConversationMembersCard(members: Account[]) {\n  const membersList = members.map((member) => member.name).join(', ');\n\n  return new AdaptiveCard(\n    new TextBlock('Conversation members', {\n      size: 'Medium',\n      weight: 'Bolder',\n      color: 'Accent',\n      style: 'heading',\n    }),\n    new TextBlock(membersList, {\n      wrap: true,\n      spacing: 'Small',\n    })\n  );\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/link-unfurling/csharp.incl.md",
    "content": "<!-- handle-link-unfurling-code -->\n\n```csharp\nusing Microsoft.Teams.Api.Activities.Invokes.MessageExtensions;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Apps.Annotations;\n\n//...\n\n[MessageExtension.QueryLink]\npublic Response OnMessageExtensionQueryLink(\n    [Context] QueryLinkActivity activity,\n    [Context] IContext.Client client,\n    [Context] ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_QUERY_LINK] Link unfurling received\");\n\n    var url = activity.Value?.Url;\n    log.Info($\"[MESSAGE_EXT_QUERY_LINK] URL: {url}\");\n\n    if (string.IsNullOrEmpty(url))\n    {\n        return CreateErrorResponse(\"No URL provided\");\n    }\n\n    return CreateLinkUnfurlResponse(url, log);\n}\n```\n\n<!-- create-link-unfurl-function -->\n\n`CreateLinkUnfurlResponse()` method\n\n```csharp\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Cards;\n\n//...\n\nprivate static Response CreateLinkUnfurlResponse(string url, ILogger log)\n{\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Link Preview\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Medium\n            },\n            new TextBlock($\"URL: {url}\")\n            {\n                IsSubtle = true,\n                Wrap = true\n            },\n            new TextBlock(\"This is a preview of the linked content generated by the message extension.\")\n            {\n                Wrap = true,\n                Size = TextSize.Small\n            }\n        }\n    };\n\n    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment\n    {\n        ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n        Content = card\n    };\n\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Result,\n            AttachmentLayout = Layout.List,\n            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }\n        }\n    };\n}\n\n// Helper method to create error responses\nprivate static Response CreateErrorResponse(string message)\n{\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Message,\n            Text = message\n        }\n    };\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/link-unfurling/python.incl.md",
    "content": "<!-- handle-link-unfurling-code -->\n\n```python\nfrom microsoft_teams.api import (\n    AdaptiveCardAttachment,\n    MessageExtensionQueryLinkInvokeActivity,\n    ThumbnailCardAttachment,\n    card_attachment,\n    InvokeResponse,\n    AttachmentLayout,\n    MessagingExtensionAttachment,\n    MessagingExtensionInvokeResponse,\n    MessagingExtensionResult,\n    MessagingExtensionResultType,\n)\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_message_ext_query_link\nasync def handle_message_ext_query_link(ctx: ActivityContext[MessageExtensionQueryLinkInvokeActivity]):\n    url = ctx.activity.value.url\n\n    if not url:\n        return InvokeResponse[MessagingExtensionInvokeResponse](status=400)\n\n    card_data = create_link_unfurl_card(url)\n    main_attachment = card_attachment(AdaptiveCardAttachment(content=card_data[\"card\"]))\n    preview_attachment = card_attachment(ThumbnailCardAttachment(content=card_data[\"thumbnail\"]))\n\n    attachment = MessagingExtensionAttachment(\n        content_type=main_attachment.content_type,\n        content=main_attachment.content,\n        preview=preview_attachment,\n    )\n\n    result = MessagingExtensionResult(\n        type=MessagingExtensionResultType.RESULT,\n        attachment_layout=AttachmentLayout.LIST,\n        attachments=[attachment],\n    )\n\n    return MessagingExtensionInvokeResponse(compose_extension=result)\n```\n\n<!-- create-link-unfurl-function -->\n\n`create_link_unfurl_card()` function\n\n```python\nfrom typing import Any, Dict\nfrom microsoft_teams.cards import AdaptiveCard\n# ...\n\ndef create_link_unfurl_card(url: str) -> Dict[str, Any]:\n    \"\"\"Create a card for link unfurling.\"\"\"\n    thumbnail = {\n        \"title\": \"Unfurled Link\",\n        \"text\": url,\n        \"images\": [{\"url\": IMAGE_URL}],\n    }\n\n    card = AdaptiveCard.model_validate(\n        {\n            \"type\": \"AdaptiveCard\",\n            \"version\": \"1.4\",\n            \"body\": [\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": \"Unfurled Link\",\n                    \"size\": \"Large\",\n                    \"weight\": \"Bolder\",\n                    \"color\": \"Accent\",\n                    \"style\": \"heading\",\n                },\n                {\n                    \"type\": \"TextBlock\",\n                    \"text\": url,\n                    \"size\": \"Small\",\n                    \"weight\": \"Lighter\",\n                    \"color\": \"Good\",\n                },\n            ],\n        }\n    )\n\n    return {\"card\": card, \"thumbnail\": thumbnail}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/link-unfurling/typescript.incl.md",
    "content": "<!-- handle-link-unfurling-code -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\nimport { IAdaptiveCard } from '@microsoft/teams.cards';\n// ...\n\napp.on('message.ext.query-link', async ({ activity }) => {\n  const { url } = activity.value;\n\n  if (!url) {\n    return { status: 400 };\n  }\n\n  const { card, thumbnail } = createLinkUnfurlCard(url);\n  const attachment = {\n    ...cardAttachment('adaptive', card), // expanded card in the compose box...\n    preview: cardAttachment('thumbnail', thumbnail), //preview card in the compose box...\n  };\n\n  return {\n    composeExtension: {\n      type: 'result',\n      attachmentLayout: 'list',\n      attachments: [attachment],\n    },\n  };\n});\n```\n\n<!-- create-link-unfurl-function -->\n\n`createLinkUnfurlCard()` function\n\n```typescript\nimport { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\nimport { ThumbnailCard } from '@microsoft/teams.api';\n// ...\n\nexport function createLinkUnfurlCard(url: string) {\n  const thumbnail = {\n    title: 'Unfurled Link',\n    text: url,\n    images: [\n      {\n        url: IMAGE_URL,\n      },\n    ],\n  } as ThumbnailCard;\n\n  const card = new AdaptiveCard(\n    new TextBlock('Unfurled Link', {\n      size: 'Large',\n      weight: 'Bolder',\n      color: 'Accent',\n      style: 'heading',\n    }),\n    new TextBlock(url, {\n      size: 'Small',\n      weight: 'Lighter',\n      color: 'Good',\n    })\n  );\n\n  return {\n    card,\n    thumbnail,\n  };\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/search-commands/csharp.incl.md",
    "content": "<!-- handle-submission-code -->\n\n```csharp\nusing Microsoft.Teams.Api.Activities.Invokes.MessageExtensions;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Apps.Annotations;\n\n//...\n\n[MessageExtension.Query]\npublic Response OnMessageExtensionQuery(\n    [Context] QueryActivity activity,\n    [Context] IContext.Client client,\n    [Context] ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_QUERY] Search query received\");\n\n    var commandId = activity.Value?.CommandId;\n    var query = activity.Value?.Parameters?.FirstOrDefault(p => p.Name == \"searchQuery\")?.Value?.ToString() ?? \"\";\n\n    log.Info($\"[MESSAGE_EXT_QUERY] Command: {commandId}, Query: {query}\");\n\n    if (commandId == \"searchQuery\")\n    {\n        return CreateSearchResults(query, log);\n    }\n\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Result,\n            AttachmentLayout = Layout.List,\n            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>()\n        }\n    };\n}\n```\n\n<!-- create-dummy-cards-function -->\n\n`CreateSearchResults()` method\n\n```csharp\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Cards;\nusing Microsoft.Teams.Common;\n\n//...\n\nprivate static Response CreateSearchResults(string query, ILogger log)\n{\n    var attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>();\n\n    // Create simple search results\n    for (int i = 1; i <= 5; i++)\n    {\n        var card = new AdaptiveCard\n        {\n            Body = new List<CardElement>\n            {\n                new TextBlock($\"Search Result {i}\")\n                {\n                    Weight = TextWeight.Bolder,\n                    Size = TextSize.Large\n                },\n                new TextBlock($\"Query: '{query}' - Result description for item {i}\")\n                {\n                    Wrap = true,\n                    IsSubtle = true\n                }\n            }\n        };\n\n        var previewCard = new ThumbnailCard()\n        {\n            Title = $\"Result {i}\",\n            Text = $\"This is a preview of result {i} for query '{query}'.\"\n        };\n\n        var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment\n        {\n            ContentType = ContentType.AdaptiveCard,\n            Content = card,\n            Preview = new Microsoft.Teams.Api.MessageExtensions.Attachment\n            {\n                ContentType = ContentType.ThumbnailCard,\n                Content = previewCard\n            }\n        };\n\n        attachments.Add(attachment);\n    }\n\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Result,\n            AttachmentLayout = Layout.List,\n            Attachments = attachments\n        }\n    };\n}\n```\n\nTo implement custom actions when a user clicks on a search result item, you can handle the select item event:\n\n```csharp\nusing System.Text.Json;\nusing Microsoft.Teams.Api;\nusing Microsoft.Teams.Api.Activities.Invokes.MessageExtensions;\nusing Microsoft.Teams.Api.MessageExtensions;\nusing Microsoft.Teams.Apps.Annotations;\nusing Microsoft.Teams.Cards;\n\n//...\n\n[MessageExtension.SelectItem]\npublic Response OnMessageExtensionSelectItem(\n    [Context] SelectItemActivity activity,\n    [Context] IContext.Client client,\n    [Context] ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_SELECT_ITEM] Item selection received\");\n\n    var selectedItem = activity.Value;\n    log.Info($\"[MESSAGE_EXT_SELECT_ITEM] Selected: {JsonSerializer.Serialize(selectedItem)}\");\n\n    return CreateItemSelectionResponse(selectedItem, log);\n}\n\n// Helper method to create item selection response\nprivate static Response CreateItemSelectionResponse(object? selectedItem, ILogger log)\n{\n    var itemJson = JsonSerializer.Serialize(selectedItem);\n\n    var card = new AdaptiveCard\n    {\n        Schema = \"http://adaptivecards.io/schemas/adaptive-card.json\",\n        Body = new List<CardElement>\n        {\n            new TextBlock(\"Item Selected\")\n            {\n                Weight = TextWeight.Bolder,\n                Size = TextSize.Large,\n                Color = TextColor.Good\n            },\n            new TextBlock(\"You selected the following item:\")\n            {\n                Wrap = true\n            },\n            new TextBlock(itemJson)\n            {\n                Wrap = true,\n                FontType = FontType.Monospace,\n                Separator = true\n            }\n        }\n    };\n\n    var attachment = new Microsoft.Teams.Api.MessageExtensions.Attachment\n    {\n        ContentType = new ContentType(\"application/vnd.microsoft.card.adaptive\"),\n        Content = card\n    };\n\n    return new Response\n    {\n        ComposeExtension = new Result\n        {\n            Type = ResultType.Result,\n            AttachmentLayout = Layout.List,\n            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment> { attachment }\n        }\n    };\n}\n```\n\n<!-- select-item-code -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/search-commands/python.incl.md",
    "content": "<!-- handle-submission-code -->\n\n```python\nfrom microsoft_teams.api import AdaptiveCardAttachment, MessageExtensionQueryInvokeActivity, ThumbnailCardAttachment, card_attachment, InvokeResponse, AttachmentLayout, MessagingExtensionAttachment, MessagingExtensionInvokeResponse, MessagingExtensionResult, MessagingExtensionResultType\n# ...\n\n@app.on_message_ext_query\nasync def handle_message_ext_query(ctx: ActivityContext[MessageExtensionQueryInvokeActivity]):\n    command_id = ctx.activity.value.command_id\n    search_query = \"\"\n    if ctx.activity.value.parameters and len(ctx.activity.value.parameters) > 0:\n        search_query = ctx.activity.value.parameters[0].value or \"\"\n\n    if command_id == \"searchQuery\":\n        cards = await create_dummy_cards(search_query)\n        attachments: list[MessagingExtensionAttachment] = []\n        for card_data in cards:\n            main_attachment = card_attachment(AdaptiveCardAttachment(content=card_data[\"card\"]))\n            preview_attachment = card_attachment(ThumbnailCardAttachment(content=card_data[\"thumbnail\"]))\n\n            attachment = MessagingExtensionAttachment(\n                content_type=main_attachment.content_type, content=main_attachment.content, preview=preview_attachment\n            )\n            attachments.append(attachment)\n\n        result = MessagingExtensionResult(\n            type=MessagingExtensionResultType.RESULT, attachment_layout=AttachmentLayout.LIST, attachments=attachments\n        )\n\n        return MessagingExtensionInvokeResponse(compose_extension=result)\n\n    return InvokeResponse[MessagingExtensionInvokeResponse](status=400)\n\n```\n\n<!-- create-dummy-cards-function -->\n\n`create_dummy_cards()` method\n\n```python\nfrom typing import Any, Dict, List\nfrom microsoft_teams.cards import AdaptiveCard\n# ...\n\nasync def create_dummy_cards(search_query: str) -> List[Dict[str, Any]]:\n    \"\"\"Create dummy cards for search results.\"\"\"\n    dummy_items = [\n        {\n            \"title\": \"Item 1\",\n            \"description\": f\"This is the first item and this is your search query: {search_query}\",\n        },\n        {\"title\": \"Item 2\", \"description\": \"This is the second item\"},\n        {\"title\": \"Item 3\", \"description\": \"This is the third item\"},\n        {\"title\": \"Item 4\", \"description\": \"This is the fourth item\"},\n        {\"title\": \"Item 5\", \"description\": \"This is the fifth item\"},\n    ]\n\n    cards: List[Dict[str, Any]] = []\n    for item in dummy_items:\n        card_data: Dict[str, Any] = {\n            \"card\": AdaptiveCard.model_validate(\n                {\n                    \"type\": \"AdaptiveCard\",\n                    \"version\": \"1.4\",\n                    \"body\": [\n                        {\n                            \"type\": \"TextBlock\",\n                            \"text\": item[\"title\"],\n                            \"size\": \"Large\",\n                            \"weight\": \"Bolder\",\n                            \"color\": \"Accent\",\n                            \"style\": \"heading\",\n                        },\n                        {\"type\": \"TextBlock\", \"text\": item[\"description\"], \"wrap\": True, \"spacing\": \"Medium\"},\n                    ],\n                }\n            ),\n            \"thumbnail\": {\n                \"title\": item[\"title\"],\n                \"text\": item[\"description\"],\n            },\n        }\n        cards.append(card_data)\n\n    return cards\n```\n\n<!-- select-item-code -->\n\n```python\nfrom microsoft_teams.api import MessageExtensionSelectItemInvokeActivity, AttachmentLayout, MessagingExtensionInvokeResponse, MessagingExtensionResult, MessagingExtensionResultType\nfrom microsoft_teams.apps import ActivityContext\n# ...\n\n@app.on_message_ext_select_item\nasync def handle_message_ext_select_item(ctx: ActivityContext[MessageExtensionSelectItemInvokeActivity]):\n    option = getattr(ctx.activity.value, \"option\", None)\n    await ctx.send(f\"Selected item: {option}\")\n\n    result = MessagingExtensionResult(\n        type=MessagingExtensionResultType.RESULT, attachment_layout=AttachmentLayout.LIST, attachments=[]\n    )\n\n    return MessagingExtensionInvokeResponse(compose_extension=result)\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/search-commands/typescript.incl.md",
    "content": "<!-- handle-submission-code -->\n\n```typescript\nimport { cardAttachment } from '@microsoft/teams.api';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message.ext.query', async ({ activity }) => {\n  const { commandId } = activity.value;\n  const searchQuery = activity.value.parameters![0].value;\n\n  if (commandId == 'searchQuery') {\n    const cards = await createDummyCards(searchQuery);\n    const attachments = cards.map(({ card, thumbnail }) => {\n      return {\n        ...cardAttachment('adaptive', card), // expanded card in the compose box...\n        preview: cardAttachment('thumbnail', thumbnail), // preview card in the compose box...\n      };\n    });\n\n    return {\n      composeExtension: {\n        type: 'result',\n        attachmentLayout: 'list',\n        attachments: attachments,\n      },\n    };\n  }\n\n  return { status: 400 };\n});\n```\n\n<!-- create-dummy-cards-function -->\n\n`createDummyCards()` function\n\n```typescript\nimport { ThumbnailCard } from '@microsoft/teams.api';\nimport { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n// ...\n\nexport async function createDummyCards(searchQuery: string) {\n  const dummyItems = [\n    {\n      title: 'Item 1',\n      description: `This is the first item and this is your search query: ${searchQuery}`,\n    },\n    { title: 'Item 2', description: 'This is the second item' },\n    { title: 'Item 3', description: 'This is the third item' },\n    { title: 'Item 4', description: 'This is the fourth item' },\n    { title: 'Item 5', description: 'This is the fifth item' },\n  ];\n\n  const cards = dummyItems.map((item) => {\n    return {\n      card: new AdaptiveCard(\n        new TextBlock(item.title, {\n          size: 'Large',\n          weight: 'Bolder',\n          color: 'Accent',\n          style: 'heading',\n        }),\n        new TextBlock(item.description, {\n          wrap: true,\n          spacing: 'Medium',\n        })\n      ),\n      thumbnail: {\n        title: item.title,\n        text: item.description,\n        // When a user clicks on a list item in Teams:\n        // - If the thumbnail has a `tap` property: Teams will trigger the `message.ext.select-item` activity\n        // - If no `tap` property: Teams will insert the full adaptive card into the compose box\n        // tap: {\n        //   type: \"invoke\",\n        //   title: item.title,\n        //   value: {\n        //     \"option\": index,\n        //   },\n        // },\n      } satisfies ThumbnailCard,\n    };\n  });\n\n  return cards;\n}\n```\n\n<!-- select-item-code -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message.ext.select-item', async ({ activity, send }) => {\n  const { option } = activity.value;\n\n  await send(`Selected item: ${option}`);\n\n  return {\n    status: 200,\n  };\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/settings/csharp.incl.md",
    "content": "<!-- html-code -->\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Message Extension Settings</title>\n    <link\n      rel=\"stylesheet\"\n      href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css\"\n    />\n    <script src=\"https://statics.teams.cdn.office.net/sdk/v1.11.0/js/MicrosoftTeams.min.js\"></script>\n    <style>\n      body {\n        margin: 0;\n        padding: 10px;\n      }\n      .form-group {\n        margin-bottom: 10px;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h3>Message Extension Settings</h3>\n      <form id=\"settingsForm\">\n        <div class=\"form-group\">\n          <label>Selected Option:</label>\n          <select class=\"form-control\" id=\"selectedOption\" name=\"selectedOption\">\n            <option value=\"\">Please select an option</option>\n            <option value=\"option1\">Option 1</option>\n            <option value=\"option2\">Option 2</option>\n            <option value=\"option3\">Option 3</option>\n          </select>\n        </div>\n        <button type=\"submit\" class=\"btn btn-primary\">Save Settings</button>\n      </form>\n    </div>\n\n    <script>\n      microsoftTeams.initialize();\n\n      // Get the selectedOption from URL parameters\n      const urlParams = new URLSearchParams(window.location.search);\n      const selectedOption = urlParams.get('selectedOption');\n      if (selectedOption) {\n        document.getElementById('selectedOption').value = selectedOption;\n      }\n\n      document.getElementById('settingsForm').addEventListener('submit', function (event) {\n        event.preventDefault();\n        let selectedValue = document.getElementById('selectedOption').value;\n        microsoftTeams.tasks.submitTask(selectedValue);\n      });\n    </script>\n  </body>\n</html>\n```\n\n<!-- serve-code -->\n\n```csharp\n// In your startup configuration (Program.cs or Startup.cs)\napp.UseStaticFiles();\napp.MapGet(\"/tabs/settings\", async context =>\n{\n    var html = await File.ReadAllTextAsync(\"wwwroot/settings.html\");\n    context.Response.ContentType = \"text/html\";\n    await context.Response.WriteAsync(html);\n});\n```\n\n<!-- tabs-note -->\n\n:::note\nThis will serve the HTML page to the `${BOT_ENDPOINT}/tabs/settings` endpoint as a tab. See [Tabs Guide](../tabs) to learn more.\n:::\n\n<!-- query-settings-code -->\n\n```csharp\nusing Microsoft.Teams.Api.Cards;\nusing Microsoft.Teams.Cards;\n\n[MessageExtension.QuerySettingsUrl]\npublic Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionQuerySettingsUrl(\n    [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.QuerySettingsUrlActivity activity,\n    [Context] IContext.Client client,\n    [Context] Microsoft.Teams.Common.Logging.ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_QUERY_SETTINGS_URL] Settings URL query received\");\n\n    // Get user settings (this could come from a database or user store)\n    var selectedOption = \"\"; // Default or retrieve from user preferences\n\n    var botEndpoint = Environment.GetEnvironmentVariable(\"BOT_ENDPOINT\") ?? \"https://your-bot-endpoint.com\";\n    var settingsUrl = $\"{botEndpoint}/tabs/settings?selectedOption={Uri.EscapeDataString(selectedOption)}\";\n\n    var settingsAction = new CardAction\n    {\n        Type = CardActionType.OpenUrl,\n        Title = \"Settings\",\n        Value = settingsUrl\n    };\n\n    var suggestedActions = new Microsoft.Teams.Api.MessageExtensions.SuggestedActions\n    {\n        Actions = new List<CardAction> { settingsAction }\n    };\n\n    var result = new Microsoft.Teams.Api.MessageExtensions.Result\n    {\n        Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Config,\n        SuggestedActions = suggestedActions\n    };\n\n    return new Microsoft.Teams.Api.MessageExtensions.Response\n    {\n        ComposeExtension = result\n    };\n}\n```\n\n<!-- handle-submission-code -->\n\n```csharp\n[MessageExtension.Setting]\npublic Microsoft.Teams.Api.MessageExtensions.Response OnMessageExtensionSetting(\n    [Context] Microsoft.Teams.Api.Activities.Invokes.MessageExtensions.SettingActivity activity,\n    [Context] IContext.Client client,\n    [Context] Microsoft.Teams.Common.Logging.ILogger log)\n{\n    log.Info(\"[MESSAGE_EXT_SETTING] Settings submission received\");\n\n    var state = activity.Value?.State;\n    log.Info($\"[MESSAGE_EXT_SETTING] State: {state}\");\n\n    if (state == \"CancelledByUser\")\n    {\n        log.Info(\"[MESSAGE_EXT_SETTING] User cancelled settings\");\n        return CreateEmptyResult();\n    }\n\n    var selectedOption = state;\n    log.Info($\"[MESSAGE_EXT_SETTING] Selected option: {selectedOption}\");\n\n    // Here you would typically save the user's settings to a database or user store\n    // SaveUserSettings(activity.From.Id, selectedOption);\n\n    // Return empty result to close the settings dialog\n    return CreateEmptyResult();\n}\n\n// Helper method to create empty result\nprivate static Microsoft.Teams.Api.MessageExtensions.Response CreateEmptyResult()\n{\n    return new Microsoft.Teams.Api.MessageExtensions.Response\n    {\n        ComposeExtension = new Microsoft.Teams.Api.MessageExtensions.Result\n        {\n            Type = Microsoft.Teams.Api.MessageExtensions.ResultType.Result,\n            AttachmentLayout = Microsoft.Teams.Api.Attachment.Layout.List,\n            Attachments = new List<Microsoft.Teams.Api.MessageExtensions.Attachment>()\n        }\n    };\n}\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/settings/python.incl.md",
    "content": "<!-- html-code -->\n\n```html\n<!DOCTYPE html>\n<html>\n  <head>\n    <title>Message Extension Settings</title>\n    <link\n      rel=\"stylesheet\"\n      href=\"https://stackpath.bootstrapcdn.com/bootstrap/4.5.0/css/bootstrap.min.css\"\n    />\n    <script src=\"https://statics.teams.cdn.office.net/sdk/v1.11.0/js/MicrosoftTeams.min.js\"></script>\n    <style>\n      body {\n        margin: 0;\n        padding: 10px;\n      }\n      .form-group {\n        margin-bottom: 10px;\n      }\n    </style>\n  </head>\n  <body>\n    <div class=\"container\">\n      <h3>Message Extension Settings</h3>\n      <form id=\"settingsForm\">\n        <div class=\"form-group\">\n          <label>Selected Option:</label>\n          <select class=\"form-control\" id=\"selectedOption\" name=\"selectedOption\">\n            <option value=\"\">Please select an option</option>\n            <option value=\"option1\">Option 1</option>\n            <option value=\"option2\">Option 2</option>\n            <option value=\"option3\">Option 3</option>\n          </select>\n        </div>\n        <button type=\"submit\" class=\"btn btn-primary\">Save Settings</button>\n      </form>\n    </div>\n\n    <script>\n      microsoftTeams.initialize();\n\n      // Get the selectedOption from URL parameters\n      const urlParams = new URLSearchParams(window.location.search);\n      const selectedOption = urlParams.get('selectedOption');\n      if (selectedOption) {\n        document.getElementById('selectedOption').value = selectedOption;\n      }\n\n      document.getElementById('settingsForm').addEventListener('submit', function (event) {\n        event.preventDefault();\n        let selectedValue = document.getElementById('selectedOption').value;\n        microsoftTeams.tasks.submitTask(selectedValue);\n      });\n    </script>\n  </body>\n</html>\n```\n\n<!-- serve-code -->\n\n```python\napp.page(\"settings\", str(Path(__file__).parent), \"/tabs/settings\")\n```\n\n<!-- query-settings-code -->\n\n```python\n@app.on_message_ext_query_settings_url\nasync def handle_message_ext_query_settings_url(ctx: ActivityContext[MessageExtensionQuerySettingUrlInvokeActivity]):\n    user_settings = {\"selectedOption\": \"\"}\n    escaped_selected_option = user_settings[\"selectedOption\"]\n\n    bot_endpoint = os.environ.get(\"BOT_ENDPOINT\", \"\")\n\n    settings_action = CardAction(\n        type=CardActionType.OPEN_URL,\n        title=\"Settings\",\n        value=f\"{bot_endpoint}/tabs/settings?selectedOption={escaped_selected_option}\",\n    )\n\n    suggested_actions = MessagingExtensionSuggestedAction(actions=[settings_action])\n\n    result = MessagingExtensionResult(type=MessagingExtensionResultType.CONFIG, suggested_actions=suggested_actions)\n\n    return MessagingExtensionInvokeResponse(compose_extension=result)\n```\n\n<!-- handle-submission-code -->\n\n```python\n@app.on_message_ext_setting\nasync def handle_message_ext_setting(ctx: ActivityContext[MessageExtensionSettingInvokeActivity]):\n    state = getattr(ctx.activity.value, \"state\", None)\n\n    if state == \"CancelledByUser\":\n        result = MessagingExtensionResult(\n            type=MessagingExtensionResultType.RESULT, attachment_layout=AttachmentLayout.LIST, attachments=[]\n        )\n        return MessagingExtensionInvokeResponse(compose_extension=result)\n\n    selected_option = state\n    await ctx.send(f\"Selected option: {selected_option}\")\n\n    result = MessagingExtensionResult(\n        type=MessagingExtensionResultType.RESULT, attachment_layout=AttachmentLayout.LIST, attachments=[]\n    )\n\n    return MessagingExtensionInvokeResponse(compose_extension=result)\n```\n\n<!-- tabs-note -->\n\n:::note\nThis will serve the HTML page to the `${BOT_ENDPOINT}/tabs/settings` endpoint as a tab.\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/message-extensions/settings/typescript.incl.md",
    "content": "<!-- html-code -->\n\n```html\n<html>\n  <body>\n    <form>\n      <fieldset>\n        <legend>What programming language do you prefer?</legend>\n        <input type=\"radio\" name=\"selectedOption\" value=\"typescript\" />Typescript<br />\n        <input type=\"radio\" name=\"selectedOption\" value=\"csharp\" />C#<br />\n      </fieldset>\n\n      <br />\n      <input type=\"button\" onclick=\"onSubmit()\" value=\"Save\" /> <br />\n    </form>\n\n    <script\n      src=\"https://res.cdn.office.net/teams-js/2.34.0/js/MicrosoftTeams.min.js\"\n      integrity=\"sha384-brW9AazbKR2dYw2DucGgWCCcmrm2oBFV4HQidyuyZRI/TnAkmOOnTARSTdps3Hwt\"\n      crossorigin=\"anonymous\"\n    ></script>\n\n    <script type=\"text/javascript\">\n      document.addEventListener('DOMContentLoaded', function () {\n        // Get the selected option from the URL\n        var urlParams = new URLSearchParams(window.location.search);\n        var selectedOption = urlParams.get('selectedOption');\n        if (selectedOption) {\n          var checkboxes = document.getElementsByName('selectedOption');\n          for (var i = 0; i < checkboxes.length; i++) {\n            var thisCheckbox = checkboxes[i];\n            if (selectedOption.includes(thisCheckbox.value)) {\n              checkboxes[i].checked = true;\n            }\n          }\n        }\n      });\n    </script>\n\n    <script type=\"text/javascript\">\n      // initialize the Teams JS SDK\n      microsoftTeams.app.initialize();\n\n      // Run when the user clicks the submit button\n      function onSubmit() {\n        var newSettings = '';\n\n        var checkboxes = document.getElementsByName('selectedOption');\n\n        for (var i = 0; i < checkboxes.length; i++) {\n          if (checkboxes[i].checked) {\n            newSettings = checkboxes[i].value;\n          }\n        }\n\n        // Closes the settings page and returns the selected option to the bot\n        microsoftTeams.authentication.notifySuccess(newSettings);\n      }\n    </script>\n  </body>\n</html>\n```\n\n<!-- serve-code -->\n\n```typescript\nimport path from 'path';\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.tab('settings', path.resolve(__dirname));\n```\n\n<!-- query-settings-code -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message.ext.query-settings-url', async ({ activity }) => {\n  // Get user settings from storage if available\n  const userSettings = (await app.storage.get(activity.from.id)) || { selectedOption: '' };\n  const escapedSelectedOption = encodeURIComponent(userSettings.selectedOption);\n\n  return {\n    composeExtension: {\n      type: 'config',\n      suggestedActions: {\n        actions: [\n          {\n            type: 'openUrl',\n            title: 'Settings',\n            // ensure the bot endpoint is set in the environment variables\n            // process.env.BOT_ENDPOINT is not populated by default in the Teams Toolkit setup.\n            value: `${process.env.BOT_ENDPOINT}/tabs/settings?selectedOption=${escapedSelectedOption}`,\n          },\n        ],\n      },\n    },\n  };\n});\n```\n\n<!-- handle-submission-code -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\n// ...\n\napp.on('message.ext.setting', async ({ activity, send }) => {\n  const { state } = activity.value;\n  if (state == 'CancelledByUser') {\n    return {\n      status: 400,\n    };\n  }\n  const selectedOption = state;\n\n  // Save the selected option to storage\n  await app.storage.set(activity.from.id, { selectedOption });\n\n  await send(`Selected option: ${selectedOption}`);\n\n  return {\n    status: 200,\n  };\n});\n```\n\n<!-- tabs-note -->\n\n:::note\nThis will serve the HTML page to the `${BOT_ENDPOINT}/tabs/settings` endpoint as a tab. See [Tabs Guide](../tabs) to learn more.\n:::\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/observability/logging/csharp.incl.md",
    "content": "<!-- default-logger -->\n\n`ConsoleLogger`\n\n<!-- package-name -->\n\n`Microsoft.Teams.Common`\n\n<!-- custom-logger-example -->\n\n```csharp\nusing Microsoft.Teams.Apps;\nusing Microsoft.Teams.Common.Logging;\nusing Microsoft.Teams.Plugins.AspNetCore.Extensions;\n\nvar builder = WebApplication.CreateBuilder(args);\n\nvar appBuilder = App.Builder()\n    .AddLogger(new ConsoleLogger())\n\nbuilder.AddTeams(appBuilder)\n\nvar app = builder.Build();\nvar teams = app.UseTeams();\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/observability/logging/python.incl.md",
    "content": "<!-- default-logger -->\n\n`ConsoleLogger`\n\n<!-- package-name -->\n\n`microsoft-teams-common`\n\n<!-- custom-logger-example -->\n\n```python\nimport asyncio\n\nfrom microsoft_teams.api import MessageActivity\nfrom microsoft_teams.api.activities.typing import TypingActivityInput\nfrom microsoft_teams.apps import ActivityContext, App\nfrom microsoft_teams.common import ConsoleLogger, ConsoleLoggerOptions\n\nlogger = ConsoleLogger().create_logger(\"echo\", ConsoleLoggerOptions(level=\"debug\"))\napp = App(logger=logger)\n\n@app.on_message\nasync def handle_message(ctx: ActivityContext[MessageActivity]):\n    logger.debug(ctx.activity)\n    await ctx.reply(TypingActivityInput())\n    await ctx.send(f\"You said '{ctx.activity.text}'\")\n\n\nif __name__ == \"__main__\":\n    asyncio.run(app.start())\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/observability/logging/typescript.incl.md",
    "content": "<!-- default-logger -->\n\n`ConsoleLogger`\n\n<!-- package-name -->\n\n`@microsoft/teams.common`\n\n<!-- custom-logger-example -->\n\n```typescript\nimport { App } from '@microsoft/teams.apps';\nimport { ConsoleLogger } from '@microsoft/teams.common';\n\n// initialize app with custom console logger\n// set to debug log level\nconst app = new App({\n  logger: new ConsoleLogger('echo', { level: 'debug' }),\n});\n\napp.on('message', async ({ send, activity, log }) => {\n  log.debug(activity);\n  await send({ type: 'typing' });\n  await send(`you said \"${activity.text}\"`);\n});\n\n(async () => {\n  await app.start();\n})();\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/observability/middleware/csharp.incl.md",
    "content": "<!-- app-use-method -->\n\n`app.Use`\n\n<!-- middleware-example -->\n\n```csharp\napp.Use(async context =>\n{\n    var start = DateTime.UtcNow;\n    try\n    {\n        await context.Next();\n    } catch\n    {\n        context.Log.Error(\"error occurred during activity processing\");\n    }\n    context.Log.Debug($\"request took {(DateTime.UtcNow - start).TotalMilliseconds}ms\");\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/observability/middleware/python.incl.md",
    "content": "<!-- app-use-method -->\n\n`app.use`\n\n<!-- middleware-example -->\n\n```python\n@app.use\nasync def log_activity(ctx: ActivityContext[MessageActivity]):\n    started_at = datetime.now()\n    await ctx.next()\n    ctx.logger.debug(f\"{datetime.now() - started_at}\")\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/observability/middleware/typescript.incl.md",
    "content": "<!-- app-use-method -->\n\n`app.use`\n\n<!-- middleware-example -->\n\n```typescript\napp.use(async ({ log, next }) => {\n  const startedAt = new Date();\n  await next();\n  log.debug(new Date().getTime() - startedAt.getTime());\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/python.incl.md",
    "content": "<!-- intro -->\n\nThis documentation covers advanced features and capabilities of the Teams SDK in Python.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/server/http-server/csharp.incl.md",
    "content": "<!-- adapter-interface -->\n\nN/A\n\n<!-- self-managed -->\n\nN/A\n\n<!-- custom-adapter -->\n\nN/A\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/server/http-server/python.incl.md",
    "content": "<!-- default-framework -->\n\n[FastAPI](https://fastapi.tiangolo.com/)\n\n<!-- adapter-interface -->\n\n```python\nclass HttpServerAdapter(Protocol):\n    def register_route(self, method: HttpMethod, path: str, handler: HttpRouteHandler) -> None: ...\n    def serve_static(self, path: str, directory: str) -> None: ...\n    async def start(self, port: int) -> None: ...\n    async def stop(self) -> None: ...\n\nclass HttpRouteHandler(Protocol):\n    async def __call__(self, request: HttpRequest) -> HttpResponse: ...\n```\n\n<!-- self-managed -->\n\n```python\nimport asyncio\nimport uvicorn\nfrom fastapi import FastAPI\nfrom microsoft_teams.apps import App, FastAPIAdapter\n\n# 1. Create your FastAPI app with your own routes\nmy_fastapi = FastAPI(title=\"My App + Teams Bot\")\n\n@my_fastapi.get(\"/health\")\nasync def health():\n    return {\"status\": \"healthy\"}\n\n# 2. Wrap it in the FastAPIAdapter\nadapter = FastAPIAdapter(app=my_fastapi)\n\n# 3. Create the Teams app with the adapter\napp = App(http_server_adapter=adapter)\n\n@app.on_message\nasync def handle_message(ctx):\n    await ctx.send(f\"Echo: {ctx.activity.text}\")\n\nasync def main():\n    # 4. Initialize — registers /api/messages on your FastAPI app (does NOT start a server)\n    await app.initialize()\n\n    # 5. Start the server yourself\n    config = uvicorn.Config(app=my_fastapi, host=\"0.0.0.0\", port=3978)\n    server = uvicorn.Server(config)\n    await server.serve()\n\nasyncio.run(main())\n```\n\n> See the full example: [FastAPI non-managed example](https://github.com/microsoft/teams.py/tree/main/examples/http-adapters/src/fastapi_non_managed.py)\n\n<!-- custom-adapter -->\n\nHere is a Starlette adapter — only `register_route` is needed:\n\n```python\nfrom starlette.applications import Starlette\nfrom starlette.requests import Request\nfrom starlette.responses import JSONResponse, Response\nfrom starlette.routing import Route\nfrom microsoft_teams.apps.http.adapter import HttpMethod, HttpRequest, HttpResponse, HttpRouteHandler\n\nclass StarletteAdapter:\n    def __init__(self, app: Starlette):\n        self._app = app\n\n    def register_route(self, method: HttpMethod, path: str, handler: HttpRouteHandler) -> None:\n        # Teams only sends POST requests to your bot endpoint\n        async def starlette_handler(request: Request) -> Response:\n            body = await request.json()\n            headers = dict(request.headers)\n            result: HttpResponse = await handler(HttpRequest(body=body, headers=headers))\n            if result.get(\"body\") is not None:\n                return JSONResponse(content=result[\"body\"], status_code=result[\"status\"])\n            return Response(status_code=result[\"status\"])\n\n        route = Route(path, starlette_handler, methods=[method])\n        self._app.routes.insert(0, route)\n```\n\nUsage:\n\n```python\nstarlette_app = Starlette()\nadapter = StarletteAdapter(starlette_app)\napp = App(http_server_adapter=adapter)\nawait app.initialize()\n# Start Starlette with uvicorn yourself\n```\n\n> See the full implementation: [Starlette adapter example](https://github.com/microsoft/teams.py/tree/main/examples/http-adapters/src/starlette_adapter.py)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/server/http-server/typescript.incl.md",
    "content": "<!-- default-framework -->\n\n[Express](https://expressjs.com/)\n\n<!-- adapter-interface -->\n\n```typescript\ninterface IHttpServerAdapter {\n  registerRoute(method: HttpMethod, path: string, handler: HttpRouteHandler): void;\n  serveStatic?(path: string, directory: string): void;\n  start?(port: number): Promise<void>;\n  stop?(): Promise<void>;\n}\n\ntype HttpRouteHandler = (request: { body: unknown; headers: Record<string, string | string[]> })\n  => Promise<{ status: number; body?: unknown }>;\n```\n\n<!-- self-managed -->\n\n```typescript\nimport http from 'http';\nimport express from 'express';\nimport { App, ExpressAdapter } from '@microsoft/teams.apps';\n\n// 1. Create your Express app with your own routes\nconst expressApp = express();\nconst httpServer = http.createServer(expressApp);\n\nexpressApp.get('/health', (_req, res) => {\n  res.json({ status: 'healthy' });\n});\n\n// 2. Wrap it in the ExpressAdapter\nconst adapter = new ExpressAdapter(httpServer);\n\n// 3. Create the Teams app with the adapter\nconst app = new App({ httpServerAdapter: adapter });\n\napp.on('message', async ({ send, activity }) => {\n  await send(`Echo: ${activity.text}`);\n});\n\n// 4. Initialize — registers /api/messages on your Express app (does NOT start a server)\nawait app.initialize();\n\n// 5. Start the server yourself\nhttpServer.listen(3978, () => console.log('Server ready on http://localhost:3978'));\n```\n\n> See the full [Express adapter example](https://github.com/microsoft/teams.ts/tree/main/examples/http-adapters/express)\n\n<!-- custom-adapter -->\n\nHere is a Restify adapter — only `registerRoute` is needed:\n\n```typescript\nimport restify from 'restify';\nimport { HttpMethod, IHttpServerAdapter, HttpRouteHandler } from '@microsoft/teams.apps';\n\nclass RestifyAdapter implements IHttpServerAdapter {\n  constructor(private server: restify.Server) {\n    this.server.use(restify.plugins.bodyParser());\n  }\n\n  registerRoute(method: HttpMethod, path: string, handler: HttpRouteHandler): void {\n    // Teams only sends POST requests to your bot endpoint\n    assert(method === 'POST', `Unsupported method: ${method}`);\n    this.server.post(path, async (req: restify.Request, res: restify.Response) => {\n      const response = await handler({\n        body: req.body,\n        headers: req.headers as Record<string, string | string[]>,\n      });\n      res.send(response.status, response.body);\n    });\n  }\n}\n```\n\nUsage:\n\n```typescript\nconst server = restify.createServer();\nconst adapter = new RestifyAdapter(server);\nconst app = new App({ httpServerAdapter: adapter });\nawait app.initialize();\nserver.listen(3978);\n```\n\n> See the full implementation: [Restify adapter example](https://github.com/microsoft/teams.ts/tree/main/examples/http-adapters/restify)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/server/static-pages/csharp.incl.md",
    "content": "<!-- method-name -->\n\n`app.AddTab()`\n\n<!-- code-example -->\n\n```csharp\napp.AddTab(\"myApp\", \"Web/bin\");\n```\n\n<!-- route-pattern -->\n\n`http://localhost:{PORT}/tabs/myApp` or `https://{BOT_DOMAIN}/tabs/myApp`\n\n<!-- additional-resources -->\n\n- For more details about Tab apps, see the [Tabs](../tabs/) in-depth guide.\n- For an example of hosting a Dialog, see the [Creating Dialogs](../dialogs/creating-dialogs) in-depth guide.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/server/static-pages/python.incl.md",
    "content": "<!-- method-name -->\n\n`app.tab()`\n\n<!-- code-example -->\n\n```python\napp.tab(\"my_app\", os.path.abspath(\"dist/client\"))\n```\n\n<!-- route-pattern -->\n\n`http://localhost:{PORT}/tabs/my_app` or `https://{BOT_DOMAIN}/tabs/my_app`\n\n<!-- additional-resources -->\n\n- For an example of hosting a Dialog, see the [Creating Dialogs](../dialogs/creating-dialogs) in-depth guide.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/server/static-pages/typescript.incl.md",
    "content": "<!-- method-name -->\n\n`app.tab()`\n\n<!-- code-example -->\n\n```typescript\napp.tab('myApp', path.resolve('dist/client'));\n```\n\n<!-- route-pattern -->\n\n`http://localhost:{PORT}/tabs/myApp` or `https://{BOT_DOMAIN}/tabs/myApp`\n\n<!-- additional-resources -->\n\n- For more details about Tab apps, see the [Tabs](../tabs) in-depth guide.\n- For an example of hosting a Dialog, see the [Creating Dialogs](../dialogs/creating-dialogs) in-depth guide.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/tabs/csharp.incl.md",
    "content": "<!-- overview -->\n\nThis SDK does not offer features for implementing Tab apps in C#. It does however let you host tab apps and implement functions that can be called by Tab apps.\n\n<!-- additional-resources -->\n\n### Additional resources\n\n- [Static Pages](../server/static-pages)\n- [TypeScript Tabs in-depth guide](../../../typescript/in-depth-guides/tabs)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/tabs/functions/csharp.incl.md",
    "content": "<!-- overview -->\n\nAgents may want to expose REST APIs that client applications can call. This SDK makes it easy to implement those APIs through the `app.AddFunction()` method. The function takes a name and a callback that implements the function.\n\n<!-- example -->\n\n```csharp\napp.AddFunction('do-something', (context) => {\n  // do something useful\n});\n```\n\nThis registers a REST API hosted at `http://localhost:{PORT}/api/functions/do-something` or `https://{BOT_DOMAIN}/api/functions/do-something` that clients can POST to. When they do, this SDK validates that the caller provides a valid Microsoft Entra bearer token before invoking the registered callback. If the token is missing or invalid, the request is denied with a HTTP 401.\n\nThe function can be typed to accept input arguments. The clients would include those in the POST request payload, and they are made available in the callback through the `Data` context argument.\n\n```csharp\npublic class ProcessMessageData\n{\n    [JsonPropertyName(\"message\")]\n    public required string Message { get; set; }\n}\n\n// ...\n\napp.AddFunction<ProcessMessageData> (\"process-message\", (context) => {\n    context.Log.Debug($\"process-message with: {context.Data.Message}\");\n});\n\n```\n\n<!-- validation-warning -->\n\n:::warning\nThis SDK does not validate that the function arguments are of the expected types or otherwise trustworthy. You must take care to validate the input arguments before using them.\n:::\n\n<!-- return-values -->\n\nIf desired, the function can return data to the caller.\n\n```csharp\napp.AddFunction('get-random-number', () => {\n    return 4; // chosen by fair dice roll;\n              // guaranteed to be random\n});\n```\n\n<!-- context-intro -->\n\nThe function callback receives a context object with a number of useful values. Some originate within the agent itself, while others are furnished by the caller via the HTTP Request.\n\n<!-- context-table -->\n\n| Property       | Source | Description                                                                                                        |\n| -------------- | ------ | ------------------------------------------------------------------------------------------------------------------ |\n| `Api`          | Agent  | The API client.                                                                                                    |\n| `AppId`        | Agent  | Unique identifier assigned to the app after deployment, ensuring correct app instance recognition across hosts.    |\n| `AppSessionId` | Caller | Unique ID for the calling app's session, used to correlate telemetry data.                                         |\n| `AuthToken`    | Caller | The validated MSAL Entra token.                                                                                    |\n| `ChannelId`    | Caller | Microsoft Teams ID for the channel associated with the content.                                                    |\n| `ChatId`       | Caller | Microsoft Teams ID for the chat associated with the content.                                                       |\n| `Data`         | Caller | The function payload.                                                                                              |\n| `Log`          | Agent  | The app logger instance.                                                                                           |\n| `MeetingId`    | Caller | Meeting ID used by tab when running in meeting context.                                                            |\n| `MessageId`    | Caller | ID of the parent message from which the task module was launched (only available in bot card-launched modules).    |\n| `PageId`       | Caller | Developer-defined unique ID for the page this content points to.                                                   |\n| `Send`         | Agent  | Sends an activity to the current conversation.                                                                     |\n| `SubPageId`    | Caller | Developer-defined unique ID for the sub-page this content points to. Used to restore specific state within a page. |\n| `TeamId`       | Caller | Microsoft Teams ID for the team associated with the content.                                                       |\n| `TenantId`     | Caller | Microsoft Entra tenant ID of the current user, extracted from the validated auth token.                            |\n| `UserId`       | Caller | Microsoft Entra object ID of the current user, extracted from the validated auth token.                            |\n| `UserName`     | Caller | Microsoft Entra name of the current user, extracted from the validated auth token.                                 |\n\n<!-- context-validation -->\n\nThe `AuthToken` is validated before the function callback is invoked, and the `TenantId`, `UserId`, and `UserName` values are extracted from the validated token. In the typical case, the remaining caller-supplied values would reflect what the Teams Tab app retrieves from the teams-js `getContext()` API, but the agent does not validate these.\n\n:::warning\nTake care to validate the caller-supplied values before using them. Don't assume that the calling user actually has access to items indicated in the context.\n:::\n\n<!-- context-helpers -->\n\nTo simplify a common scenarios, the context provides a `Send` method. This method sends an activity to the current conversation ID, determined from the context values provided by the client (chatId and channelId). If neither chatId or channelId is provided by the caller, the ID of the 1:1 conversation between the agent and the user is assumed.\n\n:::warning\nThe `Send` method does not validate that the chat ID or conversation ID provided by the caller is valid or correct. You must take care to validate that the user and agent both have appropriate access to the conversation.\n:::\n\n<!-- additional-resources -->\n\n- For details on how to Tab apps can call these functions, see the TypeScript [Executing Functions](../../../../typescript/in-depth-guides/tabs/functions/function-calling) in-depth guide.\n- For more information about the teams-js getContext() API, see the [Teams JavaScript client library](https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/using-teams-client-library) documentation.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/tabs/functions/typescript.incl.md",
    "content": "<!-- overview -->\n\nAgents may want to expose REST APIs that client applications can call. This SDK makes it easy to implement those APIs through the `app.function()` method. The function takes a name and a callback that implements the function.\n\n<!-- example -->\n\n```typescript\napp.function('do-something', () => {\n  // do something useful\n});\n```\n\nThis registers a REST API hosted at `http://localhost:{PORT}/api/functions/do-something` or `https://{BOT_DOMAIN}/api/functions/do-something` that clients can POST to. When they do, this SDK validates that the caller provides a valid Microsoft Entra bearer token before invoking the registered callback. If the token is missing or invalid, the request is denied with a HTTP 401.\n\nThe function can be typed to accept input arguments. The clients would include those in the POST request payload, and they are made available in the callback through the `data` context argument.\n\n```typescript\napp.function<{}, { message: string }>('process-message', ({ data, log }) => {\n  log.info(`process-message called with: ${data.message}`);\n});\n```\n\n<!-- validation-warning -->\n\n:::warning\nThis SDK does not validate that the function arguments are of the expected types or otherwise trustworthy. You must take care to validate the input arguments before using them.\n:::\n\n<!-- return-values -->\n\nIf desired, the function can return data to the caller. The return value can be a string, an object, or an array.\n\n```typescript\napp.function('get-random-number', () => {\n  return '4'; // chosen by fair dice roll;\n  // guaranteed to be random\n});\n```\n\nIf your function returns a number, that will be interpreted as an HTTP status code:\n\n```typescript\napp.function('privileged-action', ({ userId }) => {\n  if (!hasPermission(userId)) {\n    return 401; // HTTP response will have status 401: unauthorized\n  }\n  // ... do something\n});\n```\n\n<!-- context-intro -->\n\nThe function callback receives a context object with a number of useful values. Some originate within the agent itself, while others are furnished by the caller via the HTTP Request.\n\n<!-- context-table -->\n\n| Property                   | Source | Description                                                                                                                               |\n| -------------------------- | ------ | ----------------------------------------------------------------------------------------------------------------------------------------- |\n| `api`                      | Agent  | The API client.                                                                                                                           |\n| `appGraph`                 | Agent  | The app graph client.                                                                                                                     |\n| `appId`                    | Agent  | Unique identifier assigned to the app after deployment, ensuring correct app instance recognition across hosts.                           |\n| `appSessionId`             | Caller | Unique ID for the calling app's session, used to correlate telemetry data.                                                                |\n| `authToken`                | Caller | The validated MSAL Entra token.                                                                                                           |\n| `channelId`                | Caller | Microsoft Teams ID for the channel associated with the content.                                                                           |\n| `chatId`                   | Caller | Microsoft Teams ID for the chat associated with the content.                                                                              |\n| `data`                     | Caller | The function payload.                                                                                                                     |\n| `getCurrentConversationId` | Agent  | Attempts to find the conversation ID where the app is used and verifies agent-user presence. Returns `undefined` if not found or invalid. |\n| `log`                      | Agent  | The app logger instance.                                                                                                                  |\n| `meetingId`                | Caller | Meeting ID used by tab when running in meeting context.                                                                                   |\n| `messageId`                | Caller | ID of the parent message from which the task module was launched (only available in bot card-launched modules).                           |\n| `pageId`                   | Caller | Developer-defined unique ID for the page this content points to.                                                                          |\n| `send`                     | Agent  | Sends an activity to the current conversation. Returns `null` if the conversation ID is invalid or undetermined.                          |\n| `subPageId`                | Caller | Developer-defined unique ID for the sub-page this content points to. Used to restore specific state within a page.                        |\n| `teamId`                   | Caller | Microsoft Teams ID for the team associated with the content.                                                                              |\n| `tenantId`                 | Caller | Microsoft Entra tenant ID of the current user, extracted from the validated auth token.                                                   |\n| `userId`                   | Caller | Microsoft Entra object ID of the current user, extracted from the validated auth token.                                                   |\n\n<!-- context-validation -->\n\nThe `authToken` is validated before the function callback is invoked, and the `tenantId` and `userId` values are extracted from the validated token. In the typical case, the remaining caller-supplied values would reflect what the Teams Tab app retrieves from the teams-js `getContext()` API, but the agent does not validate them.\n\n:::warning\nTake care to validate the caller-supplied values before using them. Don't assume that the calling user actually has access to items indicated in the context.\n:::\n\n<!-- context-helpers -->\n\nTo simplify two common scenarios, the context provides the `getCurrentConversationId` and `send` methods.\n\n- The `getCurrentConversationId` method attempts to find the current conversation ID based on the context provided by the client (chatId and channelId) and validates that both the agent and the calling user are actually present in the conversation. If neither chatId or channelId is provided by the caller, the ID of the 1:1 conversation between the agent and the user is returned.\n- The `send` method relies on `getCurrentConversationId` to find the conversation where the app is hosted and posts an activity.\n\n<!-- additional-resources -->\n\n- For details on how to Tab apps can invoke these functions, see the [Executing Functions](./function-calling) in-depth guide.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/tabs/typescript.incl.md",
    "content": "<!-- overview -->\n\nTab apps will often need to interact with remote services. They may need to fetch data from [Microsoft Graph](https://learn.microsoft.com/en-us/graph/overview) or invoke remote agent functions, using the [Nested App Authentication](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/nested-authentication) (NAA) and the [Microsoft Authentication Library](https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview) (MSAL) to ensure user consent and to allow the remote service authenticate the user.\n\nThe `@microsoft/teams.client` package in this SDK builds on TeamsJS and MSAL to streamline these common scenarios. It aims to simplify:\n\n- **Remote Service Authentication** through MSAL-based authentication and token acquisition.\n- **Graph API Integration** by offering a pre-configured and type-safe Microsoft Graph client.\n- **Agent Function Calling** by handling authentication and including app context when calling server-side functions implemented Teams SDK agents.\n- **Scope Consent Management** by providing simple APIs to test for and request user consent.\n\n<!-- additional-resources -->\n\n### Additional resources\n\n- [Static Pages](../server/static-pages)\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/typescript.incl.md",
    "content": "<!-- intro -->\n\nThis documentation covers advanced features and capabilities of the Teams SDK in TypeScript.\n"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/user-authentication/csharp.incl.md",
    "content": "<!-- create-project -->\n\nUse your terminal to run the following command:\n\n```sh\nnpx @microsoft/teams.cli@latest new csharp oauth-app --template graph\n```\n\n<!-- configure-oauth -->\n\n```cs\nvar builder = WebApplication.CreateBuilder(args);\n\nvar appBuilder = App.Builder()\n    .AddOAuth(\"graph\");\n\nbuilder.AddTeams(appBuilder);\nvar app = builder.Build();\nvar teams = app.UseTeams();\n```\n\n<!-- signing-in -->\n\n```cs\nteams.OnMessage(\"/signin\", async (context, cancellationToken) =>\n{\n    if (context.IsSignedIn)\n    {\n        await context.Send(\"you are already signed in!\", cancellationToken);\n        return;\n    }\n    else\n    {\n        await context.SignIn(cancellationToken);\n    }\n});\n```\n\n<!-- signin-event -->\n\n```cs\nteams.OnSignIn(async (_, teamsEvent, cancellationToken) =>\n{\n    var context = teamsEvent.Context;\n    await context.Send($\"Signed in using OAuth connection {context.ConnectionName}. Please type **/whoami** to see your profile or **/signout** to sign out.\", cancellationToken);\n});\n```\n\n<!-- using-graph -->\n\n```cs\nteams.OnMessage(\"/whoami\", async (context, cancellationToken) =>\n{\n    if (!context.IsSignedIn)\n    {\n        await context.Send(\"you are not signed in!. Please type **/signin** to sign in\", cancellationToken);\n        return;\n    }\n    var me = await context.GetUserGraphClient().Me.GetAsync();\n    await context.Send($\"user \\\"{me!.DisplayName}\\\" signed in.\", cancellationToken);\n});\n\nteams.OnMessage(async (context, cancellationToken) =>\n{\n    if (context.IsSignedIn)\n    {\n        await context.Send($\"You said : {context.Activity.Text}.  Please type **/whoami** to see your profile or **/signout** to sign out.\", cancellationToken);\n    }\n    else\n    {\n        await context.Send($\"You said : {context.Activity.Text}.  Please type **/signin** to sign in.\", cancellationToken);\n    }\n});\n```\n\n<!-- signing-out -->\n\n```cs\nteams.OnMessage(\"/signout\", async (context, cancellationToken) =>\n{\n    if (!context.IsSignedIn)\n    {\n        await context.Send(\"you are not signed in!\", cancellationToken);\n        return;\n    }\n\n    await context.SignOut(cancellationToken);\n    await context.Send(\"you have been signed out!\", cancellationToken);\n});\n```\n<!-- signin-failure -->\n\n```cs\nteams.OnSignInFailure(async (context, cancellationToken) =>\n{\n    var failure = context.Activity.Value;\n    Console.WriteLine($\"Sign-in failed: {failure?.Code} - {failure?.Message}\");\n    await context.Send(\"Sign-in failed.\", cancellationToken);\n});\n```\n\n<!-- regional-bot -->\n\nN/A"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/user-authentication/python.incl.md",
    "content": "<!-- create-project -->\n\nUse your terminal to run the following command:\n\n```sh\nnpx @microsoft/teams.cli@latest new python oauth-app --template graph\n```\n\nThis command:\n\n1. Creates a new directory called `oauth-app`.\n2. Bootstraps the graph agent template files into it under `oauth-app/src`.\n3. Creates your agent's manifest files, including a `manifest.json` file and placeholder icons in the `oauth-app/appPackage` directory.\n\n<!-- configure-oauth -->\n\n```python\nfrom teams import App\nfrom teams.api import MessageActivity, SignInEvent\nfrom teams.apps import ActivityContext\nfrom teams.logger import ConsoleLogger, ConsoleLoggerOptions\n\napp = App(\n    # The name of the auth connection to use.\n    # It should be the same as the Oauth connection name defined in the Azure Bot configuration.\n    default_connection_name=\"graph\",\n    logger=ConsoleLogger().create_logger(\"auth\", options=ConsoleLoggerOptions(level=\"debug\")))\n```\n\n<!-- signing-in -->\n\n```python\n@app.on_message\nasync def handle_signin_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle message activities for signing in.\"\"\"\n    ctx.logger.info(\"User requested sign-in.\")\n    if ctx.is_signed_in:\n        await ctx.send(\"You are already signed in.\")\n    else:\n        await ctx.sign_in()\n```\n\n<!-- signin-event -->\n\n```python\n@app.event(\"sign_in\")\nasync def handle_sign_in(event: SignInEvent):\n    \"\"\"Handle sign-in events.\"\"\"\n    await event.activity_ctx.send(\"You are now signed in!\")\n```\n\n<!-- using-graph -->\n\n```python\n@app.on_message\nasync def handle_whoami_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle messages to show user information from Microsoft Graph.\"\"\"\n    if not ctx.is_signed_in:\n        await ctx.send(\"You are not signed in! Please sign in to continue.\")\n        return\n\n    # Access user's Microsoft Graph data\n    me = await ctx.user_graph.me.get()\n    await ctx.send(f\"Hello {me.display_name}! Your email is {me.mail or me.user_principal_name}\")\n\n@app.on_message\nasync def handle_all_messages(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle all other messages.\"\"\"\n    if ctx.is_signed_in:\n        await ctx.send(f'You said: \"{ctx.activity.text}\". Please type **/whoami** to see your profile or **/signout** to sign out.')\n    else:\n        await ctx.send(f'You said: \"{ctx.activity.text}\". Please type **/signin** to sign in.')\n```\n\n<!-- signing-out -->\n\n```python\n@app.on_message\nasync def handle_signout_message(ctx: ActivityContext[MessageActivity]):\n    \"\"\"Handle sign out requests.\"\"\"\n    if not ctx.is_signed_in:\n        await ctx.send(\"You are not signed in!\")\n        return\n\n    await ctx.sign_out()\n    await ctx.send(\"You have been signed out!\")\n```\n\n<!-- signin-failure -->\n\n```python\n@app.on_signin_failure()\nasync def handle_signin_failure(ctx):\n    failure = ctx.activity.value\n    print(f\"Sign-in failed: {failure.code} - {failure.message}\")\n    await ctx.send(\"Sign-in failed.\")\n```\n\n:::note\nIn Python, registering a custom handler does **not** replace the built-in default handler. Both will run as part of the middleware chain.\n:::\n\n<!-- regional-bot -->\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n## Regional Configs\nYou may be building a regional bot that is deployed in a specific Azure region (such as West Europe, East US, etc.) rather than global. This is important for organizations that have data residency requirements or want to reduce latency by keeping data and authentication flows within a specific area.\n\nThese examples use West Europe, but follow the equivalent for other regions.\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\nTo configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. \n\n1. Deploy a new App Registration in `westeurope`.\n2. Deploy and link a new Enterprise Application (Service Principal) on Microsoft Entra in `westeurope`.\n3. Deploy and link a new Azure Bot in `westeurope`.\n4. In your App Registration, in the `Authentication (Preview)` tab, add a `Redirect URI` for the Platform Type `Web` to your regional endpoint (e.g., `https://europe.token.botframework.com/.auth/web/redirect`)\n\n![Authentication Tab](/screenshots/regional-auth.png)\n\n5. In your `.env` file (or wherever you set your environment variables), add your `OAUTH_URL`. For example:\n`OAUTH_URL=https://europe.token.botframework.com`\n</TabItem>\n\n<TabItem value=\"atk\" label=\"Agents Toolkit\">\nTo configure a new regional bot with ATK, you will need to make a few updates. Note that this assumes you have not yet deployed the bot previously.\n\n1. In `azurebot.bicep`, replace all `global` occurrences to `westeurope`\n2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com`\n3. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect`\n4. In your `.env` file, add your `OAUTH_URL`. For example:\n`OAUTH_URL=https://europe.token.botframework.com`.\n</TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/in-depth-guides/user-authentication/typescript.incl.md",
    "content": "<!-- create-project -->\n\nUse your terminal to run the following command:\n\n```sh\nnpx @microsoft/teams.cli@latest new typescript oauth-app --template graph\n```\n\nThis command:\n\n1. Creates a new directory called `oauth-app`.\n2. Bootstraps the graph agent template files into it under `oauth-app/src`.\n3. Creates your agent's manifest files, including a `manifest.json` file and placeholder icons in the `oauth-app/appPackage` directory.\n\n<!-- configure-oauth -->\n\n```ts\nimport { App } from '@microsoft/teams.apps';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App({\n  oauth: {\n    defaultConnectionName: 'graph',\n  },\n});\n```\n\n<!-- signing-in -->\n\n```ts\napp.message('/signin', async ({ signin, send }) => {\n  if (await signin()) {\n    await send('you are already signed in!');\n  }\n});\n```\n\n<!-- signin-event -->\n\n```ts\napp.event('signin', async ({ send, token }) => {\n  await send(\n    `Signed in using OAuth connection ${token.connectionName}. Please type **/whoami** to see your profile or **/signout** to sign out.`\n  );\n});\n```\n\n<!-- using-graph -->\n\n```ts\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\napp.message('/whoami', async ({ send, userGraph, signin }) => {\n  if (!await signin()) {\n    return;\n  }\n  const me = await userGraph.call(endpoints.me.get);\n  await send(\n    `you are signed in as \"${me.displayName}\" and your email is \"${me.mail || me.userPrincipalName}\"`\n  );\n});\n\napp.on('message', async ({ send, activity, signin }) => {\n  if (await signin()) {\n    await send(\n      `You said: \"${activity.text}\". Please type **/whoami** to see your profile or **/signout** to sign out.`\n    );\n  } else {\n    await send(`You said: \"${activity.text}\". Please type **/signin** to sign in.`);\n  }\n});\n```\n\n<!-- signing-out -->\n\n```ts\napp.message('/signout', async ({ send, signout, isSignedIn }) => {\n  if (!isSignedIn) return;\n  await signout();\n  await send('you have been signed out!');\n});\n```\n\n<!-- signin-failure -->\n\n```ts\napp.on('signin.failure', async ({ activity, send }) => {\n  const { code, message } = activity.value;\n  console.log(`Sign-in failed: ${code} - ${message}`);\n  await send('Sign-in failed.');\n});\n```\n\n<!-- regional-bot -->\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n## Regional Configs\nYou may be building a regional bot that is deployed in a specific Azure region (such as West Europe, East US, etc.) rather than global. This is important for organizations that have data residency requirements or want to reduce latency by keeping data and authentication flows within a specific area.\n\nThese examples use West Europe, but follow the equivalent for other regions.\n\n<Tabs>\n<TabItem value=\"portal\" label=\"Azure Portal\">\nTo configure a new regional bot in Azure, you must setup your resoures in the desired region. Your resource group must also be in the same region. \n\n1. Deploy a new App Registration in `westeurope`.\n2. Deploy and link a new Enterprise Application (Service Principal) on Microsoft Entra in `westeurope`.\n3. Deploy and link a new Azure Bot in `westeurope`.\n4. In your App Registration, in the `Authentication (Preview)` tab, add a `Redirect URI` for the Platform Type `Web` to your regional endpoint (e.g., `https://europe.token.botframework.com/.auth/web/redirect`)\n\n![Authentication Tab](/screenshots/regional-auth.png)\n\n5. In your `.env` file (or wherever you set your environment variables), add your `OAUTH_URL`. For example:\n`OAUTH_URL=https://europe.token.botframework.com`\n</TabItem>\n\n<TabItem value=\"atk\" label=\"Agents Toolkit\">\nTo configure a new regional bot with ATK, you will need to make a few updates. Note that this assumes you have not yet deployed the bot previously.\n\n1. In `azurebot.bicep`, replace all `global` occurrences to `westeurope`\n2. In `manifest.json`, in `validDomains`, `*.botframework.com` should be replaced by `europe.token.botframework.com`\n3. In `aad.manifest.json`, replace `https://token.botframework.com/.auth/web/redirect` with `https://europe.token.botframework.com/.auth/web/redirect`\n4. In your `.env` file, add your `OAUTH_URL`. For example:\n`OAUTH_URL=https://europe.token.botframework.com`\n</TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/integration/csharp.incl.md",
    "content": "<!-- example -->\n\n<Tabs>\n  <TabItem value=\"Program.cs\" default>\n    ```csharp\n\n    using Microsoft.Bot.Builder.Integration.AspNet.Core;\n    using Microsoft.Teams.Api.Activities;\n    using Microsoft.Teams.Apps;\n    using Microsoft.Teams.Apps.Activities;\n    using Microsoft.Teams.Apps.Annotations;\n    using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n\n    public static partial class Program\n    {\n        public static void Main(string[] args)\n        {\n            var builder = WebApplication.CreateBuilder(args);\n            builder\n                .AddTeams()\n                // highlight-next-line\n                .AddBotBuilder<Bot, BotBuilderAdapter, ConfigurationBotFrameworkAuthentication>();\n\n            var app = builder.Build();\n\n            var teams = app.UseTeams();\n            app.Run();\n        }\n\n        teams.OnMessage(async (context, cancellationToken) =>\n        {\n            await context.Client.Typing(cancellationToken);\n            await context.Client.Send($\"hi from teams...\", cancellationToken);\n        });\n    }\n    ```\n\n  </TabItem>\n  <TabItem value=\"BotBuilderAdapter.cs\">\n    ```csharp\n    using Microsoft.Bot.Builder.Integration.AspNet.Core;\n    using Microsoft.Bot.Connector.Authentication;\n\n    // replace with your Adapter\n    // highlight-start\n    public class BotBuilderAdapter : CloudAdapter\n    {\n        public BotBuilderAdapter(BotFrameworkAuthentication auth, ILogger<IBotFrameworkHttpAdapter> logger)\n            : base(auth, logger)\n        {\n            OnTurnError = async (turnContext, exception) =>\n            {\n                logger.LogError(exception, $\"[OnTurnError] unhandled error : {exception.Message}\");\n\n                // Send a message to the user\n                await turnContext.SendActivityAsync(\"The bot encountered an error or bug.\");\n            };\n        }\n    }\n    // highlight-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"ActivityHandler.cs\">\n    ```csharp\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Schema;\n\n    // replace with your ActivityHandler\n    // highlight-start\n    public class Bot : ActivityHandler\n    {\n        protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)\n        {\n            var replyText = $\"hi from botbuilder...\";\n            await turnContext.SendActivityAsync(MessageFactory.Text(replyText, replyText), cancellationToken);\n        }\n    }\n    // highlight-end\n    ```\n\n  </TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/integration/python.incl.md",
    "content": "<!-- example -->\n\n<Tabs>\n  <TabItem value=\"app.py\" default>\n    ```python\n    import asyncio\n    from adapter import adapter\n    from activity_handler import MyActivityHandler\n    from microsoft_teams.api import MessageActivity\n    from microsoft_teams.apps import ActivityContext, App\n    from microsoft_teams.botbuilder import BotBuilderPlugin\n\n    # highlight-next-line\n    app = App(plugins=[BotBuilderPlugin(adapter=adapter, handler=MyActivityHandler())])\n\n    @app.on_message\n    async def handle_message(ctx: ActivityContext[MessageActivity]):\n        print(\"Handling message in app...\")\n        await ctx.send(\"hi from teams...\")\n\n\n    if __name__ == \"__main__\":\n        asyncio.run(app.start())\n    ```\n\n  </TabItem>\n  <TabItem value=\"adapter.py\">\n    ```python\n    from botbuilder.core import TurnContext\n    from botbuilder.integration.aiohttp import (\n        CloudAdapter,\n        ConfigurationBotFrameworkAuthentication,\n    )\n    from botbuilder.schema import Activity, ActivityTypes\n    from types import SimpleNamespace\n    \n    config = SimpleNamespace(\n                APP_TYPE=\"SingleTenant\" if tenant_id else \"MultiTenant\",\n                APP_ID=client_id,\n                APP_PASSWORD=client_secret,\n                APP_TENANTID=tenant_id,\n            )\n\n    # replace with your Adapter\n    # highlight-start\n    adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config))\n\n    async def on_error(context: TurnContext, error: Exception):\n        # Send a message to the user\n        await context.send_activity(\"The bot encountered an error or bug.\")\n\n    adapter.on_turn_error = on_error\n    # highlight-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"activity_handler.py\">\n    ```python\n    from botbuilder.core import ActivityHandler, TurnContext\n\n    # replace with your ActivityHandler\n    # highlight-start\n    class MyActivityHandler(ActivityHandler):\n        async def on_message_activity(self, turn_context: TurnContext):\n            await turn_context.send_activity(\"hi from botbuilder...\")\n    # highlight-end\n    ```\n\n  </TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/integration/typescript.incl.md",
    "content": "<!-- example -->\n\n<Tabs>\n  <TabItem value=\"index.ts\" default>\n    ```typescript\n    import { App } from '@microsoft/teams.apps';\n    import { BotBuilderPlugin } from '@microsoft/teams.botbuilder';\n\n    import adapter from './adapter';\n    import handler from './activity-handler';\n\n    const app = new App({\n      // highlight-next-line\n      plugins: [new BotBuilderPlugin({ adapter, handler })],\n    });\n\n    app.on('message', async ({ send }) => {\n      await send('hi from teams...');\n    });\n\n    (async () => {\n      await app.start();\n    })();\n    ```\n\n  </TabItem>\n  <TabItem value=\"adapter.ts\">\n    ```typescript\n    import { CloudAdapter } from 'botbuilder';\n\n    // replace with your BotAdapter\n    // highlight-start\n    const adapter = new CloudAdapter(\n      new ConfigurationBotFrameworkAuthentication(\n        {},\n        new ConfigurationServiceClientCredentialFactory({\n          MicrosoftAppType: tenantId ? 'SingleTenant' : 'MultiTenant',\n          MicrosoftAppId: clientId,\n          MicrosoftAppPassword: clientSecret,\n          MicrosoftAppTenantId: tenantId,\n        })\n      )\n    );\n    // highlight-end\n\n    export default adapter;\n    ```\n\n  </TabItem>\n  <TabItem value=\"activity-handler.ts\">\n    ```typescript\n    import { TeamsActivityHandler } from 'botbuilder';\n\n    // replace with your TeamsActivityHandler\n    // highlight-start\n    export class ActivityHandler extends TeamsActivityHandler {\n      constructor() {\n        super();\n        this.onMessage(async (ctx, next) => {\n          await ctx.sendActivity('hi from botbuilder...');\n          await next();\n        });\n      }\n    }\n    // highlight-end\n\n    const handler = new ActivityHandler();\n    export default handler;\n    ```\n\n  </TabItem>\n</Tabs>\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/proactive-activities/csharp.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"proactive-activities\">\n  <TabItem value=\"Diff\" default>\n    ```csharp\n    // highlight-error-start\n-   using Microsoft.Bot.Builder;\n-   using Microsoft.Bot.Builder.Integration.AspNet.Core;\n-   using Microsoft.Bot.Schema;\n    // highlight-error-end\n    // highlight-success-line\n+   using Microsoft.Teams.Apps;\n\n    // highlight-error-start\n-   var conversationReference = new ConversationReference\n-   {\n-       ServiceUrl = \"...\",\n-       Bot = new ChannelAccount { ... },\n-       ChannelId = \"msteams\",\n-       Conversation = new ConversationAccount { ... },\n-       User = new ChannelAccount { ... }\n-   };\n-\n-   await adapter.ContinueConversationAsync(\n-       configuration[\"MicrosoftAppId\"],\n-       conversationReference,\n-       async (turnContext, cancellationToken) =>\n-       {\n-           await turnContext.SendActivityAsync(\"proactive hello\", cancellationToken: cancellationToken);\n-       },\n-       default);\n    // highlight-error-end\n    // highlight-success-start\n+   var teams = app.UseTeams();\n+   await teams.Send(\"your-conversation-id\", \"proactive hello\");\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Builder.Integration.AspNet.Core;\n    using Microsoft.Bot.Schema;\n\n    // highlight-start\n    var conversationReference = new ConversationReference\n    {\n        ServiceUrl = \"...\",\n        Bot = new ChannelAccount { ... },\n        ChannelId = \"msteams\",\n        Conversation = new ConversationAccount { ... },\n        User = new ChannelAccount { ... }\n    };\n\n    await adapter.ContinueConversationAsync(\n        configuration[\"MicrosoftAppId\"],\n        conversationReference,\n        async (turnContext, cancellationToken) =>\n        {\n            await turnContext.SendActivityAsync(\"proactive hello\", cancellationToken: cancellationToken);\n        },\n        default);\n    // highlight-end\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Apps;\n\n    // highlight-start\n    var teams = app.UseTeams();\n    await teams.Send(\"your-conversation-id\", \"proactive hello\");\n    // highlight-end\n    ```\n  </TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/proactive-activities/python.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"proactive-activities\">\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-   from botbuilder.core import TurnContext\n-   from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication\n-   from botbuilder.schema import ChannelAccount, ConversationAccount, ConversationReference\n    # highlight-error-end\n    # highlight-success-line\n+   from microsoft_teams.apps import App\n\n    # highlight-error-start\n-   adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config))\n    # highlight-error-end\n    # highlight-success-line\n+   app = App()\n\n    # highlight-error-start\n-   conversation_reference = ConversationReference(\n-       service_url=\"...\",\n-       bot=ChannelAccount(...),\n-       channel_id=\"msteams\",\n-       conversation=ConversationAccount(...),\n-       user=ChannelAccount(...)\n-   )\n-\n-   async def send_proactive(turn_context: TurnContext):\n-       await turn_context.send_activity(\"proactive hello\")\n-\n-   await adapter.continue_conversation(\n-       conversation_reference,\n-       send_proactive,\n-   )\n    # highlight-error-end\n    # highlight-success-start\n+   await app.send(\"your-conversation-id\", \"proactive hello\")\n    # highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import TurnContext\n    from botbuilder.integration.aiohttp import CloudAdapter, ConfigurationBotFrameworkAuthentication\n    from botbuilder.schema import ChannelAccount, ConversationAccount, ConversationReference\n\n    adapter = CloudAdapter(ConfigurationBotFrameworkAuthentication(config))\n\n    # highlight-start\n    conversation_reference = ConversationReference(\n        service_url=\"...\",\n        bot=ChannelAccount(...),\n        channel_id=\"msteams\",\n        conversation=ConversationAccount(...),\n        user=ChannelAccount(...)\n    )\n\n    async def send_proactive(turn_context: TurnContext):\n        await turn_context.send_activity(\"proactive hello\")\n\n    await adapter.continue_conversation(\n        conversation_reference,\n        send_proactive\n    )\n    # highlight-end\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.apps import App\n\n    app = App()\n\n    # highlight-start\n    await app.send(\"your-conversation-id\", \"proactive hello\")\n    # highlight-end\n    ```\n  </TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/proactive-activities/typescript.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"proactive-activities\">\n  <TabItem value=\"Diff\" default>\n    ```typescript\n    // highlight-error-start\n-    import {\n-      CloudAdapter,\n-      ConfigurationBotFrameworkAuthentication,\n-      ConversationReference,\n-    } from 'botbuilder';\n    // highlight-error-end\n    // highlight-success-line\n+    import { App } from '@microsoft/teams.apps';\n\n    // highlight-error-start\n-    const auth = new ConfigurationBotFrameworkAuthentication(process.env);\n-    const adapter = new CloudAdapter(auth);\n    // highlight-error-end\n    // highlight-success-line\n+    const app = new App();\n\n    (async () => {\n      // highlight-error-start\n-      const conversationReference: ConversationReference = {\n-        serviceUrl: '...',\n-        bot: { ... },\n-        channelId: 'msteams',\n-        conversation: { ... },\n-        user: { ... },\n-      };\n\n-      await adapter.continueConversationAsync(process.env.MicrosoftAppId ?? '', conversationReference, async context => {\n-        await context.sendActivity('proactive hello');\n-      });\n      // highlight-error-end\n      // highlight-success-start\n+      await app.start();\n+      await app.send('your-conversation-id', 'proactive hello');\n      // highlight-success-end\n    }());\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```typescript showLineNumbers\n    import {\n      CloudAdapter,\n      ConfigurationBotFrameworkAuthentication,\n      ConversationReference,\n    } from 'botbuilder';\n\n    const auth = new ConfigurationBotFrameworkAuthentication(process.env);\n    const adapter = new CloudAdapter(auth);\n\n    // highlight-start\n    (async () => {\n      const conversationReference: ConversationReference = {\n        serviceUrl: '...',\n        bot: { ... },\n        channelId: 'msteams',\n        conversation: { ... },\n        user: { ... },\n      };\n\n      await adapter.continueConversationAsync(process.env.MicrosoftAppId ?? '', conversationReference, async context => {\n        await context.sendActivity('proactive hello');\n      });\n    }());\n    // highlight-end\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```typescript showLineNumbers\n    import { App } from '@microsoft/teams.apps';\n\n    const app = new App();\n\n    // highlight-start\n    (async () => {\n      await app.start();\n      await app.send('your-conversation-id', 'proactive hello');\n    }());\n    // highlight-end\n    ```\n  </TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/sending-activities/csharp.incl.md",
    "content": "<!-- examples -->\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```csharp\n    // highlight-error-start\n-   using Microsoft.Bot.Builder;\n-   using Microsoft.Bot.Schema;\n    // highlight-error-end\n    // highlight-success-start\n+   using Microsoft.Teams.Apps;\n+   using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n+   using Microsoft.Teams.Api.Activities;\n    //highlight-success-end\n\n    // highlight-error-start\n-   public class MyActivityHandler : ActivityHandler\n-   {\n-       protected override async Task OnMessageActivityAsync(\n-           ITurnContext<IMessageActivity> turnContext,\n-           CancellationToken cancellationToken)\n-       {\n-           await turnContext.SendActivityAsync(\n-               Activity.CreateTypingActivity(), \n-               cancellationToken: cancellationToken);\n-       }\n-   }\n    // highlight-error-end\n    // highlight-success-start\n+   var teams = app.UseTeams();\n+   teams.OnMessage(async (context, cancellationToken) =>\n+   {\n+       await context.Send(new Activity(type:\"typing\"), cancellationToken);\n+   });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Schema;\n\n    public class MyActivityHandler : ActivityHandler\n    {\n        protected override async Task OnMessageActivityAsync(\n            ITurnContext<IMessageActivity> turnContext,\n            CancellationToken cancellationToken)\n        {\n            // highlight-next-line\n            await turnContext.SendActivityAsync(\n                Activity.CreateTypingActivity(), \n                cancellationToken: cancellationToken);\n        }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Apps;\n    using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n    using Microsoft.Teams.Api.Activities;\n\n    var teams = app.UseTeams();\n    teams.OnMessage(async (context, cancellationToken) =>\n    {\n        // highlight-next-line\n        await context.Send(new Activity(type:\"typing\"), cancellationToken);\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n## Strings\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```csharp\n    // highlight-error-start\n-   using Microsoft.Bot.Builder;\n-   using Microsoft.Bot.Schema;\n    // highlight-error-end\n    // highlight-success-start\n+   using Microsoft.Teams.Apps;\n+   using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n    //highlight-success-end\n\n    // highlight-error-start\n-   public class MyActivityHandler : ActivityHandler\n-   {\n-       protected override async Task OnMessageActivityAsync(\n-           ITurnContext<IMessageActivity> turnContext,\n-           CancellationToken cancellationToken)\n-       {\n-           await turnContext.SendActivityAsync(\"hello world\", cancellationToken: cancellationToken);\n-       }\n-   }\n    // highlight-error-end\n    // highlight-success-start\n+   var teams = app.UseTeams();\n+   teams.OnMessage(async (context, cancellationToken) =>\n+   {\n+       await context.Send(\"hello world\", cancellationToken);\n+   });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Schema;\n\n    public class MyActivityHandler : ActivityHandler\n    {\n        protected override async Task OnMessageActivityAsync(\n            ITurnContext<IMessageActivity> turnContext,\n            CancellationToken cancellationToken)\n        {\n            // highlight-next-line\n            await turnContext.SendActivityAsync(\"hello world\", cancellationToken: cancellationToken);\n        }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Apps;\n    using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n\n    var teams = app.UseTeams();\n    teams.OnMessage(async (context, cancellationToken) =>\n    {\n        // highlight-next-line\n        await context.Send(\"hello world\", cancellationToken);\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n## Adaptive Cards\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```csharp\n    // highlight-error-start\n-   using Microsoft.Bot.Builder;\n-   using Microsoft.Bot.Schema;\n    // highlight-error-end\n    // highlight-success-start\n+   using Microsoft.Teams.Apps;\n+   using Microsoft.Teams.Cards;\n+   using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n    // highlight-success-end\n\n    // highlight-error-start\n-   public class MyActivityHandler : ActivityHandler\n-   {\n-       protected override async Task OnMessageActivityAsync(\n-           ITurnContext<IMessageActivity> turnContext,\n-           CancellationToken cancellationToken)\n-       {\n-           var card = new\n-           {\n-               type = \"AdaptiveCard\",\n-               version = \"1.0\",\n-               body = new[]\n-               {\n-                   new { type = \"TextBlock\", text = \"hello world\" }\n-               }\n-           };\n-           var attachment = new Attachment\n-           {\n-               ContentType = \"application/vnd.microsoft.card.adaptive\",\n-               Content = card\n-           };\n-           var activity = MessageFactory.Attachment(attachment);\n-           await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken);\n-       }\n-   }\n    // highlight-error-end\n    // highlight-success-start\n+   var teams = app.UseTeams();\n+   teams.OnMessage(async (context, cancellationToken) =>\n+   {\n+       await context.Send(new AdaptiveCard(new TextBlock(\"hello world\")), cancellationToken);\n+   });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Schema;\n\n    public class MyActivityHandler : ActivityHandler\n    {\n        protected override async Task OnMessageActivityAsync(\n            ITurnContext<IMessageActivity> turnContext,\n            CancellationToken cancellationToken)\n        {\n            // highlight-start\n            var card = new\n            {\n                type = \"AdaptiveCard\",\n                version = \"1.0\",\n                body = new[]\n                {\n                    new { type = \"TextBlock\", text = \"hello world\" }\n                }\n            };\n            var attachment = new Attachment\n            {\n                ContentType = \"application/vnd.microsoft.card.adaptive\",\n                Content = card\n            };\n            var activity = MessageFactory.Attachment(attachment);\n            await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken);\n            // highlight-end\n        }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Cards;\n    using Microsoft.Teams.Apps;\n    using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n\n    var teams = app.UseTeams();\n    teams.OnMessage(async (context, cancellationToken) =>\n    {\n        // highlight-next-line\n        await context.Send(new AdaptiveCard(new TextBlock(\"hello world\")), cancellationToken);\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n## Attachments\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```csharp\n    // highlight-error-start\n-   using Microsoft.Bot.Builder;\n-   using Microsoft.Bot.Schema;\n    // highlight-error-end\n    // highlight-success-start\n+   using Microsoft.Teams.Apps;\n+   using Microsoft.Teams.Api;\n+   using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n    // highlight-success-end\n\n    // highlight-error-start\n-   public class MyActivityHandler : ActivityHandler\n-   {\n-       protected override async Task OnMessageActivityAsync(\n-           ITurnContext<IMessageActivity> turnContext,\n-           CancellationToken cancellationToken)\n-       {\n-           var activity = MessageFactory.Attachment(new Attachment { /* ... */ });\n-           await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken);\n-       }\n-   }\n    // highlight-error-end\n    // highlight-success-start\n+   var teams = app.UseTeams();\n+   teams.OnMessage(async (context, cancellationToken) =>\n+   {\n+       var activity = new MessageActivity();\n+       activity.AddAttachment(new Attachment { /* ... */ });\n+       await context.SendAsync(activity, cancellationToken);\n+   });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Schema;\n\n    public class MyActivityHandler : ActivityHandler\n    {\n        protected override async Task OnMessageActivityAsync(\n            ITurnContext<IMessageActivity> turnContext,\n            CancellationToken cancellationToken)\n        {\n            // highlight-start\n            var activity = MessageFactory.Attachment(new Attachment { /* ... */ });\n            await turnContext.SendActivityAsync(activity, cancellationToken: cancellationToken);\n            // highlight-end\n        }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Api;\n    using Microsoft.Teams.Apps;\n    using Microsoft.Teams.Plugins.AspNetCore.Extensions;\n    \n    var teams = app.UseTeams();\n    teams.OnMessage(async (context, cancellationToken) =>\n    {\n        // highlight-start\n        var activity = new MessageActivity();\n        activity.AddAttachment(new Attachment { /* ... */ });\n        await context.SendAsync(activity, cancellationToken);\n        // highlight-end\n    });\n    ```\n  </TabItem>\n</Tabs>\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/sending-activities/python.incl.md",
    "content": "<!-- examples -->\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-   from botbuilder.core import ActivityHandler, TurnContext\n-   from botbuilder.schema import Activity\n    # highlight-error-end\n    # highlight-success-start\n+   from microsoft_teams.api import MessageActivity, TypingActivityInput\n+   from microsoft_teams.apps import ActivityContext, App\n    # highlight-success-end\n\n    # highlight-error-start\n-   class MyActivityHandler(ActivityHandler):\n-       async def on_message_activity(self, turn_context: TurnContext):\n-           await turn_context.send_activity(Activity(type=\"typing\"))\n    # highlight-error-end\n    # highlight-success-start\n+   @app.on_message\n+   async def on_message(context: ActivityContext[MessageActivity]):\n+       await context.send(TypingActivityInput())\n    # highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import ActivityHandler, TurnContext\n    from botbuilder.schema import Activity\n\n    class MyActivityHandler(ActivityHandler):\n        async def on_message_activity(self, turn_context: TurnContext):\n            # highlight-next-line\n            await turn_context.send_activity(Activity(type=\"typing\"))\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.api import MessageActivity, TypingActivityInput\n    from microsoft_teams.apps import ActivityContext, App\n\n    @app.on_message\n    async def on_message(context: ActivityContext[MessageActivity]):\n        # highlight-next-line\n        await context.send(TypingActivityInput())\n    ```\n  </TabItem>\n</Tabs>\n\n## Strings\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-   from botbuilder.core import ActivityHandler, TurnContext\n    # highlight-error-end\n    # highlight-success-start\n+   from microsoft_teams.api import MessageActivity\n+   from microsoft_teams.apps import ActivityContext, App\n    # highlight-success-end\n\n    # highlight-error-start\n-   class MyActivityHandler(ActivityHandler):\n-       async def on_message_activity(self, turn_context: TurnContext):\n-           await turn_context.send_activity(\"hello world\")\n    # highlight-error-end\n    # highlight-success-start\n+   @app.on_message\n+   async def on_message(context: ActivityContext[MessageActivity]):\n+       await context.send(\"hello world\")\n    # highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import ActivityHandler, TurnContext\n\n    class MyActivityHandler(ActivityHandler):\n        async def on_message_activity(self, turn_context: TurnContext):\n            # highlight-next-line\n            await turn_context.send_activity(\"hello world\")\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.api import MessageActivity\n    from microsoft_teams.apps import ActivityContext, App\n\n    @app.on_message\n    async def on_message(context: ActivityContext[MessageActivity]):\n        # highlight-next-line\n        await context.send(\"hello world\")\n    ```\n  </TabItem>\n</Tabs>\n\n## Adaptive Cards\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-   from botbuilder.core import ActivityHandler, TurnContext\n-   from botbuilder.schema import Activity, Attachment\n    # highlight-error-end\n    # highlight-success-start\n+   from microsoft_teams.api import MessageActivity\n+   from microsoft_teams.apps import ActivityContext, App\n+   from microsoft_teams.cards import AdaptiveCard, TextBlock\n    # highlight-success-end\n\n    # highlight-error-start\n-   class MyActivityHandler(ActivityHandler):\n-       async def on_message_activity(self, turn_context: TurnContext):\n-         card = {\"type\": \"AdaptiveCard\", \"version\": \"1.0\", \"body\": [{\"type\": \"TextBlock\", \"text\": \"hello world\"}]}\n-         attachment = Attachment(content_type=\"application/vnd.microsoft.card.adaptive\", content=card)\n-         activity = Activity(type=\"message\", attachments=[attachment])\n-         await turn_context.send_activity(activity)\n    # highlight-error-end\n    # highlight-success-start\n+   @app.on_message\n+   async def on_message(context: ActivityContext[MessageActivity]):\n+       await context.send(AdaptiveCard().with_body([TextBlock(text=\"Hello from Adaptive Card!\")]))\n    # highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import ActivityHandler, TurnContext\n    from botbuilder.schema import Activity, Attachment\n\n    class MyActivityHandler(ActivityHandler):\n        async def on_message_activity(self, turn_context: TurnContext):\n          # hightlight-start\n          card = {\"type\": \"AdaptiveCard\", \"version\": \"1.0\", \"body\": [{\"type\": \"TextBlock\", \"text\": \"hello world\"}]}\n          attachment = Attachment(content_type=\"application/vnd.microsoft.card.adaptive\", content=card)\n          activity = Activity(type=\"message\", attachments=[attachment])\n          await turn_context.send_activity(activity)\n          # highlight-end\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.api import MessageActivity\n    from microsoft_teams.apps import ActivityContext, App\n    from microsoft_teams.cards import AdaptiveCard, TextBlock\n\n    @app.on_message\n    async def on_message(context: ActivityContext[MessageActivity]):\n        # highlight-next-line\n        await context.send(AdaptiveCard(body=[TextBlock(text=\"Hello from Adaptive Card!\")]))\n    ```\n  </TabItem>\n</Tabs>\n\n## Attachments\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-   from botbuilder.core import ActivityHandler, TurnContext\n-   from botbuilder.schema import Activity, Attachment\n    # highlight-error-end\n    # highlight-success-start\n+   from microsoft_teams.api import Attachment, MessageActivity, MessageActivityInput\n+   from microsoft_teams.apps import ActivityContext, App\n    # highlight-success-end\n\n    # highlight-error-start\n-   class MyActivityHandler(ActivityHandler):\n-       async def on_message_activity(self, turn_context: TurnContext):\n-         attachment = Attachment(...)\n-         activity = Activity(type=\"message\", attachments=[attachment])\n-         await turn_context.send_activity(activity)\n    # highlight-error-end\n    # highlight-success-start\n+   @app.on_message\n+   async def on_message(context: ActivityContext[MessageActivity]):\n+       attachment = Attachment(...)\n+       activity = MessageActivityInput().add_attachments([attachment])\n+       await context.send(activity)\n    # highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import ActivityHandler, TurnContext\n    from botbuilder.schema import Activity, Attachment\n\n    class MyActivityHandler(ActivityHandler):\n        async def on_message_activity(self, turn_context: TurnContext):\n            # highlight-start\n            attachment = Attachment(...)\n            activity = Activity(type=\"message\", attachments=[attachment])\n            await turn_context.send_activity(activity)\n            # highlight-end\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.api import Attachment, MessageActivity, MessageActivityInput\n    from microsoft_teams.apps import ActivityContext, App\n\n    @app.on_message\n    async def on_message(context: ActivityContext[MessageActivity]):\n        # highlight-start\n        attachment = Attachment(...)\n        activity = MessageActivityInput().add_attachments([attachment])\n        await context.send(activity)\n        # highlight-end\n    ```\n  </TabItem>\n</Tabs>\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/sending-activities/typescript.incl.md",
    "content": "<!-- examples -->\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```typescript\n    // highlight-error-start\n-    import { TeamsActivityHandler } from 'botbuilder';\n\n-    export class ActivityHandler extends TeamsActivityHandler {\n-      constructor() {\n-        super();\n-        this.onMessage(async (context) => {\n-          await context.sendActivity({ type: 'typing' });\n-        });\n-      }\n-    }\n    // highlight-error-end\n    // highlight-success-start\n+    app.on('message', async ({ send }) => {\n+      await send({ type: 'typing' });\n+    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```typescript showLineNumbers\n    import { TeamsActivityHandler } from 'botbuilder';\n\n    export class ActivityHandler extends TeamsActivityHandler {\n      constructor() {\n        super();\n        this.onMessage(async (context) => {\n          // highlight-next-line\n          await context.sendActivity({ type: 'typing' });\n        });\n      }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```typescript showLineNumbers\n    app.on('message', async ({ send }) => {\n      // highlight-next-line\n      await send({ type: 'typing' });\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n## Strings\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```typescript\n    // highlight-error-start\n-    import { TeamsActivityHandler } from 'botbuilder';\n\n-    export class ActivityHandler extends TeamsActivityHandler {\n-      constructor() {\n-        super();\n-        this.onMessage(async (context) => {\n-          await context.sendActivity('hello world');\n-        });\n-      }\n-    }\n    // highlight-error-end\n    // highlight-success-start\n+    app.on('message', async ({ send }) => {\n+      await send('hello world');\n+    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```typescript showLineNumbers\n    import { TeamsActivityHandler } from 'botbuilder';\n\n    export class ActivityHandler extends TeamsActivityHandler {\n      constructor() {\n        super();\n        this.onMessage(async (context) => {\n          // highlight-next-line\n          await context.sendActivity('hello world');\n        });\n      }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```typescript showLineNumbers\n    app.on('message', async ({ send }) => {\n      // highlight-next-line\n      await send('hello world');\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n## Adaptive Cards\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```typescript\n    // highlight-error-line\n-    import { TeamsActivityHandler, CardFactory } from 'botbuilder';\n    // highlight-success-line\n+    import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n\n    // highlight-error-start\n-    export class ActivityHandler extends TeamsActivityHandler {\n-      constructor() {\n-        super();\n-        this.onMessage(async (context) => {\n-          await context.sendActivity({\n-            type: 'message',\n-            attachments: [\n-              CardFactory.adaptiveCard({\n-                $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n-                type: 'AdaptiveCard',\n-                version: '1.0',\n-                body: [{\n-                  type: 'TextBlock',\n-                  text: 'hello world'\n-                }]\n-              })\n-            ]\n-          });\n-        });\n-      }\n-    }\n    // highlight-error-end\n    // highlight-success-start\n+    app.on('message', async ({ send }) => {\n+      await send(new AdaptiveCard(new TextBlock('hello world')));\n+    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```typescript showLineNumbers\n    import { TeamsActivityHandler, CardFactory } from 'botbuilder';\n\n    export class ActivityHandler extends TeamsActivityHandler {\n      constructor() {\n        super();\n        this.onMessage(async (context) => {\n          // highlight-start\n          await context.sendActivity({\n            type: 'message',\n            attachments: [\n              CardFactory.adaptiveCard({\n                $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n                type: 'AdaptiveCard',\n                version: '1.0',\n                body: [{\n                  type: 'TextBlock',\n                  text: 'hello world'\n                }]\n              })\n            ]\n          });\n          // highlight-end\n        });\n      }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```typescript showLineNumbers\n    import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n\n    app.on('message', async ({ send }) => {\n      // highlight-next-line\n      await send(new AdaptiveCard(new TextBlock('hello world')));\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n## Attachments\n\n<Tabs groupId=\"sending-activities\">\n  <TabItem value=\"Diff\" default>\n    ```typescript\n    // highlight-error-line\n-    import { TeamsActivityHandler } from 'botbuilder';\n    // highlight-success-line\n+    import { AdaptiveCard, TextBlock } from '@microsoft/teams.cards';\n\n    // highlight-error-start\n-    export class ActivityHandler extends TeamsActivityHandler {\n-      constructor() {\n-        super();\n-        this.onMessage(async (context) => {\n-          await context.sendActivity({\n-            type: 'message',\n-            attachments: [\n-              ...\n-            ]\n-          });\n-        });\n-      }\n-    }\n    // highlight-error-end\n    // highlight-success-start\n+    app.on('message', async ({ send }) => {\n+      await send(new MessageActivity().addAttachment(...));\n+    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```typescript showLineNumbers\n    import { TeamsActivityHandler } from 'botbuilder';\n\n    export class ActivityHandler extends TeamsActivityHandler {\n      constructor() {\n        super();\n        this.onMessage(async (context) => {\n          // highlight-start\n          await context.sendActivity({\n            type: 'message',\n            attachments: [\n              ...\n            ]\n          });\n          // highlight-end\n        });\n      }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```typescript showLineNumbers\n\n    app.on('message', async ({ send }) => {\n      // highlight-next-line\n      await send(new MessageActivity().addAttachment(...));\n    });\n    ```\n  </TabItem>\n</Tabs>"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/the-api-client/csharp.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"api-client\">\n  <TabItem value=\"Diff\" default>\n  ```csharp\n  // highlight-error-start\n-  using Microsoft.Bot.Builder;\n-  using Microsoft.Bot.Builder.Teams;\n  // highlight-error-end\n  // highlight-success-line\n+  using Microsoft.Teams.Apps;\n\n  // highlight-error-start\n-  public class MyActivityHandler : ActivityHandler\n-  {\n-      protected override async Task OnMessageActivityAsync(\n-          ITurnContext<IMessageActivity> turnContext,\n-          CancellationToken cancellationToken)\n-      {\n-          var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);\n-      }\n-  }\n  // highlight-error-end\n  // highlight-success-start\n+  var teams = app.UseTeams();\n+  teams.OnMessage(async (context, cancellationToken) =>\n+  {\n+      var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id);\n+  });\n  // highlight-success-end\n  ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Builder.Teams;\n\n    public class MyActivityHandler : TeamsActivityHandler\n    {\n        protected override async Task OnMessageActivityAsync(\n            ITurnContext<IMessageActivity> turnContext,\n            CancellationToken cancellationToken)\n        {\n            // highlight-next-line\n            var members = await TeamsInfo.GetMembersAsync(turnContext, cancellationToken);\n        }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Apps;\n    \n    app.OnMessage(async (context, cancellationToken) =>\n    {\n        // highlight-next-line\n        var members = await context.Api.Conversations.Members.GetAsync(context.Activity.Conversation.Id);\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- api-table -->\n\n| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) |\n|------------------------|----------------------|\n| `TeamsInfo.GetMemberAsync(context, userId)` | `Api.Conversations.Members.GetByIdAsync(conversationId, userId)` |\n| `TeamsInfo.GetTeamDetailsAsync(context, teamId)` | `Api.Teams.GetByIdAsync(teamId)` |\n| `TeamsInfo.GetMeetingInfoAsync(context, meetingId)` | `Api.Meetings.GetByIdAsync(meetingId)` |\n| `TeamsInfo.SendMessageToTeamsChannelAsync(context, teamId, message)` | `Api.Conversations.CreateAsync(CreateRequest)` then `Api.Conversations.Activities.CreateAsync(conversationId, activity)` |\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/the-api-client/python.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"api-client\">\n  <TabItem value=\"Diff\" default>\n  ```python\n  # highlight-error-start\n-  from botbuilder.core import ActivityHandler, TurnContext\n-  from botbuilder.core.teams import TeamsInfo\n  # highlight-error-end\n  # highlight-success-line\n+  from microsoft_teams.apps import ActivityContext\n+  from microsoft_teams.api import MessageActivity\n\n  # highlight-error-start\n-  class MyActivityHandler(ActivityHandler):\n-      async def on_message_activity(self, turn_context: TurnContext):\n-          members = await TeamsInfo.get_members(turn_context)\n  # highlight-error-end\n  # highlight-success-start\n+  @app.on_message\n+  async def on_message(context: ActivityContext[MessageActivity]):\n+      members = await context.api.conversations.members(context.activity.conversation.id).get_all()\n  # highlight-success-end\n  ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import ActivityHandler, TurnContext\n    from botbuilder.core.teams import TeamsInfo\n\n    class MyActivityHandler(ActivityHandler):\n        async def on_message_activity(self, turn_context: TurnContext):\n            # highlight-next-line\n            members = await TeamsInfo.get_members(turn_context)\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.api import MessageActivity\n    from microsoft_teams.apps import ActivityContext\n\n    @app.on_message\n    async def on_message(context: ActivityContext[MessageActivity]):\n        # highlight-next-line\n        members = await context.api.conversations.members(context.activity.conversation.id).get()\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- api-table -->\n\n| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) |\n|------------------------|----------------------|\n| `TeamsInfo.getMembers(context, user_id)` | `api.conversations.members(conversation_id).get(user_id)` |\n| `TeamsInfo.get_team_details(context, team_id)` | `api.teams.get_by_id(team_id)` |\n| `TeamsInfo.get_meeting_info(context, meeting_id)` | `api.meetings.get_by_id(meeting_id)` |\n| `TeamsInfo.send_message_to_teams_channel(context, team_id, message)` | `api.conversations.create(CreateConversationParams)` then `api.conversations.activities(conversation_id).create(activity)` |\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/the-api-client/typescript.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"api-client\">\n  <TabItem value=\"Diff\" default>\n  ```typescript\n  // highlight-error-start\n-  import {\n-    CloudAdapter,\n-    ConfigurationBotFrameworkAuthentication,\n-    TeamsInfo,\n-  } from 'botbuilder';\n  // highlight-error-end\n  // highlight-success-line\n+  import { App } from '@microsoft/teams.apps';\n\n  // highlight-error-start\n-  const auth = new ConfigurationBotFrameworkAuthentication(process.env);\n-  const adapter = new CloudAdapter(auth);\n  // highlight-error-end\n  // highlight-success-line\n+  const app = new App();\n\n  // highlight-error-start\n-  export class ActivityHandler extends TeamsActivityHandler {\n-    constructor() {\n-      super();\n-      this.onMessage(async (context) => {\n-        const members = await TeamsInfo.getMembers(context);\n-      });\n-    }\n-  }\n  // highlight-error-end\n  // highlight-success-start\n+  app.on('message', async ({ api, activity }) => {\n+    const members = await api.conversations.members(activity.conversation.id).get();\n+  });\n  // highlight-success-end\n  ```\n  </TabItem>\n  <TabItem value=\"BotBuilder\">\n    ```typescript showLineNumbers\n    import {\n      CloudAdapter,\n      ConfigurationBotFrameworkAuthentication,\n      TeamsInfo,\n    } from 'botbuilder';\n\n    const auth = new ConfigurationBotFrameworkAuthentication(process.env);\n    const adapter = new CloudAdapter(auth);\n\n    export class ActivityHandler extends TeamsActivityHandler {\n      constructor() {\n        super();\n        this.onMessage(async (context) => {\n          // highlight-next-line\n          const members = await TeamsInfo.getMembers(context);\n        });\n      }\n    }\n    ```\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```typescript showLineNumbers\n    import { App } from '@microsoft/teams.apps';\n\n    const app = new App();\n\n    app.on('message', async ({ api, activity }) => {\n      // highlight-next-line\n      const members = await api.conversations.members(activity.conversation.id).get();\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- api-table -->\n\n| BotBuilder (TeamsInfo) | Teams SDK (ApiClient) |\n|------------------------|----------------------|\n| `TeamsInfo.getMember(context, userId)` | `api.conversations.members(conversationId).getById(userId)` |\n| `TeamsInfo.getTeamDetails(context, teamId)` | `api.teams.getById(teamId)` |\n| `TeamsInfo.getMeetingInfo(context, meetingId)` | `api.meetings.getById(meetingId)` |\n| `TeamsInfo.sendMessageToTeamsChannel(context, teamId, message)` | `api.conversations.create(CreateConversationParams)` then `api.conversations.activities(conversationId).create(activity)` |\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/user-authentication/csharp.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"user-auth\">\n  <TabItem value=\"BotBuilder\">\n    ```csharp showLineNumbers\n    using Microsoft.Bot.Builder;\n    using Microsoft.Bot.Builder.Dialogs;\n    using Microsoft.Bot.Schema;\n\n    public class MyActivityHandler : TeamsActivityHandler\n    {\n        private readonly ConversationState _conversationState;\n        private readonly UserState _userState;\n        private readonly Dialog _dialog;\n\n        public MyActivityHandler(string connectionName, ConversationState conversationState, UserState userState)\n        {\n            _conversationState = conversationState;\n            _userState = userState;\n            _dialog = new SignInDialog(\"signin\", connectionName);\n        }\n\n        protected override async Task OnMessageActivityAsync(\n            ITurnContext<IMessageActivity> turnContext,\n            CancellationToken cancellationToken)\n        {\n            await _dialog.RunAsync(turnContext, _conversationState.CreateProperty<DialogState>(\"DialogState\"), cancellationToken);\n        }\n\n        public override async Task OnTurnAsync(ITurnContext turnContext, CancellationToken cancellationToken = default)\n        {\n            await base.OnTurnAsync(turnContext, cancellationToken);\n            await _conversationState.SaveChangesAsync(turnContext, false, cancellationToken);\n            await _userState.SaveChangesAsync(turnContext, false, cancellationToken);\n        }\n    }\n\n    public class SignInDialog : ComponentDialog\n    {\n        private readonly string _connectionName;\n\n        public SignInDialog(string id, string connectionName) : base(id)\n        {\n            _connectionName = connectionName;\n\n            AddDialog(new OAuthPrompt(\"OAuthPrompt\", new OAuthPromptSettings\n            {\n                ConnectionName = connectionName,\n                Text = \"Please Sign In\",\n                Title = \"Sign In\",\n                Timeout = 300000\n            }));\n\n            AddDialog(new WaterfallDialog(\"Main\", new WaterfallStep[]\n            {\n                PromptStepAsync,\n                LoginStepAsync\n            }));\n\n            InitialDialogId = \"Main\";\n        }\n\n        private async Task<DialogTurnResult> PromptStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)\n        {\n            return await stepContext.BeginDialogAsync(\"OAuthPrompt\", null, cancellationToken);\n        }\n\n        private async Task<DialogTurnResult> LoginStepAsync(WaterfallStepContext stepContext, CancellationToken cancellationToken)\n        {\n            await stepContext.Context.SendActivityAsync(\"You have been signed in.\", cancellationToken: cancellationToken);\n            return await stepContext.EndDialogAsync(null, cancellationToken);\n        }\n    }\n\n    var storage = new MemoryStorage();\n    var conversationState = new ConversationState(storage);\n    var userState = new UserState(storage);\n    var handler = new MyActivityHandler(\n        builder.Configuration[\"ConnectionName\"],\n        conversationState,\n        userState\n    );\n    ```\n\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```csharp showLineNumbers\n    using Microsoft.Teams.Apps;\n\n    var builder = WebApplication.CreateBuilder(args);\n    var appBuilder = App.Builder().AddOAuth(\"ConnectionName\");\n    var app = builder.Build();\n    var teams = app.UseTeams();\n\n    teams.OnMessage(\"/signout\", async (context, cancellationToken) =>\n    {\n        if (!context.IsSignedIn) return;\n        await context.SignOut(cancellationToken);\n        await context.Send(\"You have been signed out.\", cancellationToken);\n    });\n\n    teams.OnMessage(async (context, cancellationToken) =>\n    {\n        if (!context.IsSignedIn)\n        {\n            await context.SignIn(cancellationToken);\n            return;\n        }\n    });\n\n    teams.OnSignIn(async (_, @event, cancellationToken) =>\n    {\n        await context.Send(\"You have been signed in.\", cancellationToken);\n    });\n\n    app.Run()\n    ```\n\n  </TabItem>\n</Tabs>\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/user-authentication/python.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"user-auth\">\n  <TabItem value=\"BotBuilder\">\n    ```python showLineNumbers\n    from botbuilder.core import (\n        ActivityHandler,\n        ConversationState,\n        UserState,\n        MemoryStorage,\n        BotFrameworkAdapter\n    )\n    from botbuilder.dialogs import (\n        ComponentDialog,\n        OAuthPrompt,\n        OAuthPromptSettings,\n        WaterfallDialog,\n        WaterfallStepContext,\n        DialogSet,\n        DialogTurnStatus\n    )\n\n    class MyActivityHandler(ActivityHandler):\n        def __init__(self, connection_name: str, conversation_state: ConversationState, user_state: UserState):\n            super().__init__()\n            self.conversation_state = conversation_state\n            self.user_state = user_state\n            self.dialog = SignInDialog(\"signin\", connection_name)\n            self.dialog_state = self.conversation_state.create_property(\"DialogState\")\n\n        async def on_message_activity(self, turn_context: TurnContext):\n            await self.dialog.run(turn_context, self.dialog_state)\n\n        async def on_turn(self, turn_context: TurnContext):\n            await super().on_turn(turn_context)\n            await self.conversation_state.save_changes(turn_context)\n            await self.user_state.save_changes(turn_context)\n\n    class SignInDialog(ComponentDialog):\n        def __init__(self, dialog_id: str, connection_name: str):\n            super().__init__(dialog_id)\n            self.connection_name = connection_name\n\n            self.add_dialog(OAuthPrompt(\n                \"OAuthPrompt\",\n                OAuthPromptSettings(\n                    connection_name=connection_name,\n                    text=\"Please Sign In\",\n                    title=\"Sign In\",\n                    timeout=300000\n                )\n            ))\n\n            self.add_dialog(WaterfallDialog(\n                \"Main\",\n                [self.prompt_step, self.login_step]\n            ))\n\n            self.initial_dialog_id = \"Main\"\n\n        async def prompt_step(self, step_context: WaterfallStepContext):\n            return await step_context.begin_dialog(\"OAuthPrompt\")\n\n        async def login_step(self, step_context: WaterfallStepContext):\n            await step_context.context.send_activity(\"You have been signed in.\")\n            return await step_context.end_dialog()\n\n        async def run(self, turn_context: TurnContext, accessor):\n            dialog_set = DialogSet(accessor)\n            dialog_set.add(self)\n\n            dialog_context = await dialog_set.create_context(turn_context)\n            results = await dialog_context.continue_dialog()\n\n            if results.status == DialogTurnStatus.Empty:\n                await dialog_context.begin_dialog(self.id)\n\n    storage = MemoryStorage()\n    conversation_state = ConversationState(storage)\n    user_state = UserState(storage)\n    handler = MyActivityHandler(\n        connection_name,\n        conversation_state,\n        user_state\n    )\n    ```\n\n  </TabItem>\n  <TabItem value=\"Teams SDK\">\n    ```python showLineNumbers\n    from microsoft_teams.apps import ActivityContext, App, SignInEvent\n    from microsoft_teams.api import MessageActivity\n\n    app = App(default_connection_name=connection_name)\n\n    @app.on_message_pattern(\"/signout\")\n    async def on_signout(context: ActivityContext[MessageActivity]):\n        if not context.is_signed_in:\n            return\n        await context.sign_out()\n        await context.send(\"You have been signed out.\")\n\n    @app.on_message\n    async def on_message(context: ActivityContext[MessageActivity]):\n        if not context.is_signed_in:\n            await context.sign_in()\n            return\n\n    @app.event(\"sign_in\")\n    async def on_signin(event: SignInEvent):\n        await context.send(\"You have been signed in.\")\n    ```\n\n  </TabItem>\n</Tabs>\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/botbuilder/user-authentication/typescript.incl.md",
    "content": "<!-- example -->\n\n<Tabs groupId=\"user-auth\">\n    <TabItem value=\"BotBuilder\">\n      ```typescript showLineNumbers\n      import restify from 'restify';\n      import {\n        TeamsActivityHandler,\n        ConversationState,\n        UserState,\n        StatePropertyAccessor,\n        CloudAdapter,\n        ConfigurationBotFrameworkAuthentication,\n        MemoryStorage,\n      } from 'botbuilder';\n\n      import { OAuthPrompt, WaterfallDialog, ComponentDialog } from 'botbuilder-dialogs';\n\n      export class ActivityHandler extends TeamsActivityHandler {\n        private readonly _conversationState: ConversationState;\n        private readonly _userState: UserState;\n        private readonly _dialog: SignInDialog;\n        private readonly _dialogState: StatePropertyAccessor;\n\n        constructor(connectionName: string, conversationState: ConversationState, userState: UserState) {\n          super();\n\n          this._conversationState = conversationState;\n          this._userState = userState;\n          this._dialog = new SignInDialog('signin', connectionName);\n          this._dialogState = this.conversationState.createProperty('DialogState');\n\n          this.onMessage(async (context, next) => {\n            await this._dialog.run(context, this._dialogState);\n            return next();\n          });\n        }\n\n        async run(context) {\n          await super.run(context);\n          await this.conversationState.saveChanges(context, false);\n          await this.userState.saveChanges(context, false);\n        }\n      }\n\n      export class SignInDialog extends ComponentDialog {\n        private readonly _connectionName: string;\n\n        constructor(id, connectionName: string) {\n          super(id);\n          this._connectionName = connectionName;\n\n          this.addDialog(new OAuthPrompt('OAuthPrompt', {\n            connectionName: connectionName,\n            text: 'Please Sign In',\n            title: 'Sign In',\n            timeout: 300000\n          }));\n\n          this.addDialog(new WaterfallDialog('Main', [\n            this.promptStep.bind(this),\n            this.loginStep.bind(this)\n          ]));\n\n          this.initialDialogId = 'Main';\n        }\n\n        async run(context, accessor) {\n          const dialogSet = new DialogSet(accessor);\n          dialogSet.add(this);\n\n          const dialogContext = await dialogSet.createContext(context);\n          const results = await dialogContext.continueDialog();\n\n          if (results.status === DialogTurnStatus.empty) {\n            await dialogContext.beginDialog(this.id);\n          }\n        }\n\n        async promptStep(stepContext) {\n          return await stepContext.beginDialog('OAuthPrompt');\n        }\n\n        async loginStep(stepContext) {\n          await stepContext.context.sendActivity('You have been signed in.');\n          return await stepContext.endDialog();\n        }\n\n        async onBeginDialog(innerDc, options) {\n          const result = await this.interrupt(innerDc);\n          if (result) return result;\n          return await super.onBeginDialog(innerDc, options);\n        }\n\n        async onContinueDialog(innerDc) {\n          const result = await this.interrupt(innerDc);\n          if (result) return result;\n          return await super.onContinueDialog(innerDc);\n        }\n\n        async interrupt(innerDc) {\n          if (innerDc.context.activity.type === ActivityTypes.Message) {\n            const text = innerDc.context.activity.text.toLowerCase();\n\n            if (text === '/signout') {\n              const userTokenClient = innerDc.context.turnState.get(innerDc.context.adapter.UserTokenClientKey);\n              const { activity } = innerDc.context;\n\n              await userTokenClient.signOutUser(activity.from.id, this.connectionName, activity.channelId);\n              await innerDc.context.sendActivity('You have been signed out.');\n              return await innerDc.cancelAllDialogs();\n            }\n          }\n        }\n      }\n\n      const server = restify.createServer();\n      const auth = new ConfigurationBotFrameworkAuthentication(process.env);\n      const adapter = new CloudAdapter(auth);\n      const memoryStorage = new MemoryStorage();\n      const conversationState = new ConversationState(memoryStorage);\n      const userState = new UserState(memoryStorage);\n      const handler = new ActivityHandler(\n        process.env.connectionName,\n        conversationState,\n        userState,\n      );\n\n      server.use(restify.plugins.bodyParser());\n      server.listen(process.env.port || process.env.PORT || 3978, function() {\n          console.log(`\\n${ server.name } listening to ${ server.url }`);\n      });\n\n      server.post('/api/messages', async (req, res) => {\n          await adapter.process(req, res, (context) => bot.run(context));\n      });\n      ```\n\n    </TabItem>\n    <TabItem value=\"Teams SDK\">\n      ```typescript showLineNumbers\n      import { App } from '@microsoft/teams.apps';\n      import { ConsoleLogger } from '@microsoft/teams.common/logging';\n\n      const app = new App({\n        oauth: {\n          defaultConnectionName: process.env.connectionName\n        }\n      });\n\n      app.message('/signout', async ({ send, signout, isSignedIn }) => {\n        if (!isSignedIn) return;\n        await signout();\n        await send('You have been signed out.');\n      });\n\n      app.on('message', async ({ send, signin }) => {\n        if (!await signin()) {\n          return;\n        }\n      });\n\n      app.event('signin', async ({ send }) => {\n        await send('You have been signed in.');\n      });\n\n      (async () => {\n        await app.start();\n      })();\n      ```\n\n    </TabItem>\n  </Tabs>\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/slack-bolt/typescript.incl.md",
    "content": "<!-- installation -->\n\nFirst, let's install the Teams SDK into your project. This will install the Teams SDK alongside any existing packages. After you've completed your migration, you can safely remove the `@microsoft/teams-ai` dependency from your `package.json` file.\n\n```sh\nnpm install @microsoft/teams.apps\n```\n\n<!-- application -->\n\nFirst, let's configure the `App` class in Teams JS. This is equivalent to Slack Bolt's `App` class.\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n\n  ```ts\n    // Setup app\n    // highlight-error-start\n    import { App } from '@slack/bolt';\n\n    const app = new App({\n        signingSecret: process.env.SLACK_SIGNING_SECRET,\n        clientId: process.env.SLACK_CLIENT_ID,\n        clientSecret: process.env.SLACK_CLIENT_SECRET,\n        scopes: [\n            \"channels:manage\",\n            \"channels:read\",\n            \"chat:write\",\n            \"groups:read\",\n            \"incoming-webhook\",\n        ],\n        installerOptions: {\n            authVersion: \"v2\",\n            directInstall: false,\n            installPath: \"/slack/install\",\n            metadata: \"\",\n            redirectUriPath: \"/slack/oauth_redirect\",\n            stateVerification: \"true\",\n            /**\n            * Example pages to navigate to on certain callbacks.\n            */\n            callbackOptions: {\n                success: (installation, installUrlOptions, req, res) => {\n                    res.send(\"The installation succeeded!\");\n                },\n                failure: (error, installUrlOptions, req, res) => {\n                    res.send(\"Something strange happened...\");\n                },\n            },\n            /**\n            * Example validation of installation options using a random state and an\n            * expiration time between requests.\n            */\n            stateStore: {\n                generateStateParam: async (installUrlOptions, now) => {\n                    const state = randomStringGenerator();\n                    const value = { options: installUrlOptions, now: now.toJSON() };\n                    await database.set(state, value);\n                    return state;\n                },\n                verifyStateParam: async (now, state) => {\n                    const value = await database.get(state);\n                    const generated = new Date(value.now);\n                    const seconds = Math.floor(\n                        (now.getTime() - generated.getTime()) / 1000,\n                    );\n                    if (seconds > 600) {\n                        throw new Error(\"The state expired after 10 minutes!\");\n                    }\n                    return value.options;\n                },\n            },\n        },\n    });\n    // highlight-error-end\n    // highlight-success-start\n    import { App } from '@microsoft/teams.apps';\n\n    // Define app\n    const app = new App({\n        clientId: process.env.ENTRA_APP_CLIENT_ID!,\n        clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!,\n        tenantId: process.env.ENTRA_TENANT_ID!,\n    });\n    // highlight-success-end\n\n    // App starts local server with route for /api/messages\n    (async () => {\n        await app.start();\n    })();\n    ```\n  </TabItem>\n  <TabItem value=\"slack\" label=\"Slack Bolt\">\n\n    ```ts\n    import { App } from '@slack/bolt';\n\n    const app = new App({\n        signingSecret: process.env.SLACK_SIGNING_SECRET,\n        clientId: process.env.SLACK_CLIENT_ID,\n        clientSecret: process.env.SLACK_CLIENT_SECRET,\n        scopes: [\n            \"channels:manage\",\n            \"channels:read\",\n            \"chat:write\",\n            \"groups:read\",\n            \"incoming-webhook\",\n        ],\n        installerOptions: {\n            authVersion: \"v2\",\n            directInstall: false,\n            installPath: \"/slack/install\",\n            metadata: \"\",\n            redirectUriPath: \"/slack/oauth_redirect\",\n            stateVerification: \"true\",\n            /**\n            * Example pages to navigate to on certain callbacks.\n            */\n            callbackOptions: {\n                success: (installation, installUrlOptions, req, res) => {\n                    res.send(\"The installation succeeded!\");\n                },\n                failure: (error, installUrlOptions, req, res) => {\n                    res.send(\"Something strange happened...\");\n                },\n            },\n            /**\n            * Example validation of installation options using a random state and an\n            * expiration time between requests.\n            */\n            stateStore: {\n                generateStateParam: async (installUrlOptions, now) => {\n                    const state = randomStringGenerator();\n                    const value = { options: installUrlOptions, now: now.toJSON() };\n                    await database.set(state, value);\n                    return state;\n                },\n                verifyStateParam: async (now, state) => {\n                    const value = await database.get(state);\n                    const generated = new Date(value.now);\n                    const seconds = Math.floor(\n                        (now.getTime() - generated.getTime()) / 1000,\n                    );\n                    if (seconds > 600) {\n                        throw new Error(\"The state expired after 10 minutes!\");\n                    }\n                    return value.options;\n                },\n            },\n        },\n    });\n\n    // App starts local server with route for /slack/events\n    (async () => {\n        await app.start();\n    })();\n    ```\n  </TabItem>\n  <TabItem value=\"teams\" label=\"Teams SDK\">\n\n    ```ts\n    import { App } from '@microsoft/teams.apps';\n\n    // Define app\n    const app = new App({\n        clientId: process.env.ENTRA_APP_CLIENT_ID!,\n        clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!,\n        tenantId: process.env.ENTRA_TENANT_ID!,\n    });\n\n    // App starts local server with route for /api/messages\n    // To reuse your restify or other server,\n    // create a custom `HttpPlugin`.\n    (async () => {\n        await app.start();\n    })();\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- message-handlers -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n\n    ```ts\n    // triggers user sends \"hi\" or \"@bot hi\"\n    // highlight-error-start\n    app.message(\"hi\", async ({ message, say }) => {\n        // Handle only newly posted messages here\n        if (message.subtype) return;\n        await say(`Hello, <@${message.user}>`);\n    });\n    // highlight-error-end\n    // highlight-success-start\n    app.message(\"hi\", async ({ send, activity }) => {\n      await send(`Hello, ${activity.from.name}!`);\n    });\n    // highlight-success-end\n    // listen for ANY message to be received\n    // highlight-error-start\n    app.message(async ({ message, say }) => {\n        // Handle only newly posted messages here\n        if (message.subtype) return;\n        // echo back users request\n        await say(`you said: ${message.text}`);\n    });\n    // highlight-error-end\n    // highlight-success-start\n    app.on('message', async ({ send, activity }) => {\n        // echo back users request\n        await send(`you said: ${activity.text}`);\n    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"slack\" label=\"Slack Bolt\">\n\n    ```ts\n    // triggers when user sends a message containing \"hi\"\n    app.message(\"hi\", async ({ message, say }) => {\n        // Handle only newly posted messages here\n        if (message.subtype) return;\n        await say(`Hello, <@${message.user}>`);\n    });\n    // listen for ANY message\n    app.message(async ({ message, say }) => {\n        // Handle only newly posted messages here\n        if (message.subtype) return;\n        // echo back users request\n        await say(`you said: ${message.text}`);\n    });\n    ```\n  </TabItem>\n  <TabItem value=\"teams\" label=\"Teams SDK\">\n\n    ```ts\n    // triggers when user sends \"hi\" or \"@bot hi\"\n    app.message(\"hi\", async ({ send, activity }) => {\n      await send(`Hello, ${activity.from.name}!`);\n    });\n    // listen for ANY message to be received\n    app.on('message', async ({ send, activity }) => {\n        // echo back users request\n        await send(`you said: ${activity.text}`);\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- adaptive-cards -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n\n    ```ts\n    // highlight-error-start\n    app.message('card', async (client) => {\n        await say({\n            blocks: [\n                {\n                    type: 'section',\n                    text: {\n                        type: 'plain_text',\n                        text: 'Hello, world!',\n                    },\n                },\n            ],\n        });\n    });\n    // highlight-error-end\n    // highlight-success-start\n    import { Card, TextBlock } from '@microsoft/teams.cards';\n\n    app.message('/card', async ({ send }) => {\n        await send(\n            new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false }))\n                .withOptions({\n                    width: 'Full',\n                })\n        );\n    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"slack\" label=\"Slack Bolt\">\n    For existing cards like this, the simplest way to convert that to Teams SDK is this:\n\n    ```ts\n    app.message('card', async (client) => {\n        await say({\n            blocks: [\n                {\n                    type: 'section',\n                    text: {\n                        type: 'plain_text',\n                        text: 'Hello, world!',\n                    },\n                },\n            ],\n        });\n    });\n    ```\n\n  </TabItem>\n  <TabItem value=\"teams\" label=\"Teams SDK\">\n    For a more thorough port, you could also do the following:\n\n    ```ts\n    import { Card, TextBlock } from '@microsoft/teams.cards';\n\n    app.message('/card', async ({ send }) => {\n      await send(\n        new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })).withOptions({\n          width: 'Full',\n        })\n      );\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- graph -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n\n    ```ts\n    // highlight-error-start\n    // TODO: Configure App class with user OAuth permissions and install app for user\n\n    app.message('me', async ({ client, message }) => {\n        const me = await client.users.info({ user: message.user });\n        await client.send(JSON.stringify(me));\n    });\n    // highlight-error-end\n    // highlight-success-start\n    import { App } from '@microsoft/teams.apps';\n    import * as endpoints from '@microsoft/teams.graph-endpoints';\n\n    const app = new App({\n        // ... rest of App config\n        oauth: {\n            // The key here should match the OAuth Connection setting\n            // defined in your Azure Bot resource.\n            defaultConnectionName: 'graph',\n        },\n    });\n\n    app.message('me', async ({ signin, userGraph, send }) => {\n        if (!await signin()) {\n            return;\n        }\n        const me = await userGraph.call(endpoints.me.get);\n        await send(JSON.stringify(me));\n    });\n    // highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"slack\" label=\"Slack Bolt\">\n\n    ```ts\n    // TODO: Configure App class with user OAuth permissions and install app for user\n\n    app.message('me', async ({ client, message }) => {\n        const me = await client.users.info({ user: message.user });\n        await client.send(JSON.stringify(me));\n    });\n    ```\n\n  </TabItem>\n  <TabItem value=\"teams\" label=\"Teams SDK\">\n\n    ```ts\n    import { App } from '@microsoft/teams.apps';\n    import * as endpoints from '@microsoft/teams.graph-endpoints';\n\n    const app = new App({\n        // ... rest of App config\n        oauth: {\n            // The key here should match the OAuth Connection setting\n            // defined in your Azure Bot resource.\n            defaultConnectionName: 'graph',\n        },\n    });\n\n    app.message('me', async ({ signin, userGraph, send }) => {\n        if (!await signin()) {\n            return;\n        }\n        const me = await userGraph.call(endpoints.me.get);\n        await send(JSON.stringify(me));\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- 3p-auth -->\n\n```ts\nimport { App } from '@microsoft/teams.apps';\n\nconst app = new App({\n    // ... rest of App config\n    oauth: {\n        // The key here should match the OAuth Connection setting\n        // defined in your Azure Bot resource.\n        defaultConnectionName: 'custom',\n    },\n});\n\napp.message('me', async ({ activity, signin, token, send }) => {\n    // In production, it is probably better to implement a local cache.\n    // (e.g. \\`activity.from.id\\` <-> token).\n    // Otherwise this triggers an API call to Azure Token Service on every inbound message.\n    if (!await signin()) {\n        return;\n    }\n\n    // Call external API\n    const response = await fetch('https://example.com/api/helloworld', {\n        method: 'POST',\n        headers: {\n            \"Authorization\": token,\n        },\n    });\n    const result = await response.json();\n    await send(JSON.stringify(result));\n});\n```\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/v1/python.incl.md",
    "content": "<!-- botbuilder-adapter-note -->\n\nWe'll also discuss how you can migrate features over incrementally via the [botbuilder plugin](./botbuilder).\n\n<!-- installation -->\n\nFirst, let's install Teams SDK into your project. Notably, this won't replace any existing installation of Teams SDK. When you've completed your migration, you can safely remove the `teams-ai` dependency from your `pyproject.toml` file.\n\n```sh\npip install microsoft-teams-apps\n```\n\n<!-- app-migration -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    # in api.py\n-    from http import HTTPStatus\n-\n-    from aiohttp import web\n-    from botbuilder.core.integration import aiohttp_error_middleware\n-\n-    from bot import app\n-\n-    routes = web.RouteTableDef()\n-\n-    @routes.post(\"/api/messages\")\n-    async def on_messages(req: web.Request) -> web.Response:\n-        res = await app.process(req)\n-        if res is not None:\n-            return res\n-        return web.Response(status=HTTPStatus.OK)\n-\n-    # in bot.py\n-    import sys\n-    import traceback\n-\n-    from botbuilder.core import TurnContext, MemoryStorage\n-    from teams import Application, ApplicationOptions, TeamsAdapter\n-    from teams.state import TurnState\n-\n-    config = Config()\n-    storage = MemoryStorage()\n-    app = Application[TurnState](\n-        ApplicationOptions(\n-            bot_app_id=config.APP_ID,\n-            adapter=TeamsAdapter(config),\n-            storage=storage\n-        )\n-    )\n-\n-    @app.activity(\"message\")\n-    async def on_message(context: TurnContext, _state: TurnState):\n-        await context.send_activity(f\"you said: {context.activity.text}\")\n-        return True\n-\n-    @app.error\n-    async def on_error(context: TurnContext, error: Exception):\n-        print(f\"\\n [on_turn_error] unhandled error: {error}\", file=sys.stderr)\n-        traceback.print_exc()\n-        await context.send_activity(\"The bot encountered an error or bug.\")\n    # highlight-error-end\n    # highlight-success-start\n+    # in main.py\n+    import asyncio\n+    import logging\n+\n+    from microsoft_teams.api import MessageActivity\n+    from microsoft_teams.apps import ActivityContext, App, ErrorEvent\n+    from microsoft_teams.common import LocalStorage\n+\n+    logger = logging.getLogger(__name__)\n+\n+    # Define the app\n+    app = App()\n+\n+    # Optionally create local storage\n+    storage: LocalStorage[str] = LocalStorage()\n+\n+    @app.on_message\n+    async def handle_message(ctx: ActivityContext[MessageActivity]):\n+        await ctx.send(f\"You said '{ctx.activity.text}'\")\n+\n+    # Listen for errors\n+    @app.event(\"error\")\n+    async def handle_error(event: ErrorEvent) -> None:\n+        \"\"\"Handle errors.\"\"\"\n+        logger.error(f\"Error occurred: {event.error}\")\n+        if event.context:\n+            logger.warning(f\"Context: {event.context}\")\n+\n+    if __name__ == \"__main__\":\n+        asyncio.run(app.start())\n    # highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```python\n    # in main.py\n    import asyncio\n    import logging\n\n    from microsoft_teams.api import MessageActivity\n    from microsoft_teams.apps import ActivityContext, App, ErrorEvent\n    from microsoft_teams.common import LocalStorage\n\n    logger = logging.getLogger(__name__)\n\n    # Define the app\n    app = App()\n\n    # Optionally create local storage\n    storage: LocalStorage[str] = LocalStorage()\n\n    @app.on_message\n    async def handle_message(ctx: ActivityContext[MessageActivity]):\n        await ctx.send(f\"You said '{ctx.activity.text}'\")\n\n    # Listen for errors\n    @app.event(\"error\")\n    async def handle_error(event: ErrorEvent) -> None:\n        \"\"\"Handle errors.\"\"\"\n        logger.error(f\"Error occurred: {event.error}\")\n        if event.context:\n            logger.warning(f\"Context: {event.context}\")\n\n\n    if __name__ == \"__main__\":\n        asyncio.run(app.start())\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    # in api.py\n    from http import HTTPStatus\n\n    from aiohttp import web\n    from botbuilder.core.integration import aiohttp_error_middleware\n\n    from bot import app\n\n    routes = web.RouteTableDef()\n\n\n    @routes.post(\"/api/messages\")\n    async def on_messages(req: web.Request) -> web.Response:\n        res = await app.process(req)\n\n        if res is not None:\n            return res\n\n        return web.Response(status=HTTPStatus.OK)\n\n\n    api = web.Application(middlewares=[aiohttp_error_middleware])\n    api.add_routes(routes)\n\n    # in app.py\n    from aiohttp import web\n\n    from api import api\n    from config import Config\n\n    if __name__ == \"__main__\":\n        web.run_app(api, host=\"localhost\", port=Config.PORT)\n\n    # in bot.py\n    import sys\n    import traceback\n\n    from botbuilder.core import TurnContext, MemoryStorage\n    from teams import Application, ApplicationOptions, TeamsAdapter\n    from teams.state import TurnState\n\n    from config import Config\n\n    config = Config()\n    storage = MemoryStorage()\n    app = Application[TurnState](\n        ApplicationOptions(\n            bot_app_id=config.APP_ID,\n            adapter=TeamsAdapter(config),\n            storage=storage\n        )\n    )\n\n    @app.activity(\"message\")\n    async def on_message(context: TurnContext, _state: TurnState):\n        await context.send_activity(f\"you said: {context.activity.text}\")\n        return True\n\n\n    @app.error\n    async def on_error(context: TurnContext, error: Exception):\n        # This check writes out errors to console log .vs. app insights.\n        # NOTE: In production environment, you should consider logging this to Azure\n        #       application insights.\n        print(f\"\\n [on_turn_error] unhandled error: {error}\", file=sys.stderr)\n        traceback.print_exc()\n\n        # Send a message to the user\n        await context.send_activity(\"The bot encountered an error or bug.\")\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- activity-handlers-intro -->\n\nslightly\n\n<!-- message-handlers -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    # Triggered when user sends \"hi\"\n-    @app.message(re.compile(r\"hi\", re.IGNORECASE))\n-    async def greeting(context: TurnContext, _state: AppTurnState) -> bool:\n-        await context.send_activity(\"Hi there!\")\n-        return True\n-\n-    # Listens for ANY message received\n-    @app.activity(\"message\")\n-    async def on_message(context: TurnContext, _state: TurnState):\n-        # Echoes back what user said\n-        await context.send_activity(f\"you said: {context.activity.text}\")\n-        return True\n    # highlight-error-end\n    # highlight-success-start\n+    # Triggered when user sends \"hi\", \"hello\", or \"greetings\"\n+    @app.on_message_pattern(re.compile(r\"hello|hi|greetings\"))\n+    async def handle_greeting(ctx: ActivityContext[MessageActivity]) -> None:\n+        await ctx.reply(\"Hello! How can I assist you today?\")\n+\n+    # Listens for ANY message received\n+    @app.on_message\n+    async def handle_message(ctx: ActivityContext[MessageActivity]):\n+        # Sends a typing indicator\n+        await ctx.reply(TypingActivityInput())\n+        await ctx.send(f\"You said '{ctx.activity.text}'\")\n    # highlight-success-end\n    ```\n\n  </TabItem>\n<TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```python\n    # Triggered when user sends \"hi\", \"hello\", or \"greetings\"\n    @app.on_message_pattern(re.compile(r\"hello|hi|greetings\"))\n    async def handle_greeting(ctx: ActivityContext[MessageActivity]) -> None:\n        await ctx.reply(\"Hello! How can I assist you today?\")\n\n    # Listens for ANY message received\n    @app.on_message\n    async def handle_message(ctx: ActivityContext[MessageActivity]):\n        # Sends a typing indicator\n        await ctx.reply(TypingActivityInput())\n        await ctx.send(f\"You said '{ctx.activity.text}'\")\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    # Triggered when user sends \"hi\"\n    @app.message(re.compile(r\"hi\", re.IGNORECASE))\n    async def greeting(context: TurnContext, _state: AppTurnState) -> bool:\n        await context.send_activity(\"Hi there!\")\n        return True\n\n    # Listens for ANY message received\n    @app.activity(\"message\")\n    async def on_message(context: TurnContext, _state: TurnState):\n        # Echoes back what user said\n        await context.send_activity(f\"you said: {context.activity.text}\")\n        return True\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- task-modules-note -->\n\nNote that on Microsoft Teams, task modules have been renamed to dialogs.\n\n<!-- task-modules -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    @app.task_module.fetch(\"connect-account\")\n-    async def on_connect_account(context: TurnContext, _state: TurnState):\n-        return TaskModuleTaskInfo(\n-            title=\"Connect your Microsoft 365 account\",\n-            height=\"medium\",\n-            width=\"medium\",\n-            url=f\"https://{config.NEXT_PUBLIC_BOT_DOMAIN}/connections\",\n-            fallbackUrl=f\"https://{config.NEXT_PUBLIC_BOT_DOMAIN}/connections\",\n-            completionBotId=config.NEXT_PUBLIC_BOT_ID,\n-        )\n-\n-    @app.task_modules.submit(\"connect-account\")\n-    async def on_submit_connect_account(context: TurnContext, _state: TurnState, data: Dict[str, Any]):\n-        print(json.dumps(data))\n-        await context.send_activity(\"You are all set! Now, how can I help you today?\")\n-        return None\n    # highlight-error-end\n    # highlight-success-start\n+    @app.on_dialog_open\n+    async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):\n+        data: Optional[Any] = ctx.activity.value.data\n+        dialog_type = data.get(\"opendialogtype\") if data else None\n+\n+        if dialog_type == \"some_type\":\n+             return InvokeResponse(\n+                body=TaskModuleResponse(\n+                    task=TaskModuleContinueResponse(\n+                        value=UrlTaskModuleTaskInfo(\n+                            title=\"Dialog title\",\n+                            height=\"medium\",\n+                            width=\"medium\",\n+                            url= f\"https://${os.getenv(\"YOUR_WEBSITE_DOMAIN\")}/some-path\",\n+                            fallback_url= f\"https://${os.getenv(\"YOUR_WEBSITE_DOMAIN\")}/fallback-path-for-web\",\n+                            completion_bot_id= os.getenv(\"ENTRA_APP_CLIENT_ID\"),\n+                        )\n+                    )\n+                )\n+            )\n+\n+    @app.on_dialog_submit\n+    async def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):\n+        data: Optional[Any] = ctx.activity.value.data\n+        dialog_type = data.get(\"submissiondialogtype\") if data else None\n+\n+        if dialog_type == \"some_type\":\n+            await ctx.send(json.dumps(ctx.activity.value))\n+\n+        return TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Received submit\"))\n    # highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```python\n    @app.on_dialog_open\n    async def handle_dialog_open(ctx: ActivityContext[TaskFetchInvokeActivity]):\n        data: Optional[Any] = ctx.activity.value.data\n        dialog_type = data.get(\"opendialogtype\") if data else None\n\n        if dialog_type == \"some_type\":\n             return InvokeResponse(\n                body=TaskModuleResponse(\n                    task=TaskModuleContinueResponse(\n                        value=UrlTaskModuleTaskInfo(\n                            title=\"Dialog title\",\n                            height=\"medium\",\n                            width=\"medium\",\n                            url= f\"https://${os.getenv(\"YOUR_WEBSITE_DOMAIN\")}/some-path\",\n                            fallback_url= f\"https://${os.getenv(\"YOUR_WEBSITE_DOMAIN\")}/fallback-path-for-web\",\n                            completion_bot_id= os.getenv(\"ENTRA_APP_CLIENT_ID\"),\n                        )\n                    )\n                )\n            )\n\n    @app.on_dialog_submit\n    async def handle_dialog_submit(ctx: ActivityContext[TaskSubmitInvokeActivity]):\n        data: Optional[Any] = ctx.activity.value.data\n        dialog_type = data.get(\"submissiondialogtype\") if data else None\n\n        if dialog_type == \"some_type\":\n            await ctx.send(json.dumps(ctx.activity.value))\n\n        return TaskModuleResponse(task=TaskModuleMessageResponse(value=\"Received submit\"))\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    @app.task_module.fetch(\"connect-account\")\n    async def on_connect_account(context: TurnContext, _state: TurnState):\n        return TaskModuleTaskInfo(\n            title=\"Connect your Microsoft 365 account\",\n            height=\"medium\",\n            width=\"medium\",\n            url=f\"https://{config.NEXT_PUBLIC_BOT_DOMAIN}/connections\",\n            fallbackUrl=f\"https://{config.NEXT_PUBLIC_BOT_DOMAIN}/connections\",\n            completionBotId=config.NEXT_PUBLIC_BOT_ID,\n        )\n\n    @app.task_modules.submit(\"connect-account\")\n    async def on_submit_connect_account(context: TurnContext, _state: TurnState, data: Dict[str, Any]):\n        print(json.dumps(data))\n        await context.send_activity(\"You are all set! Now, how can I help you today?\")\n        return None\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- adaptive-cards -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    @app.message(\"/card\")\n-    async def adaptive_card(context: TurnContext, _state: AppTurnState) -> bool:\n-        attachment = CardFactory.adaptive_card(\n-             {\n-                \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n-                \"version\": \"1.6\",\n-                \"type\": \"AdaptiveCard\",\n-                \"body\": [\n-                    {\n-                        \"text\": \"Hello, world!\",\n-                        \"wrap\": True,\n-                        \"type\": \"TextBlock\",\n-                    },\n-                ],\n-                \"msteams\": {\n-                    \"width\": \"Full\"\n-                }\n-            }\n-        )\n-        await context.send_activity(Activity(attachments=[attachment]))\n-        return True\n    # highlight-error-end\n    # highlight-success-start\n+    @app.on_message_pattern(\"/card\")\n+    async def handle_card_message(ctx: ActivityContext[MessageActivity]):\n+        print(f\"[CARD] Card requested by: {ctx.activity.from_}\")\n+        card = AdaptiveCard.model_validate(\n+            {\n+                \"schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n+                \"version\": \"1.6\",\n+                \"type\": \"AdaptiveCard\",\n+                \"body\": [\n+                    {\n+                        \"text\": \"Hello, world!\",\n+                        \"wrap\": True,\n+                        \"type\": \"TextBlock\",\n+                    },\n+                ],\n+                \"msteams\": {\n+                    \"width\": \"Full\"\n+                }\n+            }\n+        )\n+        await ctx.send(card)\n    # highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2-option1\" label=\"Teams SDK v2 (Option 1)\">\n    For existing cards like this, the simplest way to convert that to Teams SDK is this:\n\n    ```python\n    @app.on_message_pattern(\"/card\")\n    async def handle_card_message(ctx: ActivityContext[MessageActivity]):\n        print(f\"[CARD] Card requested by: {ctx.activity.from_}\")\n        card = AdaptiveCard.model_validate(\n            {\n                \"schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n                \"version\": \"1.6\",\n                \"type\": \"AdaptiveCard\",\n                \"body\": [\n                    {\n                        \"text\": \"Hello, world!\",\n                        \"wrap\": True,\n                        \"type\": \"TextBlock\",\n                    },\n                ],\n                \"msteams\": {\n                    \"width\": \"Full\"\n                }\n            }\n        )\n        await ctx.send(card)\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2-option2\" label=\"Teams SDK v2 (Option 2)\">\n    For a more thorough port, you could also do the following:\n\n    ```python\n    @app.on_message_pattern(\"/card\")\n    async def handle_card_message(ctx: ActivityContext[MessageActivity]):\n        card = AdaptiveCard(\n            schema=\"http://adaptivecards.io/schemas/adaptive-card.json\",\n            body=[\n                TextBlock(text=\"Hello, world\", wrap=True, weight=\"Bolder\"),\n            ],\n            ms_teams=TeamsCardProperties(width='full'),\n        )\n        await ctx.send(card)\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    @app.message(\"/card\")\n    async def adaptive_card(context: TurnContext, _state: AppTurnState) -> bool:\n        attachment = CardFactory.adaptive_card(\n             {\n                \"$schema\": \"http://adaptivecards.io/schemas/adaptive-card.json\",\n                \"version\": \"1.6\",\n                \"type\": \"AdaptiveCard\",\n                \"body\": [\n                    {\n                        \"text\": \"Hello, world!\",\n                        \"wrap\": True,\n                        \"type\": \"TextBlock\",\n                    },\n                ],\n                \"msteams\": {\n                    \"width\": \"Full\"\n                }\n            }\n        )\n        await context.send_activity(Activity(attachments=[attachment]))\n        return True\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- authentication -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    app = Application[TurnState[ConversationState, UserState, TempState]](\n-        ApplicationOptions(\n-            bot_app_id=config.APP_ID,\n-            storage=MemoryStorage(),\n-            adapter=TeamsAdapter(config),\n-            auth=AuthOptions(\n-                default=\"graph\",\n-                auto=True,\n-                settings={\n-                    \"graph\": OAuthOptions(\n-                        connection_name=config.OAUTH_CONNECTION_NAME,\n-                        title=\"Sign In\",\n-                        text=\"please sign in\",\n-                        end_on_invalid_message=True,\n-                        enable_sso=True,\n-                    ),\n-                },\n-            ),\n-        )\n-    )\n-\n-    auth = app.auth.get(\"graph\")\n-\n-    @app.message(\"/signout\")\n-    async def on_sign_out(\n-        context: TurnContext, state: TurnState[ConversationState, UserState, TempState]\n-    ):\n-        await auth.sign_out(context, state)\n-        await context.send_activity(\"you are now signed out...👋\")\n-        return False\n-\n-    @auth.on_sign_in_success\n-    async def on_sign_in_success(\n-        context: TurnContext, state: TurnState[ConversationState, UserState, TempState]\n-    ):\n-        await context.send_activity(\"successfully logged in!\")\n-        await context.send_activity(f\"token: {state.temp.auth_tokens['graph']}\")\n-\n-    @auth.on_sign_in_failure\n-    async def on_sign_in_failure(\n-        context: TurnContext,\n-        _state: TurnState[ConversationState, UserState, TempState],\n-        _res: SignInResponse,\n-    ):\n-        await context.send_activity(\"failed to login...\")\n    # highlight-error-end\n    # highlight-success-start\n+    app = App()\n+\n+    @app.on_message\n+    async def handle_message(ctx: ActivityContext[MessageActivity]):\n+        ctx.logger.info(\"User requested sign-in.\")\n+        if ctx.is_signed_in:\n+            await ctx.send(\"You are already signed in.\")\n+        else:\n+            await ctx.sign_in()\n+\n+    @app.on_message_pattern(\"/signout\")\n+    async def handle_sign_out(ctx: ActivityContext[MessageActivity]):\n+        await ctx.sign_out()\n+        await ctx.send(\"You have been signed out.\")\n+\n+    @app.event(\"sign_in\")\n+    async def handle_sign_in(event: SignInEvent):\n+        \"\"\"Handle sign-in events.\"\"\"\n+        await event.activity_ctx.send(\"You are now signed in!\")\n+\n+    @app.event(\"error\")\n+    async def handle_error(event: ErrorEvent):\n+        \"\"\"Handle error events.\"\"\"\n+        print(f\"Error occurred: {event.error}\")\n+        if event.context:\n+            print(f\"Context: {event.context}\")\n    # highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```python\n    app = App()\n\n    @app.on_message\n    async def handle_message(ctx: ActivityContext[MessageActivity]):\n        ctx.logger.info(\"User requested sign-in.\")\n        if ctx.is_signed_in:\n            await ctx.send(\"You are already signed in.\")\n        else:\n            await ctx.sign_in()\n\n    @app.on_message_pattern(\"/signout\")\n    async def handle_sign_out(ctx: ActivityContext[MessageActivity]):\n        await ctx.sign_out()\n        await ctx.send(\"You have been signed out.\")\n\n    @app.event(\"sign_in\")\n    async def handle_sign_in(event: SignInEvent):\n        \"\"\"Handle sign-in events.\"\"\"\n        await event.activity_ctx.send(\"You are now signed in!\")\n\n    @app.event(\"error\")\n    async def handle_error(event: ErrorEvent):\n        \"\"\"Handle error events.\"\"\"\n        print(f\"Error occurred: {event.error}\")\n        if event.context:\n            print(f\"Context: {event.context}\")\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    app = Application[TurnState[ConversationState, UserState, TempState]](\n        ApplicationOptions(\n            bot_app_id=config.APP_ID,\n            storage=MemoryStorage(),\n            adapter=TeamsAdapter(config),\n            auth=AuthOptions(\n                default=\"graph\",\n                auto=True,\n                settings={\n                    \"graph\": OAuthOptions(\n                        connection_name=config.OAUTH_CONNECTION_NAME,\n                        title=\"Sign In\",\n                        text=\"please sign in\",\n                        end_on_invalid_message=True,\n                        enable_sso=True,\n                    ),\n                },\n            ),\n        )\n    )\n\n    auth = app.auth.get(\"graph\")\n\n    @app.message(\"/signout\")\n    async def on_sign_out(\n        context: TurnContext, state: TurnState[ConversationState, UserState, TempState]\n    ):\n        await auth.sign_out(context, state)\n        await context.send_activity(\"you are now signed out...👋\")\n        return False\n\n    @auth.on_sign_in_success\n    async def on_sign_in_success(\n        context: TurnContext, state: TurnState[ConversationState, UserState, TempState]\n    ):\n        await context.send_activity(\"successfully logged in!\")\n        await context.send_activity(f\"token: {state.temp.auth_tokens['graph']}\")\n\n    @auth.on_sign_in_failure\n    async def on_sign_in_failure(\n        context: TurnContext,\n        _state: TurnState[ConversationState, UserState, TempState],\n        _res: SignInResponse,\n    ):\n        await context.send_activity(\"failed to login...\")\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- ai-content -->\n\n### Action planner\n\nWhen we created Teams SDK, LLM's didn't natively support tool calling or orchestration. A lot has changed since then, which is why we decided to deprecate `ActionPlanner` from Teams SDK, and replace it with something a bit more lightweight. Notably, Teams SDK had two similar concepts: functions and actions. In Teams SDK, these are consolidated into functions.\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    # Create AI components\n-    model = OpenAIModel(\n-        OpenAIModelOptions(api_key=config.OPENAI_KEY, default_model=\"gpt-4o\")\n-    )\n-\n-    prompts = PromptManager(\n-        PromptManagerOptions(prompts_folder=f\"{os.path.dirname(__file__)}/prompts\")\n-    )\n-\n-    # Define a prompt function for getting the current status of the lights\n-    @prompts.function(\"get_light_status\")\n-    async def on_get_light_status(context, state, functions, tokenizer, args):\n-        return \"on\" if state.get(\"conversation.lightsOn\") else \"off\"\n-\n-    planner = ActionPlanner(\n-        ActionPlannerOptions(model=model, prompts=prompts, default_prompt=\"tools\")\n-    )\n-\n-    # Define storage and application\n-    storage = MemoryStorage()\n-    app = Application[AppTurnState](\n-        ApplicationOptions(\n-            bot_app_id=config.APP_ID,\n-            storage=storage,\n-            adapter=TeamsAdapter(config),\n-            ai=AIOptions(planner=planner),\n-        )\n-    )\n-\n-    # Register action handlers\n-    @app.ai.action(\"LightsOn\")\n-    async def on_lights_on(context: ActionTurnContext, state: AppTurnState):\n-        state.conversation.lights_on = True\n-        return \"the lights are now on\"\n-\n-    @app.ai.action(\"LightsOff\")\n-    async def on_lights_off(context: ActionTurnContext, state: AppTurnState):\n-        state.conversation.lights_on = False\n-        return \"the lights are now off\"\n-\n-    @app.ai.action(\"Pause\")\n-    async def on_pause(context: ActionTurnContext, state: AppTurnState):\n-        time_ms = int(context.data[\"time\"]) if context.data[\"time\"] else 1000\n-        time.sleep(time_ms / 1000)\n-        return \"done pausing\"\n    # highlight-error-end\n    # highlight-success-start\n+    import asyncio\n+    from microsoft_teams.ai import ChatPrompt\n+    from microsoft_teams.api import MessageActivity\n+    from microsoft_teams.apps import ActivityContext, App\n+    from microsoft_teams.common import LocalStorage\n+    from microsoft_teams.openai import OpenAICompletionsAIModel\n+\n+    storage = LocalStorage()\n+    app = App()\n+\n+    @app.on_message\n+    async def handle_message(ctx: ActivityContext[MessageActivity]):\n+        state = storage.get(ctx.activity.from_.id) or {\"lights_on\": False}\n+\n+        prompt = ChatPrompt(\n+            instructions=\"You are a helpful assistant that can control lights.\",\n+            model=OpenAICompletionsAIModel(model=\"gpt-4o\"),\n+        )\n+\n+        # Define functions inline\n+        prompt.function(\n+            \"get_light_status\",\n+            \"Gets the current status of the lights\",\n+            lambda: \"on\" if state[\"lights_on\"] else \"off\"\n+        )\n+\n+        prompt.function(\n+            \"lights_on\",\n+            \"Turns the lights on\",\n+            lambda: (state.update({\"lights_on\": True}), \"the lights are now on\")[1]\n+        )\n+\n+        prompt.function(\n+            \"lights_off\",\n+            \"Turns the lights off\",\n+            lambda: (state.update({\"lights_on\": False}), \"the lights are now off\")[1]\n+        )\n+\n+        async def pause(time: int):\n+            await asyncio.sleep(time / 1000)\n+            return \"done pausing\"\n+\n+        prompt.function(\n+            \"pause\",\n+            \"Delays for a period of time\",\n+            {\n+                \"type\": \"object\",\n+                \"properties\": {\n+                    \"time\": {\n+                        \"type\": \"number\",\n+                        \"description\": \"The amount of time to delay in milliseconds\"\n+                    }\n+                },\n+                \"required\": [\"time\"]\n+            },\n+            pause\n+        )\n+\n+        result = await prompt.send(ctx.activity.text)\n+        storage.set(ctx.activity.from_.id, state)\n+\n+        if result.response.content:\n+            await ctx.send(result.response.content)\n    # highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    In Teams SDK, there is no `actions.json` file. Instead, function prompts, parameters, etc. are declared in your code.\n\n    ```python\n    import asyncio\n    from microsoft_teams.ai import ChatPrompt\n    from microsoft_teams.api import MessageActivity\n    from microsoft_teams.apps import ActivityContext, App\n    from microsoft_teams.common import LocalStorage\n    from microsoft_teams.openai import OpenAICompletionsAIModel\n\n    storage = LocalStorage()\n    app = App()\n\n    @app.on_message\n    async def handle_message(ctx: ActivityContext[MessageActivity]):\n        state = storage.get(ctx.activity.from_.id) or {\"lights_on\": False}\n\n        prompt = ChatPrompt(\n            instructions=\"You are a helpful assistant that can control lights.\",\n            model=OpenAICompletionsAIModel(model=\"gpt-4o\"),\n        )\n\n        # Define functions inline - no separate actions.json needed\n        prompt.function(\n            \"get_light_status\",\n            \"Gets the current status of the lights\",\n            lambda: \"on\" if state[\"lights_on\"] else \"off\"\n        )\n\n        prompt.function(\n            \"lights_on\",\n            \"Turns the lights on\",\n            lambda: (state.update({\"lights_on\": True}), \"the lights are now on\")[1]\n        )\n\n        prompt.function(\n            \"lights_off\",\n            \"Turns the lights off\",\n            lambda: (state.update({\"lights_on\": False}), \"the lights are now off\")[1]\n        )\n\n        async def pause(time: int):\n            await asyncio.sleep(time / 1000)\n            return \"done pausing\"\n\n        prompt.function(\n            \"pause\",\n            \"Delays for a period of time\",\n            {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"time\": {\n                        \"type\": \"number\",\n                        \"description\": \"The amount of time to delay in milliseconds\"\n                    }\n                },\n                \"required\": [\"time\"]\n            },\n            pause\n        )\n\n        result = await prompt.send(ctx.activity.text)\n        storage.set(ctx.activity.from_.id, state)\n\n        if result.response.content:\n            await ctx.send(result.response.content)\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    # Create AI components\n    model = OpenAIModel(\n        OpenAIModelOptions(api_key=config.OPENAI_KEY, default_model=\"gpt-4o\")\n    )\n\n    prompts = PromptManager(\n        PromptManagerOptions(prompts_folder=f\"{os.path.dirname(__file__)}/prompts\")\n    )\n\n    # Define a prompt function for getting the current status of the lights\n    @prompts.function(\"get_light_status\")\n    async def on_get_light_status(context, state, functions, tokenizer, args):\n        return \"on\" if state.get(\"conversation.lightsOn\") else \"off\"\n\n    planner = ActionPlanner(\n        ActionPlannerOptions(model=model, prompts=prompts, default_prompt=\"tools\")\n    )\n\n    # Define storage and application\n    storage = MemoryStorage()\n    app = Application[AppTurnState](\n        ApplicationOptions(\n            bot_app_id=config.APP_ID,\n            storage=storage,\n            adapter=TeamsAdapter(config),\n            ai=AIOptions(planner=planner),\n        )\n    )\n\n    # Register action handlers\n    @app.ai.action(\"LightsOn\")\n    async def on_lights_on(context: ActionTurnContext, state: AppTurnState):\n        state.conversation.lights_on = True\n        return \"the lights are now on\"\n\n    @app.ai.action(\"LightsOff\")\n    async def on_lights_off(context: ActionTurnContext, state: AppTurnState):\n        state.conversation.lights_on = False\n        return \"the lights are now off\"\n\n    @app.ai.action(\"Pause\")\n    async def on_pause(context: ActionTurnContext, state: AppTurnState):\n        time_ms = int(context.data[\"time\"]) if context.data[\"time\"] else 1000\n        time.sleep(time_ms / 1000)\n        return \"done pausing\"\n\n    @app.ai.action(\"LightStatus\")\n    async def on_lights_status(context: ActionTurnContext, state: AppTurnState):\n        return \"the lights are on\" if state.conversation.lights_on else \"the lights are off\"\n    ```\n\n    And the corresponding `actions.json` file:\n\n    ```json\n    [\n        {\n            \"name\": \"LightsOn\",\n            \"description\": \"Turns on the lights\"\n        },\n        {\n            \"name\": \"LightsOff\",\n            \"description\": \"Turns off the lights\"\n        },\n        {\n            \"name\": \"Pause\",\n            \"description\": \"Delays for a period of time\",\n            \"parameters\": {\n                \"type\": \"object\",\n                \"properties\": {\n                    \"time\": {\n                        \"type\": \"number\",\n                        \"description\": \"The amount of time to delay in milliseconds\"\n                    }\n                },\n                \"required\": [\"time\"]\n            }\n        },\n        {\n            \"name\": \"LightStatus\",\n            \"description\": \"Gets the lights status\"\n        }\n    ]\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- feedback -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```python\n    # highlight-error-start\n-    app = Application[AppTurnState](\n-        ApplicationOptions(\n-            # ... other options\n-            ai=AIOptions(\n-                enable_feedback_loop=enableFeedbackLoop\n-            ),\n-        )\n-    )\n-\n-    @app.message()\n-    async def on_message(context: TurnContext, state: AppTurnState):\n-        await context.send_activity(Activity(text=\"Hey, give me feedback!\", channel_data={\"feedbackLoop\": { \"type\": \"custom\"}}))\n-\n-    @app.feedback_loop()\n-    async def feedback_loop(context: TurnContext, state: AppTurnState, feedback_data: FeedbackLoopData):\n-        print(\"Feedback loop triggered\")\n    # highlight-error-end\n    # highlight-success-start\n+    # Reply with message including feedback buttons\n+    @app.on_message\n+    async def handle_feedback(ctx: ActivityContext[MessageActivity]):\n+        await ctx.send(MessageActivityInput(text=\"Hey, give me feedback!\").add_ai_generated().add_feedback())\n+\n+    @app.on_message_submit_feedback\n+    async def handle_message_feedback(ctx: ActivityContext[MessageSubmitActionInvokeActivity]):\n+        # Custom logic here..\n    # highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```python\n    # Reply with message including feedback buttons\n    @app.on_message\n    async def handle_feedback(ctx: ActivityContext[MessageActivity]):\n        await ctx.send(MessageActivityInput(text=\"Hey, give me feedback!\").add_ai_generated().add_feedback())\n\n    @app.on_message_submit_feedback\n    async def handle_message_feedback(ctx: ActivityContext[MessageSubmitActionInvokeActivity]):\n        # Custom logic here..\n    ```\n\n    _Note:_ In Teams SDK, you do not need to opt into feedback at the `App` level.\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```python\n    app = Application[AppTurnState](\n        ApplicationOptions(\n            # ... other options\n            ai=AIOptions(\n                enable_feedback_loop=enableFeedbackLoop\n            ),\n        )\n    )\n\n    @app.message()\n    async def on_message(context: TurnContext, state: AppTurnState):\n        await context.send_activity(Activity(text=\"Hey, give me feedback!\", channel_data={\"feedbackLoop\": { \"type\": \"custom\"}}))\n\n    @app.feedback_loop()\n    async def feedback_loop(context: TurnContext, state: AppTurnState, feedback_data: FeedbackLoopData):\n        print(\"Feedback loop triggered\")\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- botbuilder-plugin -->\n\n## Incrementally migrating code via botbuilder plugin\n\n:::info\nComparison code coming soon!\n:::\n\nIf you aren't ready to migrate all of your code, you can run your existing Teams SDK code in parallel with Teams SDK. Learn more [here](./botbuilder/integration).\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/v1/typescript.incl.md",
    "content": "<!-- botbuilder-adapter-note -->\n\nWe'll also discuss how you can migrate features over incrementally via the [botbuilder plugin](./botbuilder).\n\n<!-- installation -->\n\nFirst, let's install Teams SDK into your project. Notably, this won't replace any existing installation of Teams SDK. When you've completed your migration, you can safely remove the `@microsoft/teams-ai` dependency from your `package.json` file.\n\n```sh\nnpm install @microsoft/teams.apps\n```\n\n<!-- app-migration -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // highlight-error-start\n-    import {\n-      ConfigurationServiceClientCredentialFactory,\n-      MemoryStorage,\n-      TurnContext,\n-    } from 'botbuilder';\n-    import { Application, TeamsAdapter } from '@microsoft/teams-ai';\n-    import * as restify from 'restify';\n    // highlight-error-end\n    // highlight-success-start\n+    import { App } from '@microsoft/teams.apps';\n+    import { LocalStorage } from '@microsoft/teams.common/storage';\n    // highlight-success-end\n\n    // highlight-error-start\n\n- // Create adapter.\n- const adapter = new TeamsAdapter(\n-        {},\n-        new ConfigurationServiceClientCredentialFactory({\n-            MicrosoftAppId: process.env.ENTRA_APP_CLIENT_ID,\n-            MicrosoftAppPassword: process.env.ENTRA_APP_CLIENT_SECRET,\n-            MicrosoftAppType: 'SingleTenant',\n-            MicrosoftAppTenantId: process.env.ENTRA_APP_TENANT_ID\n-        })\n- );\n\n- // Catch-all for errors.\n- const onTurnErrorHandler = async (context: TurnContext, error: any) => {\n-        console.error(`\\n [onTurnError] unhandled error: ${error}`);\n-        // Send a message to the user\n-        await context.sendActivity('The bot encountered an error or bug.');\n- };\n\n- // Set the onTurnError for the singleton CloudAdapter.\n- adapter.onTurnError = onTurnErrorHandler;\n\n- // Create HTTP server.\n- const server = restify.createServer();\n- server.use(restify.plugins.bodyParser());\n\n- server.listen(process.env.port || process.env.PORT || 3978, () => {\n-        console.log(`\\n${server.name} listening to ${server.url}`);\n- });\n\n- // Define storage and application\n- const app = new Application<ApplicationTurnState>({\n-        storage: new MemoryStorage()\n- });\n\n- // Listen for incoming server requests.\n- server.post('/api/messages', async (req, res) => {\n-        // Route received a request to adapter for processing\n-        await adapter.process(req, res, async (context) => {\n-            // Dispatch to application for routing\n-            await app.run(context);\n-        });\n- });\n  // highlight-error-end\n  // highlight-success-start\n\n* // Define app\n* const app = new App({\n*      clientId: process.env.ENTRA_APP_CLIENT_ID!,\n*      clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!,\n*      tenantId: process.env.ENTRA_TENANT_ID!,\n* });\n\n* // Optionally create local storage\n* const storage = new LocalStorage();\n\n* // Listen for errors\n* app.event('error', async (client) => {\n*      console.error('Error event received:', client.error);\n*      if (client.activity) {\n*        await app.send(\n*          client.activity.conversation.id,\n*          'An error occurred while processing your message.',\n*        );\n*      }\n* });\n\n* // App creates local server with route for /api/messages\n* // To reuse your restify or other server,\n* // create a custom `HttpPlugin`.\n* (async () => {\n*      // starts the server\n*      await app.start();\n* })();\n  // highlight-success-end\n\n      ```\n\n    </TabItem>\n    <TabItem value=\"v2\" label=\"Teams SDK v2\">\n      ```ts\n      import { App } from '@microsoft/teams.apps';\n      import { LocalStorage } from '@microsoft/teams.common/storage';\n\n      // Define app\n      const app = new App({\n        clientId: process.env.ENTRA_APP_CLIENT_ID!,\n        clientSecret: process.env.ENTRA_APP_CLIENT_SECRET!,\n        tenantId: process.env.ENTRA_TENANT_ID!,\n      });\n\n      // Optionally create local storage\n      const storage = new LocalStorage();\n\n      // Listen for errors\n      app.event('error', async (client) => {\n        console.error('Error event received:', client.error);\n        if (client.activity) {\n          await app.send(\n            client.activity.conversation.id,\n            'An error occurred while processing your message.',\n          );\n        }\n      });\n\n      // App creates local server with route for /api/messages\n      // To reuse your restify or other server,\n      // create a custom `HttpPlugin`.\n      (async () => {\n        // starts the server\n        await app.start();\n      })();\n      ```\n\n    </TabItem>\n    <TabItem value=\"v1\" label=\"Teams SDK v1\">\n      ```ts\n      import {\n        ConfigurationServiceClientCredentialFactory,\n        MemoryStorage,\n        TurnContext,\n      } from 'botbuilder';\n      import { Application, TeamsAdapter } from '@microsoft/teams-ai';\n      import * as restify from 'restify';\n\n      // Create adapter.\n      const adapter = new TeamsAdapter(\n          {},\n          new ConfigurationServiceClientCredentialFactory({\n              MicrosoftAppId: process.env.ENTRA_APP_CLIENT_ID,\n              MicrosoftAppPassword: process.env.ENTRA_APP_CLIENT_SECRET,\n              MicrosoftAppType: 'SingleTenant',\n              MicrosoftAppTenantId: process.env.ENTRA_APP_TENANT_ID\n          })\n      );\n\n      // Catch-all for errors.\n      const onTurnErrorHandler = async (context: TurnContext, error: any) => {\n          console.error(`\\n [onTurnError] unhandled error: ${error}`);\n          // Send a message to the user\n          await context.sendActivity('The bot encountered an error or bug.');\n      };\n\n      // Set the onTurnError for the singleton CloudAdapter.\n      adapter.onTurnError = onTurnErrorHandler;\n\n      // Create HTTP server.\n      const server = restify.createServer();\n      server.use(restify.plugins.bodyParser());\n\n      server.listen(process.env.port || process.env.PORT || 3978, () => {\n          console.log(`\\n${server.name} listening to ${server.url}`);\n      });\n\n      // Define storage and application\n      const app = new Application<ApplicationTurnState>({\n          storage: new MemoryStorage()\n      });\n\n      // Listen for incoming server requests.\n      server.post('/api/messages', async (req, res) => {\n          // Route received a request to adapter for processing\n          await adapter.process(req, res, async (context) => {\n              // Dispatch to application for routing\n              await app.run(context);\n          });\n      });\n      ```\n\n    </TabItem>\n  </Tabs>\n\n<!-- activity-handlers-intro -->\n\nActivity handlers in Teams SDK v2 work slightly differently than in v1, with a more streamlined event-based approach.\n\n<!-- message-handlers -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // triggers when user sends \"/hi\" or \"@bot /hi\"\n    // highlight-error-start\n-    app.message(\"/hi\", async (context) => {\n-      await context.sendActivity(\"Hi!\");\n-    });\n    // highlight-error-end\n    // highlight-success-start\n+    app.message('/hi', async (client) => {\n+      // SDK does not auto send typing indicators\n+      await client.send({ type: 'typing' });\n+      await client.send(\"Hi!\");\n+    });\n    // highlight-success-end\n    // listen for ANY message to be received\n    // highlight-error-start\n-    app.activity(\n-      ActivityTypes.Message,\n-      async (context) => {\n-        // echo back users request\n-        await context.sendActivity(\n-          `you said: ${context.activity.text}`\n-        );\n-      }\n-    );\n    // highlight-error-end\n    // highlight-success-start\n+    app.on('message', async (client) => {\n+      await client.send({ type: 'typing' });\n+      await client.send(\n+        `you said \"${client.activity.text}\"`\n+      );\n+    });\n    // highlight-success-end\n    ```\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```ts\n    // triggers when user sends \"/hi\" or \"@bot /hi\"\n    app.message(\"/hi\", async (context) => {\n      await context.sendActivity(\"Hi!\");\n    });\n    // listen for ANY message to be received\n    app.activity(\n      ActivityTypes.Message,\n      async (context) => {\n        // echo back users request\n        await context.sendActivity(\n          `you said: ${context.activity.text}`\n        );\n      }\n    );\n    ```\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```ts\n    // triggers when user sends \"/hi\" or \"@bot /hi\"\n    app.message('/hi', async (client) => {\n      // SDK does not auto send typing indicators\n      await client.send({ type: 'typing' });\n      await client.send(\"Hi!\");\n    });\n    // listen for ANY message to be received\n    app.on('message', async (client) => {\n      await client.send({ type: 'typing' });\n      await client.send(\n        `you said \"${client.activity.text}\"`\n      );\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- task-modules-note -->\n\nNote that on Microsoft Teams, task modules have been renamed to dialogs.\n\n<!-- task-modules -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // highlight-error-start\n-    app.taskModules.fetch('connect-account', async (context, state, data) => {\n-      const taskInfo: TaskModuleTaskInfo = {\n-        title: 'Connect your Microsoft 365 account',\n-        height: 'medium',\n-        width: 'medium',\n-        url: `https://${process.env.NEXT_PUBLIC_BOT_DOMAIN}/connections`,\n-        fallbackUrl: `https://${process.env.NEXT_PUBLIC_BOT_DOMAIN}/connections`,\n-        completionBotId: process.env.NEXT_PUBLIC_BOT_ID,\n-      };\n-      return taskInfo;\n-    });\n-    app.taskModules.submit('connect-account', async (context, state, data) => {\n-      console.log(\n-        `bot-app.ts taskModules.submit(\"connect-account\"): data`,\n-        JSON.stringify(data, null, 4)\n-      );\n-      await context.sendActivity('You are all set! Now, how can I help you today?');\n-      return undefined;\n-    });\n    // highlight-error-end\n    // highlight-success-start\n+    app.on('dialog.open', (client) => {\n+      const dialogType = client.activity.value.data?.opendialogtype;\n+      if (dialogType === 'some-type') {\n+        return {\n+          task: {\n+            type: 'continue',\n+            value: {\n+              title: 'Dialog title',\n+              height: 'medium',\n+              width: 'medium',\n+              url: `https://${process.env.YOUR_WEBSITE_DOMAIN}/some-path`,\n+              fallbackUrl: `https://${process.env.YOUR_WEBSITE_DOMAIN}/fallback-path-for-web`,\n+              completionBotId: process.env.ENTRA_APP_CLIENT_ID!,\n+            },\n+          },\n+        };\n+      }\n+    });\n\n- app.on('dialog.submit', async (client) => {\n-      const dialogType = client.activity.value.data?.submissiondialogtype;\n-      if (dialogType === 'some-type') {\n-        const { data } = client.activity.value;\n-        await client.send(JSON.stringify(data));\n-      }\n-      return undefined;\n- });\n  // highlight-success-end\n\n      ```\n\n    </TabItem>\n    <TabItem value=\"v2\" label=\"Teams SDK v2\">\n      ```ts\n      app.on('dialog.open', (client) => {\n        const dialogType = client.activity.value.data?.opendialogtype;\n        if (dialogType === 'some-type') {\n          return {\n            task: {\n              type: 'continue',\n              value: {\n                title: 'Dialog title',\n                height: 'medium',\n                width: 'medium',\n                url: `https://${process.env.YOUR_WEBSITE_DOMAIN}/some-path`,\n                fallbackUrl: `https://${process.env.YOUR_WEBSITE_DOMAIN}/fallback-path-for-web`,\n                completionBotId: process.env.ENTRA_APP_CLIENT_ID!,\n              },\n            },\n          };\n        }\n      });\n\n      app.on('dialog.submit', async (client) => {\n        const dialogType = client.activity.value.data?.submissiondialogtype;\n        if (dialogType === 'some-type') {\n          const { data } = client.activity.value;\n          await client.send(JSON.stringify(data));\n        }\n        return undefined;\n      });\n      ```\n\n    </TabItem>\n    <TabItem value=\"v1\" label=\"Teams SDK v1\">\n      ```ts\n      app.taskModules.fetch('connect-account', async (context, state, data) => {\n        const taskInfo: TaskModuleTaskInfo = {\n          title: 'Connect your Microsoft 365 account',\n          height: 'medium',\n          width: 'medium',\n          url: `https://${process.env.NEXT_PUBLIC_BOT_DOMAIN}/connections`,\n          fallbackUrl: `https://${process.env.NEXT_PUBLIC_BOT_DOMAIN}/connections`,\n          completionBotId: process.env.NEXT_PUBLIC_BOT_ID,\n        };\n        return taskInfo;\n      });\n      app.taskModules.submit('connect-account', async (context, state, data) => {\n        console.log(\n          `bot-app.ts taskModules.submit(\"connect-account\"): data`,\n          JSON.stringify(data, null, 4)\n        );\n        await context.sendActivity('You are all set! Now, how can I help you today?');\n        return undefined;\n      });\n      ```\n    </TabItem>\n  </Tabs>\n\n<!-- adaptive-cards -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // highlight-error-start\n-    app.message('/card', async (context: TurnContext) => {\n-      const card = CardFactory.adaptiveCard({\n-        $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n-        version: '1.5',\n-        type: 'AdaptiveCard',\n-        body: [\n-          {\n-            type: 'TextBlock',\n-            text: 'Hello, world!',\n-            wrap: true,\n-            isSubtle: false,\n-          },\n-        ],\n-        msteams: {\n-          width: 'Full',\n-        },\n-      });\n-      await context.sendActivity({\n-        attachments: [card],\n-      });\n-    });\n    // highlight-error-end\n    // highlight-success-start\n+    app.message('/card', async (client) => {\n+      await client.send({\n+        $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n+        version: '1.5',\n+        type: 'AdaptiveCard',\n+        body: [\n+          {\n+            type: 'TextBlock',\n+            text: 'Hello, world!',\n+            wrap: true,\n+            isSubtle: false,\n+          },\n+        ],\n+        msteams: {\n+          width: 'Full',\n+        },\n+      });\n+    });\n    // highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2-option1\" label=\"Teams SDK v2 (Option 1)\">\n    For existing cards like this, the simplest way to convert that to Teams SDK is this:\n\n    ```ts\n    app.message('/card', async (client) => {\n      await client.send({\n        $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n        version: '1.5',\n        type: 'AdaptiveCard',\n        body: [\n          {\n            type: 'TextBlock',\n            text: 'Hello, world!',\n            wrap: true,\n            isSubtle: false,\n          },\n        ],\n        msteams: {\n          width: 'Full',\n        },\n      });\n    });\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2-option2\" label=\"Teams SDK v2 (Option 2)\">\n    For a more thorough port, you could also do the following:\n\n    ```ts\n    import { Card, TextBlock } from '@microsoft/teams.cards';\n\n    app.message('/card', async (client) => {\n      await client.send(\n        new Card(new TextBlock('Hello, world!', { wrap: true, isSubtle: false })).withOptions({\n          width: 'Full',\n        })\n      );\n    });\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```ts\n    app.message('/card', async (context: TurnContext) => {\n      const card = CardFactory.adaptiveCard({\n        $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',\n        version: '1.5',\n        type: 'AdaptiveCard',\n        body: [\n          {\n            type: 'TextBlock',\n            text: 'Hello, world!',\n            wrap: true,\n            isSubtle: false,\n          },\n        ],\n        msteams: {\n          width: 'Full',\n        },\n      });\n      await context.sendActivity({\n        attachments: [card],\n      });\n    });\n    ```\n  </TabItem>\n</Tabs>\n\n<!-- authentication -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // highlight-error-start\n-    const storage = new MemoryStorage();\n-    const app = new Application({\n-      storage: new MemoryStorage(),\n-      authentication: {\n-        autoSignIn: (context) => {\n-          const activity = context.activity;\n-          // No auth when user wants to sign in\n-          if (activity.text === '/signout') {\n-            return Promise.resolve(false);\n-          }\n-          // No auth for \"/help\"\n-          if (activity.text === '/help') {\n-            return Promise.resolve(false);\n-          }\n-          // Manually sign in (for illustrative purposes)\n-          if (activity.text === '/signin') {\n-            return Promise.resolve(false);\n-          }\n-          // For all other messages, require sign in\n-          return Promise.resolve(true);\n-        },\n-        settings: {\n-          graph: {\n-            connectionName: process.env.OAUTH_CONNECTION_NAME!,\n-            title: 'Sign in',\n-            text: 'Please sign in to use the bot.',\n-            endOnInvalidMessage: true,\n-            tokenExchangeUri: process.env.TOKEN_EXCHANGE_URI!,\n-            enableSso: true,\n-          },\n-        },\n-      },\n-    });\n-\n-    app.message('/signout', async (context, state) => {\n-      await app.authentication.signOutUser(context, state);\n-      await context.sendActivity(`You have signed out`);\n-    });\n-\n-    app.message('/help', async (context, state) => {\n-      await context.sendActivity(`your help text`);\n-    });\n-\n-    app.authentication.get('graph').onUserSignInSuccess(async (context, state) => {\n-      await context.sendActivity('Successfully logged in');\n-      await context.sendActivity(`Token string length: ${state.temp.authTokens['graph']!.length}`);\n-    });\n    // highlight-error-end\n    // highlight-success-start\n+    const app = new App({\n+      oauth: {\n+        defaultConnectionName: 'graph',\n+      },\n+      logger: new ConsoleLogger('@tests/auth', { level: 'debug' }),\n+    });\n+\n+    app.message('/signout', async ({ isSignedIn, signout, send }) => {\n+      if (!isSignedIn) return;\n+      await signout();\n+      await send('you have been signed out!');\n+    });\n+\n+    app.message('/help', async ({ send }) => {\n+      await send('your help text');\n+    });\n+\n+    app.on('message', async ({ signin, userGraph, log }) => {\n+      if (!await signin({\n+        oauthCardText: 'Sign in to your account',\n+        signInButtonText: 'Sign in',\n+      })) {\n+        return;\n+      }\n+      const me = await userGraph.me.get();\n+      log.info(`user \"${me.displayName}\" already signed in!`);\n+    });\n+\n+    app.event('signin', async ({ userGraph, send, token }) => {\n+      const me = await userGraph.me.get();\n+      await send(`user \"${me.displayName}\" signed in.`);\n+      await send(`Token string length: ${token.token.length}`);\n+    });\n    // highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```ts\n    const app = new App({\n      oauth: {\n        // oauth configurations\n        /**\n         * The name of the auth connection to use.\n         * It should be the same as the OAuth connection name defined in the Azure Bot configuration.\n         */\n        defaultConnectionName: 'graph',\n      },\n      logger: new ConsoleLogger('@tests/auth', { level: 'debug' }),\n    });\n\n    app.message('/signout', async ({ isSignedIn, signout, send }) => {\n      if (!isSignedIn) return;\n      await signout(); // call signout for your auth connection...\n      await send('you have been signed out!');\n    });\n\n    app.message('/help', async ({ send }) => {\n      await send('your help text');\n    });\n\n    app.on('message', async ({ signin, userGraph, log }) => {\n      if (!await signin({\n        // Customize the OAuth card text (only renders in OAuth flow, not SSO)\n        oauthCardText: 'Sign in to your account',\n        signInButtonText: 'Sign in',\n      })) { // call signin for your auth connection...\n        return;\n      }\n\n      const me = await userGraph.me.get();\n      log.info(`user \"${me.displayName}\" already signed in!`);\n    });\n\n    app.event('signin', async ({ userGraph, send, token }) => {\n      const me = await userGraph.me.get();\n      await send(`user \"${me.displayName}\" signed in.`);\n      await send(`Token string length: ${token.token.length}`);\n    });\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```ts\n    const storage = new MemoryStorage();\n    const app = new Application({\n      storage: new MemoryStorage(),\n      authentication: {\n        autoSignIn: (context) => {\n          const activity = context.activity;\n          // No auth when user wants to sign in\n          if (activity.text === '/signout') {\n            return Promise.resolve(false);\n          }\n          // No auth for \"/help\"\n          if (activity.text === '/help') {\n            return Promise.resolve(false);\n          }\n          // Manually sign in (for illustrative purposes)\n          if (activity.text === '/signin') {\n            return Promise.resolve(false);\n          }\n          // For all other messages, require sign in\n          return Promise.resolve(true);\n        },\n        settings: {\n          graph: {\n            connectionName: process.env.OAUTH_CONNECTION_NAME!,\n            title: 'Sign in',\n            text: 'Please sign in to use the bot.',\n            endOnInvalidMessage: true,\n            tokenExchangeUri: process.env.TOKEN_EXCHANGE_URI!,\n            enableSso: true,\n          },\n        },\n      },\n    });\n\n    app.message('/signout', async (context, state) => {\n      await app.authentication.signOutUser(context, state);\n\n      // Echo back users request\n      await context.sendActivity(`You have signed out`);\n    });\n\n    app.message('/signin', async (context, state) => {\n      let token = state.temp.authTokens['graph'];\n      if (!token) {\n        const res = await app.authentication.signInUser(context, state);\n        if (res.error) {\n          console.log(res.error);\n          return;\n        }\n        token = state.temp.authTokens['graph'];\n      }\n\n      if (token) {\n        // Echo back users request\n        await context.sendActivity(`You are already authenticated!`);\n        return;\n      }\n\n      // Sign in is pending...\n    });\n\n    app.message('/help', async (context, state) => {\n      await context.sendActivity(`your help text`);\n    });\n\n    app.authentication.get('graph').onUserSignInSuccess(async (context, state) => {\n      // Successfully logged in\n      await context.sendActivity('Successfully logged in');\n      await context.sendActivity(`Token string length: ${state.temp.authTokens['graph']!.length}`);\n    });\n\n    app.authentication.get('graph').onUserSignInFailure(async (context, _state, error) => {\n      // Failed to login\n      await context.sendActivity(`Failed to login with error: ${error.message}`);\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- ai-content -->\n\n### Action planner\n\nWhen we created Teams SDK, LLM's didn't natively support tool calling or orchestration. A lot has changed since then, which is why we decided to deprecate `ActionPlanner` from Teams SDK, and replace it with something a bit more lightweight. Notably, Teams SDK had two similar concepts: functions and actions. In Teams SDK, these are consolidated into functions.\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // highlight-error-start\n-    // Create AI components\n-    const model = new OpenAIModel({\n-      apiKey: process.env.OPENAI_KEY!,\n-      defaultModel: 'gpt-4o',\n-      logRequests: true,\n-    });\n-\n-    const prompts = new PromptManager({\n-      promptsFolder: path.join(__dirname, '../src/prompts'),\n-    });\n-\n-    // Define a prompt function for getting the current status of the lights\n-    prompts.addFunction('getLightStatus', async (context, memory) => {\n-      return memory.getValue('conversation.lightsOn') ? 'on' : 'off';\n-    });\n-\n-    const planner = new ActionPlanner({\n-      model,\n-      prompts,\n-      defaultPrompt: 'tools',\n-    });\n-\n-    // Define storage and application\n-    const storage = new MemoryStorage();\n-    const app = new Application<ApplicationTurnState>({\n-      storage,\n-      ai: {\n-        planner,\n-      },\n-    });\n-\n-    // Register action handlers\n-    app.ai.action('ToggleLights', async (context, state) => {\n-      state.conversation.lightsOn = !state.conversation.lightsOn;\n-      const lightStatusText = state.conversation.lightsOn ? 'on' : 'off';\n-      await context.sendActivity(`[lights ${lightStatusText}]`);\n-      return `the lights are now ${lightStatusText}$`;\n-    });\n-\n-    app.ai.action('Pause', async (context, state, parameters: PauseParameters) => {\n-      await context.sendActivity(`[pausing for ${parameters.time / 1000} seconds]`);\n-      await new Promise((resolve) => setTimeout(resolve, parameters.time));\n-      return `done pausing`;\n-    });\n    // highlight-error-end\n    // highlight-success-start\n+    const storage = new LocalStorage<IStorageState>();\n+    const app = new App();\n+\n+    app.on('message', async (client) => {\n+      let state = storage.get(client.activity.from.id);\n+\n+      if (!state) {\n+        state = {\n+          status: false,\n+          messages: [],\n+        };\n+        storage.set(client.activity.from.id, state);\n+      }\n+\n+      const prompt = new ChatPrompt({\n+        messages: state.messages,\n+        instructions: `The assistant can turn a light on or off. The lights are currently off.`,\n+        model: new OpenAIChatModel({\n+          model: 'gpt-4o-mini',\n+          apiKey: process.env.OPENAI_API_KEY,\n+        }),\n+      })\n+        .function('get_light_status', 'get the current light status', () => {\n+          return state.status;\n+        })\n+        .function('toggle_lights', 'toggles the lights on/off', () => {\n+          state.status = !state.status;\n+          storage.set(client.activity.from.id, state);\n+        })\n+        .function(\n+          'pause',\n+          'delays for a period of time',\n+          {\n+            type: 'object',\n+            properties: {\n+              time: {\n+                type: 'number',\n+                description: 'the amount of time to delay in milliseconds',\n+              },\n+            },\n+            required: ['time'],\n+          },\n+          async ({ time }: { time: number }) => {\n+            await new Promise((resolve) => setTimeout(resolve, time));\n+          }\n+        );\n+\n+      await prompt.send(client.activity.text, {\n+        onChunk: (chunk) => {\n+          client.stream.emit(new MessageActivity(chunk));\n+        },\n+      });\n+    });\n    // highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    In Teams SDK, there is no `actions.json` file. Instead, function prompts, parameters, etc. are declared in your code.\n\n    ```ts\n    import '@azure/openai/types';\n    import { ChatPrompt, Message } from '@microsoft/teams.ai';\n    import { MessageActivity } from '@microsoft/teams.api';\n    import { App } from '@microsoft/teams.apps';\n    import { LocalStorage } from '@microsoft/teams.common/storage';\n    import { OpenAIChatModel } from '@microsoft/teams.openai';\n\n    interface IStorageState {\n      status: boolean;\n      messages: Message[];\n    }\n\n    const storage = new LocalStorage<IStorageState>();\n\n    const app = new App();\n\n    app.on('message', async (client) => {\n      let state = storage.get(client.activity.from.id);\n\n      if (!state) {\n        state = {\n          status: false,\n          messages: [],\n        };\n\n        storage.set(client.activity.from.id, state);\n      }\n\n      const prompt = new ChatPrompt({\n        messages: state.messages,\n        instructions: `The following is a conversation with an AI assistant.\n      The assistant can turn a light on or off.\n      The lights are currently off.`,\n        model: new OpenAIChatModel({\n          model: 'gpt-4o-mini',\n          apiKey: process.env.OPENAI_API_KEY,\n        }),\n      })\n        .function('get_light_status', 'get the current light status', () => {\n          return state.status;\n        })\n        .function('toggle_lights', 'toggles the lights on/off', () => {\n          state.status = !state.status;\n          storage.set(client.activity.from.id, state);\n        })\n        .function(\n          'pause',\n          'delays for a period of time',\n          {\n            type: 'object',\n            properties: {\n              time: {\n                type: 'number',\n                description: 'the amount of time to delay in milliseconds',\n              },\n            },\n            required: ['time'],\n          },\n          async ({ time }: { time: number }) => {\n            await new Promise((resolve) => setTimeout(resolve, time));\n          }\n        );\n\n      await prompt.send(client.activity.text, {\n        onChunk: (chunk) => {\n          client.stream.emit(new MessageActivity(chunk));\n        },\n      });\n    });\n\n    (async () => {\n      await app.start();\n    })();\n    ```\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n\n    ```ts\n    // Create AI components\n    const model = new OpenAIModel({\n      // OpenAI Support\n      apiKey: process.env.OPENAI_KEY!,\n      defaultModel: 'gpt-4o',\n\n      // Azure OpenAI Support\n      azureApiKey: process.env.AZURE_OPENAI_KEY!,\n      azureDefaultDeployment: 'gpt-4o',\n      azureEndpoint: process.env.AZURE_OPENAI_ENDPOINT!,\n      azureApiVersion: '2023-03-15-preview',\n\n      // Request logging\n      logRequests: true,\n    });\n\n    const prompts = new PromptManager({\n      promptsFolder: path.join(__dirname, '../src/prompts'),\n    });\n\n    // Define a prompt function for getting the current status of the lights\n    prompts.addFunction('getLightStatus', async (context, memory) => {\n      return memory.getValue('conversation.lightsOn') ? 'on' : 'off';\n    });\n\n    const planner = new ActionPlanner({\n      model,\n      prompts,\n      defaultPrompt: 'tools',\n    });\n\n    // Define storage and application\n    const storage = new MemoryStorage();\n    const app = new Application<ApplicationTurnState>({\n      storage,\n      ai: {\n        planner,\n      },\n    });\n\n    // Register action handlers\n    app.ai.action('ToggleLights', async (context, state) => {\n      state.conversation.lightsOn = !state.conversation.lightsOn;\n      const lightStatusText = state.conversation.lightsOn ? 'on' : 'off';\n      await context.sendActivity(`[lights ${lightStatusText}]`);\n      return `the lights are now ${lightStatusText}$`;\n    });\n\n    interface PauseParameters {\n      time: number;\n    }\n\n    app.ai.action('Pause', async (context, state, parameters: PauseParameters) => {\n      await context.sendActivity(`[pausing for ${parameters.time / 1000} seconds]`);\n      await new Promise((resolve) => setTimeout(resolve, parameters.time));\n      return `done pausing`;\n    });\n\n    // Listen for incoming server requests.\n    server.post('/api/messages', async (req, res) => {\n      // Route received a request to adapter for processing\n      await adapter.process(req, res as any, async (context) => {\n        // Dispatch to application for routing\n        await app.run(context);\n      });\n    });\n    ```\n\n    And the corresponding `actions.json` file:\n\n    ```json\n    [\n      {\n        \"name\": \"ToggleLights\",\n        \"description\": \"Turns on/off the lights\"\n      },\n      {\n        \"name\": \"Pause\",\n        \"description\": \"Delays for a period of time\",\n        \"parameters\": {\n          \"type\": \"object\",\n          \"properties\": {\n            \"time\": {\n              \"type\": \"number\",\n              \"description\": \"The amount of time to delay in milliseconds\"\n            }\n          },\n          \"required\": [\"time\"]\n        }\n      }\n    ]\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- feedback -->\n\n<Tabs>\n  <TabItem value=\"Diff\" default>\n    ```ts\n    // highlight-error-start\n-    export const app = new Application({\n-      ai: {\n-        // opts into feedback loop\n-        enable_feedback_loop: true,\n-      },\n-    });\n-\n-    // Reply with message including feedback buttons\n-    app.activity(ActivityTypes.Message, async (context) => {\n-      await context.sendActivity({\n-        type: ActivityTypes.Message,\n-        text: `Hey, give me feedback!`,\n-        channelData: {\n-          feedbackLoop: {\n-            type: 'custom',\n-          },\n-        },\n-      });\n-    });\n-\n-    // Handle feedback submit\n-    app.feedbackLoop(async (context, state, feedbackLoopData) => {\n-      // custom logic here...\n-    });\n    // highlight-error-end\n    // highlight-success-start\n+    // Reply with message including feedback buttons\n+    app.on('message', async (client) => {\n+      await client.send(\n+        new MessageActivity('Hey, give me feedback!')\n+          .addAiGenerated() // AI generated label\n+          .addFeedback() // Feedback buttons\n+      );\n+    });\n+\n+    // Listen for feedback submissions\n+    app.on('message.submit.feedback', async ({ activity, log }) => {\n+      // custom logic here...\n+    });\n    // highlight-success-end\n    ```\n\n  </TabItem>\n  <TabItem value=\"v2\" label=\"Teams SDK v2\">\n    ```ts\n    import { MessageActivity } from '@microsoft/teams.api';\n\n    // Reply with message including feedback buttons\n    app.on('message', async (client) => {\n      await client.send(\n        new MessageActivity('Hey, give me feedback!')\n          .addAiGenerated() // AI generated label\n          .addFeedback() // Feedback buttons\n      );\n    });\n\n    // Listen for feedback submissions\n    app.on('message.submit.feedback', async ({ activity, log }) => {\n      // custom logic here...\n    });\n    ```\n\n    _Note:_ In Teams SDK, you do not need to opt into feedback at the `App` level.\n\n  </TabItem>\n  <TabItem value=\"v1\" label=\"Teams SDK v1\">\n    ```ts\n    export const app = new Application({\n      ai: {\n        // opts into feedback loop\n        enable_feedback_loop: true,\n      },\n    });\n\n    // Reply with message including feedback buttons\n    app.activity(ActivityTypes.Message, async (context) => {\n      await context.sendActivity({\n        type: ActivityTypes.Message,\n        text: `Hey, give me feedback!`,\n        channelData: {\n          feedbackLoop: {\n            type: 'custom',\n          },\n        },\n      });\n    });\n\n    // Handle feedback submit\n    app.feedbackLoop(async (context, state, feedbackLoopData) => {\n      // custom logic here...\n    });\n    ```\n\n  </TabItem>\n</Tabs>\n\n<!-- botbuilder-plugin -->\n\n## Incrementally migrating code via botbuilder plugin\n\n:::info\nComparison code coming soon!\n:::\n\nIf you aren't ready to migrate all of your code, you can run your existing Teams SDK code in parallel with Teams SDK. Learn more [here](./botbuilder/integration).\n"
  },
  {
    "path": "teams.md/src/components/include/migrations/v2-previews/typescript.incl.md",
    "content": "<!-- content -->\n\nIf you're moving from preview versions of Teams SDK, you may encounter a few breaking changes along the way. This page outlines those and shows how to get back on track.\n\n## Graph Client\n\nThe Graph Client has been redesigned to be more flexible and support far more Graph APIs, while being mindful of code and package size. The Graph endpoints are now an optional dependency, and one that is fully tree-shakable if you're using a modern bundler.\n\nThe redesign changes the calling pattern slightly. If you were using Graph APIs, you may need to update your code.\n\n### Installing endpoints dependency\n\nThe first step in the migration is to install the `@microsoft/teams.graph-endpoints` dependency. This dependency is optional to reduce overhead for for applications that don't need to call Graph APIs. If you're reading this migration guide however, you'll likely want to install it.\n\nThis package is installed just like any other NPM package, using your package manager of choice. For instance:\n\n```sh\nnpm install @microsoft/teams.graph-endpoints\n```\n\n### Updating code\n\nOnce the endpoints dependency is installed, the code changes should be fairly straight forward as the overall Graph taxonomy and API naming conventions have not changed. The overall change is that instead of invoking an endpoint function directly, you now pass in an endpoint to the `graphClient.call()` method.\n\n#### Calling endpoints\n\nIn earlier preview versions, the way to get details for the current user was:\n\n```typescript\n// GET /me\nconst me = await app.graph.me.get();\n```\n\nIn current versions, the equivalent method is:\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\n// GET /me\nconst me = await app.graph.call(endpoints.me.get);\n```\n\n#### Providing arguments\n\nIn earlier preview versions, variables were passes as an argument when invoking the endpoint. To get details for a specific user, you would do the following:\n\n```typescript\n// GET /users/{id | userPrincipalName}\nconst user = await app.graph.users.get({ 'user-id': userId });\n```\n\nIn current versions, the variables are provided as a separate argument after the endpoint:\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\n// GET /users/{id | userPrincipalName}\nconst user = await app.graph.call(endpoints.users.get, { 'user-id': userId });\n```\n\n#### No more redundant arguments\n\nIn earlier preview versions, some endpoints required the same argument to be provided twice. For instance:\n\n```typescript\n// GET /teams/{team-id}/installedApps`\nconst apps = await app.graph.teams.installedApps(teamId).list({ 'team-id': teamId });\n```\n\nIn current versions, once is enough:\n\n```typescript\n// GET /teams/{team-id}/installedApps`\nconst apps = await app.graph.call(endpoints.teams.installedApps.list, { 'team-id': teamId });\n```\n\n#### Improving readability\n\nIf you find it helpful for readability, you can scope your endpoint import as you prefer. For instance:\n\n```typescript\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\nimport { me } from '@microsoft/teams.graph-endpoints';\nimport { presence } from '@microsoft/teams.graph-endpoints/me';\nimport { setPresence } from '@microsoft/teams.graph-endpoints/me/presence';\nimport { create as updatePresence } from '@microsoft/teams.graph-endpoints/me/presence/setPresence';\n\n// different ways to POST to /me/presence/setPresence\nconst newPresence = { availability: 'Away', activity: 'Away', sessionId: clientId };\nawait app.graph.call(endpoints.me.presence.setPresence.create, newPresence);\nawait app.graph.call(me.presence.setPresence.create, newPresence);\nawait app.graph.call(presence.setPresence.create, newPresence);\nawait app.graph.call(setPresence.create, newPresence);\nawait app.graph.call(updatePresence, newPresence);\n```\n"
  },
  {
    "path": "teams.md/src/constants/languages.ts",
    "content": "/**\n * Supported languages for Teams SDK documentation\n * This is the single source of truth for all language-related code\n */\nexport const LANGUAGES = ['typescript', 'csharp', 'python'] as const;\nexport type Language = (typeof LANGUAGES)[number];\n\nexport const LANGUAGE_NAMES = {\n  typescript: 'TypeScript',\n  csharp: 'C#',\n  python: 'Python',\n} as const;\n\nexport const DEFAULT_LANGUAGE: Language = 'typescript';\n\n/**\n * Maps page paths to arrays of languages where the page is NOT available\n * e.g. Typescript has activity-ref.md but C# and Python do not.\n */\nexport interface LanguageAvailabilityMap {\n  [path: string]: Language[];\n}\n"
  },
  {
    "path": "teams.md/src/css/code-blocks.css",
    "content": ".code-block-error-line {\n  background-color: #ff000020;\n  color: #ff000080 !important;\n  display: block;\n  margin: 0 calc(-1 * var(--ifm-pre-padding));\n  padding: 0 var(--ifm-pre-padding);\n  border-left: 3px solid #ff000080;\n\n  * {\n    color: #ff000080 !important;\n  }\n}\n\n.code-block-success-line {\n  background-color: #003100;\n  color: #e6f6e6 !important;\n  border-left: 3px solid #009400;\n  display: block;\n  margin: 0 calc(-1 * var(--ifm-pre-padding));\n  padding: 0 var(--ifm-pre-padding);\n\n  * {\n    color: #e6f6e6 !important;\n  }\n}\n\n[data-theme='dark'] {\n  .code-block-error-line {\n    background-color: #ff000020;\n    color: rgba(254, 100, 100, 0.67) !important;\n    border-left: 3px solid #ff000080;\n\n    * {\n      color:rgba(254, 100, 100, 0.67) !important;\n    }\n  }\n\n  .code-block-success-line {\n    background-color: #003100;\n    color: #e6f6e6 !important;\n    border-left: 3px solid #009400;\n\n    * {\n      color: #e6f6e6 !important;\n    }\n  }\n}"
  },
  {
    "path": "teams.md/src/css/custom.css",
    "content": "/**\n * Any CSS included here will be global. The classic template\n * bundles Infima by default. Infima is a CSS framework designed to\n * work well for content-centric websites.\n */\n\n/* ==========================================================================\n   CSS CUSTOM PROPERTIES / VARIABLES\n   ========================================================================== */\n\n/* You can override the default Infima variables here. */\n:root {\n  --ifm-color-primary: #3c47b7;\n  --ifm-color-primary-dark: #3640a5;\n  --ifm-color-primary-darker: #333c9c;\n  --ifm-color-primary-darkest: #2a3280;\n  --ifm-color-primary-light: #4853c3;\n  --ifm-color-primary-lighter: #515cc6;\n  --ifm-color-primary-lightest: #6d76cf;\n  --ifm-code-font-size: 95%;\n  --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1);\n  --navbar-background-color: rgb(222, 222, 222);\n}\n\n/* For readability concerns, you should choose a lighter palette in dark mode. */\n[data-theme='dark'] {\n  --ifm-color-primary: #949cdc;\n  --ifm-color-primary-dark: #7882d3;\n  --ifm-color-primary-darker: #6a76ce;\n  --ifm-color-primary-darkest: #414fc1;\n  --ifm-color-primary-light: #b0b6e5;\n  --ifm-color-primary-lighter: #bec2ea;\n  --ifm-color-primary-lightest: #e7e9f7;\n  --docusaurus-highlighted-code-line-bg: rgba(91, 91, 91, 0.3);\n  --navbar-background-color: rgb(75, 75, 75);\n}\n\n/* ==========================================================================\n   NAVBAR COMPONENTS\n   ========================================================================== */\n\n/* Navbar items styling */\n.navbar__items:not(.navbar__items--right) .navbar__item {\n  border: none;\n  border-radius: 4px;\n  padding: 8px 12px;\n  text-decoration: none;\n  display: inline-block;\n  text-align: center;\n  cursor: pointer;\n  transition: background-color 0.3s ease;\n  margin-right: 2px;\n\n  &.navbar__link--active {\n    background-color: var(--navbar-background-color);\n  }\n}\n\n/* Color mode toggle spacing */\n.navbar__items--right .colorModeToggle {\n  margin-left: 0.5rem;\n}\n\n/* GitHub link icon */\n.header-github-link:before {\n  background: url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E\")\n    no-repeat;\n  content: '';\n  display: flex;\n  height: 24px;\n  width: 24px;\n}\n\n[data-theme='dark'] .header-github-link:before {\n  background: url(\"data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 24 24' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='%23fff' d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E\")\n    no-repeat;\n}\n\n/* ==========================================================================\n   LANGUAGE DROPDOWN COMPONENT\n   ========================================================================== */\n\n/* Language dropdown container */\n.language-dropdown {\n  position: relative;\n  display: inline-block;\n}\n\n/* Language dropdown trigger button */\n.language-dropdown .navbar__link {\n  display: flex;\n  align-items: center;\n  gap: 0.25rem;\n  padding: 0.5rem 0.75rem;\n  border: none;\n  background: none;\n  color: var(--ifm-navbar-link-color);\n  text-decoration: none;\n  cursor: pointer;\n  font-size: var(--ifm-font-size-base);\n}\n\n.language-dropdown .navbar__link:hover {\n  color: var(--ifm-navbar-link-hover-color);\n}\n\n.language-dropdown .navbar__link:focus {\n  color: var(--ifm-navbar-link-hover-color);\n  outline: 2px solid var(--ifm-focus-ring-color, var(--ifm-color-primary));\n  outline-offset: 2px;\n  border-radius: 4px;\n}\n\n/* Language dropdown arrow */\n.language-dropdown-arrow {\n  margin-left: 0.5rem;\n  width: 0;\n  height: 0;\n  border-style: solid;\n  border-width: 6px 6px 0 6px;\n  border-color: currentColor transparent transparent transparent;\n  display: inline-block;\n  vertical-align: middle;\n}\n\n/* Language dropdown menu */\n#language-switch-list {\n  position: absolute;\n  top: 100%;\n  left: 0;\n  right: 0;\n  min-width: 120px;\n  background: var(--ifm-background-surface-color);\n  border: 1px solid transparent;\n  border-radius: var(--ifm-global-radius);\n  box-shadow: var(--ifm-global-shadow-lw);\n  list-style: none;\n  margin: 0;\n  padding: 0.5rem 0;\n  z-index: var(--ifm-z-index-dropdown);\n}\n\n#language-switch-list li {\n  margin: 0;\n  padding: 0;\n}\n\n/* Language dropdown menu items */\n#language-switch-list [role='option'],\n.language-dropdown-option {\n  width: 100%;\n  text-align: left;\n  padding: 0.375rem 1rem;\n  border: none;\n  background: none;\n  color: inherit;\n  cursor: pointer;\n  font-size: var(--ifm-font-size-base);\n  display: block;\n  transition: background-color var(--ifm-transition-fast);\n  white-space: nowrap;\n  -webkit-user-select: none;\n  user-select: none;\n}\n\n#language-switch-list [role='option']:hover,\n#language-switch-list [role='option'][data-active='true'] {\n  background-color: var(--ifm-menu-color-background-hover);\n}\n\n#language-switch-list:focus {\n  border-radius: var(--ifm-global-radius);\n}\n\n/* Active language indicator */\n#language-switch-list [role='option'][aria-selected='true'] {\n  background-color: var(--ifm-color-emphasis-200);\n  position: relative;\n}\n\n#language-switch-list [role='option'][aria-selected='true']::before {\n  content: '';\n  position: absolute;\n  left: 0;\n  top: 0;\n  bottom: 0;\n  width: 3px;\n  background-color: var(--ifm-color-primary);\n}\n\n/* ==========================================================================\n   CODE BLOCKS AND SYNTAX HIGHLIGHTING\n   ========================================================================== */\n\n/* Dark theme code background */\n[data-theme='dark'] code {\n  background-color: rgba(14, 13, 13, 1);\n}\n\n[data-theme='dark'] pre span {\n  --ifm-pre-background: rgba(14, 13, 13, 1) !important;\n}\n\n/* ==========================================================================\n   RESPONSIVE DESIGN\n   ========================================================================== */\n\n/* Simple language banner notification */\n.language-banner {\n  position: fixed;\n  top: var(--ifm-navbar-height, 60px);\n  left: 0;\n  right: 0;\n  width: 50%;\n  margin: 0 auto;\n  background: var(--ifm-color-info-contrast-background, #d1ecf1);\n  color: var(--ifm-color-info-contrast-foreground, #0c5460);\n  border: 1px solid var(--ifm-color-info);\n  border-radius: var(--ifm-border-radius);\n  padding: 0.75rem 1rem;\n  font-size: 0.9rem;\n  display: block;\n  z-index: 1000;\n  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);\n  animation: slideInFromTop 0.3s ease-out;\n}\n\n.language-banner__content {\n  display: flex;\n  align-items: center;\n  justify-content: space-between;\n  gap: 1rem;\n}\n\n.language-banner__text {\n  flex: 1;\n  color: inherit;\n}\n\n.language-banner__actions {\n  display: flex;\n  align-items: center;\n  gap: 0.5rem;\n}\n\n.language-banner__button {\n  border: none;\n  border-radius: var(--ifm-button-border-radius);\n  padding: 0.25rem 0.75rem;\n  font-size: 0.85rem;\n  cursor: pointer;\n  transition: all 0.2s ease;\n  text-decoration: none;\n}\n\n.language-banner__button--primary {\n  background: var(--ifm-color-primary);\n  color: white;\n  font-weight: 500;\n}\n\n.language-banner__button--primary:hover {\n  background: var(--ifm-color-primary-dark);\n  text-decoration: none;\n  color: white;\n}\n\n.language-banner__button--secondary {\n  background: transparent;\n  color: var(--ifm-color-emphasis-600);\n  padding: 0.25rem 0.5rem;\n  font-size: 1.2rem;\n  width: 1.5rem;\n  height: 1.5rem;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  border-radius: 50%;\n}\n\n.language-banner__button--secondary svg {\n  width: 2rem;\n  height: 2rem;\n}\n\n.language-banner__button--secondary:hover {\n  background: var(--ifm-color-emphasis-200);\n  color: var(--ifm-color-emphasis-800);\n}\n\n.language-banner__button:focus {\n  outline: 2px solid var(--ifm-color-primary);\n  outline-offset: 2px;\n}\n\n@keyframes slideInFromTop {\n  from {\n    opacity: 0;\n    transform: translateY(-10px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n/* Mobile responsiveness for LanguageDropdown */\n@media (max-width: 768px) {\n  .language-dropdown .navbar__link {\n    padding: 0.4rem 0.6rem;\n    font-size: 0.75rem;\n  }\n\n  #language-switch-list {\n    min-width: 120px;\n  }\n\n  #language-switch-list [role='option'],\n  .language-dropdown-option {\n    padding: 0.5rem 0.75rem;\n    white-space: nowrap;\n    font-size: 0.8rem;\n  }\n}\n"
  },
  {
    "path": "teams.md/src/hooks/useLanguagePreference.tsx",
    "content": "import React, {\n  createContext,\n  ReactNode,\n  useState,\n  useEffect,\n  useContext,\n  useCallback,\n  useRef,\n} from 'react';\nimport { LANGUAGES, type Language, DEFAULT_LANGUAGE } from '../constants/languages';\n\ninterface LanguageContextProps {\n  language: Language;\n  setLanguage: (language: Language) => void;\n}\n\nconst LanguageContext = createContext<LanguageContextProps | undefined>(undefined);\n\nconst STORAGE_KEY = 'teams-sdk-language-preference';\n\n// Type guard to check if a string is a valid Language\nconst isLanguage = (value: string): value is Language =>\n  (LANGUAGES as readonly string[]).includes(value);\n\n// Verify localStorage is available and we can write to it.\nconst isLocalStorageAvailable = (): boolean => {\n  try {\n    if (typeof window === 'undefined' || !('localStorage' in window)) {\n      return false;\n    }\n\n    const testKey = '__test__';\n    localStorage.setItem(testKey, '1');\n    localStorage.removeItem(testKey);\n\n    return true;\n  } catch {\n    return false;\n  }\n};\n\nexport function LanguageProvider({ children }: { children: ReactNode }) {\n  const [language, setLanguage] = useState<Language>(() => {\n    if (isLocalStorageAvailable()) {\n      const stored = localStorage.getItem(STORAGE_KEY);\n      if (stored && isLanguage(stored)) {\n        return stored;\n      }\n    }\n    return DEFAULT_LANGUAGE;\n  });\n  const localStorageAvailable = useRef(isLocalStorageAvailable());\n\n  // Persist language storage across tabs\n  useEffect(() => {\n    if (!localStorageAvailable.current) {\n      return;\n    }\n\n    const handler = (e: StorageEvent) => {\n      if (e.key === STORAGE_KEY && e.newValue && isLanguage(e.newValue)) {\n        setLanguage(e.newValue);\n      }\n    };\n    window.addEventListener('storage', handler);\n\n    return () => window.removeEventListener('storage', handler);\n  }, []);\n\n  const updateLanguage = useCallback((newLanguage: Language) => {\n    setLanguage(newLanguage);\n\n    if (localStorageAvailable.current) {\n      localStorage.setItem(STORAGE_KEY, newLanguage);\n    }\n  }, []);\n\n  return (\n    <LanguageContext.Provider value={{ language, setLanguage: updateLanguage }}>\n      {children}\n    </LanguageContext.Provider>\n  );\n}\n\nexport function useLanguagePreference(): LanguageContextProps {\n  const context = useContext(LanguageContext);\n\n  if (!context) {\n    throw new Error('useLanguagePreference must be used within a LanguageProvider');\n  }\n\n  return context;\n}\n"
  },
  {
    "path": "teams.md/src/pages/csharp.tsx",
    "content": "import { Redirect } from '@docusaurus/router';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\n\nexport default function CSharp() {\n  const baseUrl = useBaseUrl('/');\n  return <Redirect to={`${baseUrl}csharp/getting-started`} />;\n}\n"
  },
  {
    "path": "teams.md/src/pages/index.module.css",
    "content": "/**\n * CSS files with the .module.css suffix will be treated as CSS modules\n * and scoped locally.\n */\n\n.heroBanner {\n  padding: 4rem 0;\n  text-align: center;\n  position: relative;\n  overflow: hidden;\n}\n\n@media screen and (max-width: 996px) {\n  .heroBanner {\n    padding: 2rem;\n  }\n}\n\n.buttons {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n"
  },
  {
    "path": "teams.md/src/pages/index.tsx",
    "content": "import { Redirect } from '@docusaurus/router';\n\nexport default function Home() {\n  return <Redirect to='welcome' />;\n}\n"
  },
  {
    "path": "teams.md/src/pages/python.tsx",
    "content": "import { Redirect } from '@docusaurus/router';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\n\nexport default function Python() {\n  const baseUrl = useBaseUrl('/');\n  return <Redirect to={`${baseUrl}python/getting-started`} />;\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/README.mdx",
    "content": "---\ntitle: Essentials\nsidebar_position: 2\nsummary: Introduction to the core concepts of Teams SDK applications including events, activities, handlers, and the reactive paradigm for building intelligent agents.\n---\n\n# Essentials\n\nAt its core, an application that hosts an agent on Microsoft Teams exists to do three things well: listen to events, handle the ones that matter, and respond efficiently. Whether a user sends a message, opens a dialog, or clicks a button — your app is there to interpret the event and act on it.\n\nWith Teams SDK, we've made it easier than ever to build this kind of reactive, conversational logic. The SDK introduces a few simple but powerful paradigms to help you connect to Teams, register handlers, and build intelligent agent behaviors quickly.\n\nBefore diving in, let's define a few key terms:\n\n<LanguageInclude section=\"key-terms\" />\n\n```mermaid\nflowchart LR\n    Teams[\"Teams\"]\n    Server[\"App Server\"]\n    AppEventHandlers[\"<LanguageInclude section=\"event-handler-label\" />\"]\n    AppRouter[\"Activity Event Router\"]\n    AppActivityHandlers[\"<LanguageInclude section=\"activity-handler-label\" />\"]\n\n    Teams --> |Activity| Server\n    Teams --> |Signed In| Server\n    Teams --> |...other<br/>incoming events| Server\n    Server --> |ActivityEvent<br/>or InvokeEvent| AppRouter\n    Server ---> |incoming<br/>events| AppEventHandlers\n    Server ---> |outgoing<br/>events<br/>| AppEventHandlers\n    AppRouter --> |message activity| AppActivityHandlers\n    AppRouter --> |card activity| AppActivityHandlers\n    AppRouter --> |installation activity| AppActivityHandlers\n    AppRouter --> |...other activities| AppActivityHandlers\n\n\n    linkStyle 0,3 stroke:#66fdf3,stroke-width:1px,color:Tomato\n    linkStyle 1,2,4,5 stroke:#66fdf3,stroke-width:1px\n    linkStyle 6,7,8,9 color:Tomato\n```\n\nThis section will walk you through the foundational pieces needed to build responsive, intelligent agents using the SDK.\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/_category_.json",
    "content": "{\n  \"position\": 2,\n  \"label\": \"Essentials\",\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/api.mdx",
    "content": "---\nsidebar_position: 6\nsidebar_label: API Client\ntitle: API Client\nsummary: Overview of the Teams API Client and how to use it to interact with conversations, meetings, and teams in your application.\n---\n\n# Teams API Client\n\nTeams has a number of areas that your application has access to via its API. These are all available via the <LanguageInclude section=\"api-object-name\" /> object. Here is a short summary of the different areas:\n\n<LanguageInclude section=\"api-table\" />\n\nAn instance of the API client is passed to handlers that can be used to fetch details:\n\n## Example\n\nIn this example, we use the API client to fetch the members in a conversation. The <LanguageInclude section=\"api-object-description\" /> object is passed to the activity handler in this case.\n\n<LanguageInclude section=\"handler-example\" />\n\n## Proactive API\n\nIt's also possible to access the API client from outside a handler via the app instance. Here we have the same example as above, but we're access the API client via the app instance.\n\n<LanguageInclude section=\"proactive-example\" />\n\n\n## Meetings Example\n\nIn this example, we use the API client to get a specific meeting participant's details, such as their role (e.g. Organizer) and whether they are currently in the meeting. Provide the user's AAD Object ID to specify which participant to look up. The `meetingId` and `tenantId` are available from the activity's channel data.\n\n:::note\nTo retrieve **all** members of a meeting, use the conversations API as shown in the [example above](#example), since meetings are also conversations.\n:::\n\n<LanguageInclude section=\"meetings-example\" />\n\nVisit [Meeting Events](../in-depth-guides/meeting-events) to learn more about meeting events.\n\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/app-authentication.mdx",
    "content": "---\nsidebar_position: 5\ntitle: App Authentication\nsummary: Configure app authentication in your Teams SDK application using client secrets, user managed identities, or federated identity credentials\nlanguages: ['typescript','python','csharp']\n---\n\n# App Authentication\n\nYour application needs to authenticate to send messages to Teams as your bot. Authentication allows your app service to certify that it is _allowed_ to send messages as your Azure Bot.\n\n:::info Azure Setup Required\nBefore configuring your application, you must first set up authentication in Azure. See the [App Authentication Setup](/teams/app-authentication) guide for instructions on creating the necessary Azure resources.\n:::\n\n## Authentication Methods\n\nThere are 3 main ways of authenticating:\n\n1. **Client Secret** - Simple password-based authentication using a client secret\n2. **User Managed Identity** - Passwordless authentication using Azure managed identities\n3. **Federated Identity Credentials** - Advanced identity federation using managed identities\n\n## Configuration Reference\n\nThe Teams SDK automatically detects which authentication method to use based on the environment variables you set:\n\n| CLIENT_ID | CLIENT_SECRET | MANAGED_IDENTITY_CLIENT_ID | Authentication Method |\n|-|-|-|-|\n| not_set | | | No-Auth (local development only) |\n| set | set | | Client Secret |\n| set | not_set | | User Managed Identity |\n| set | not_set | set (same as CLIENT_ID) | User Managed Identity |\n| set | not_set | set (different from CLIENT_ID) | Federated Identity Credentials (UMI) |\n| set | not_set | \"system\" | Federated Identity Credentials (System Identity) |\n\n## Client Secret\n\nThe simplest authentication method using a password-like secret.\n\n### Setup\n\nFirst, complete the [Client Secret Setup](/teams/app-authentication/client-secret) in Azure Portal or Azure CLI.\n\n### Configuration\n\nSet the following environment variables in your application:\n\n- `CLIENT_ID`: Your Application (client) ID\n- `CLIENT_SECRET`: The client secret value you created\n- `TENANT_ID`: The tenant id where your bot is registered\n\n```env\nCLIENT_ID=your-client-id-here\nCLIENT_SECRET=your-client-secret-here\nTENANT_ID=your-tenant-id\n```\n\nThe SDK will automatically use Client Secret authentication when both `CLIENT_ID` and `CLIENT_SECRET` are provided.\n\n## User Managed Identity\n\nPasswordless authentication using Azure managed identities - no secrets to rotate or manage.\n\n### Setup\n\nFirst, complete the [User Managed Identity Setup](/teams/app-authentication/user-managed-identity) in Azure Portal or Azure CLI.\n\n### Configuration\n\n<LanguageInclude section=\"configure-application\" />\n\n## Federated Identity Credentials\n\nAdvanced identity federation allowing you to assign managed identities directly to your App Registration.\n\n<LanguageInclude section=\"availability-note\" />\n\n### Setup\n\nFirst, complete the [Federated Identity Credentials Setup](/teams/app-authentication/federated-identity-credentials) in Azure Portal or Azure CLI.\n\n### Configuration\n\nDepending on the type of managed identity you select, set the environment variables accordingly.\n\n**For User Managed Identity:**\n\nSet the following environment variables:\n- `CLIENT_ID`: Your Application (client) ID\n- `MANAGED_IDENTITY_CLIENT_ID`: The Client ID for the User Managed Identity resource\n- **Do not set** `CLIENT_SECRET`\n- `TENANT_ID`: The tenant id where your bot is registered\n\n```env\nCLIENT_ID=your-app-client-id-here\nMANAGED_IDENTITY_CLIENT_ID=your-managed-identity-client-id-here\n# Do not set CLIENT_SECRET\nTENANT_ID=your-tenant-id\n```\n\n**For System Assigned Identity:**\n\nSet the following environment variables:\n- `CLIENT_ID`: Your Application (client) ID\n- `MANAGED_IDENTITY_CLIENT_ID`: `system`\n- **Do not set** `CLIENT_SECRET`\n- `TENANT_ID`: The tenant id where your bot is registered\n\n```env\nCLIENT_ID=your-app-client-id-here\nMANAGED_IDENTITY_CLIENT_ID=system\n# Do not set CLIENT_SECRET\nTENANT_ID=your-tenant-id\n```\n\n## Troubleshooting\n\nIf you encounter authentication errors, see the [Authentication Troubleshooting](/teams/app-authentication/troubleshooting) guide for common issues and solutions.\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/app-basics.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: App Basics\ntitle: App Basics\nsummary: Comprehensive guide to the App class, the main entry point for Teams SDK agents that handles server hosting, request routing, authentication, and plugin management.\nsuppressLanguageIncludeWarning: true\n---\n\n# App Basics\n\nThe `App` class is the main entry point for your agent.\n\nIt is responsible for:\n\n1. Hosting and running the server (via plugins)\n2. Serving incoming requests and routing them to your handlers\n3. Handling authentication for your agent to the Teams backend\n4. Providing helpful utilities which simplify the ability for your application to interact with the Teams platform\n5. Managing plugins which can extend the functionality of your agent\n\n```mermaid\nflowchart LR\n    %% Layout Definitions\n    direction LR\n\n    Teams\n\n    subgraph AppClass\n        CorePlugins[\"Plugins\"]\n        Events[\"Events\"]\n        subgraph AppResponsibilities\n            direction TB\n            ActivityRouting[\"Activity Routing\"]\n            Utilities[\"Utilities\"]\n            Auth[\"Auth\"]\n        end\n        Plugins2[\"Plugins\"]\n    end\n    ApplicationLogic[\"Application Logic\"]\n\n    %% Connections\n    Teams --> CorePlugins\n    CorePlugins --> Events\n    Events --> ActivityRouting\n    ActivityRouting --> Plugins2\n    Plugins2 --> ApplicationLogic\n    Auth --> ApplicationLogic\n    Utilities --> ApplicationLogic\n\n    %% Styling\n    style Teams fill:#2E86AB,stroke:#1B4F72,stroke-width:2px,color:#ffffff\n    style ApplicationLogic fill:#28B463,stroke:#1D8348,stroke-width:2px,color:#ffffff\n```\n\n## Core Components\n\n**Plugins**\n\n- Can be used to set up the server\n- Can listen to messages or send messages out\n\n**Events**\n\n- Listens to events from core plugins\n- Emit interesting events to the application\n\n**Activity Routing**\n\n- Routes activities to appropriate handlers\n\n**Utilities**\n\n- Provides utility functions for convenience (like sending replies or proactive messages)\n\n**Auth**\n\n- Handles authenticating your agent with Teams, Graph, etc.\n- Simplifies the process of authenticating your app or user for your app\n\n**Plugins (Secondary)**\n\n- Can hook into activity handlers or proactive scenarios\n- Can modify or update agent activity events\n\n## Plugins\n\nYou'll notice that plugins are present in the front, which exposes your application as a server, and also in the back after the app does some processing to the incoming message. The plugin architecture allows the application to be built in an extremely modular way. Each plugin can be swapped out to change or augment the functionality of the application. The plugins can listen to various events that happen (e.g. the server starting or ending, an error occuring, etc), activities being sent to or from the application and more. This allows the application to be extremely flexible and extensible.\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/graph.mdx",
    "content": "---\nsidebar_position: 8\nsidebar_label: Graph API Client\ntitle: Graph API Client\nsummary: Guide to using the Microsoft Graph API client to access Microsoft 365 data and services from your Teams SDK application.\n---\n\n# Graph API Client\n\n[Microsoft Graph](https://docs.microsoft.com/en-us/graph/overview) gives you access to the wider Microsoft 365 ecosystem. You can enrich your application with data from across Microsoft 365.\n\nThe SDK gives your application easy access to the Microsoft Graph API via the <LanguageInclude section=\"package-info\" />.\n\n<LanguageInclude section=\"migration-note\" />\n\n<LanguageInclude section=\"package-overview\" />\n\n## Calling APIs\n\nMicrosoft Graph can be accessed by your application using its own application token, or by using the user's token. If you need access to resources that your application may not have, but your user does, you will need to use the user's scoped graph client. To grant explicit consent for your application to access resources on behalf of a user, follow the [auth guide](../in-depth-guides/user-authentication).\n\nTo access the graph using the Graph using the app, you may use the <LanguageInclude section=\"app-graph-object\" /> object <LanguageInclude section=\"app-access-method\" />.\n\n<LanguageInclude section=\"app-graph-example\" />\n\n<LanguageInclude section=\"user-graph-intro\" />\n\n<LanguageInclude section=\"user-graph-example\" />\n\nHere, the <LanguageInclude section=\"user-graph-object\" /> object is a scoped graph client for the user that sent the message.\n\n:::tip\nYou also have access to the <LanguageInclude section=\"app-graph-in-handler\" /> object in the activity handler. This is equivalent to <LanguageInclude section=\"app-graph-reference\" />.\n:::\n\n<LanguageInclude section=\"advanced-sections\" />\n\n<LanguageInclude section=\"additional-resources\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/on-activity/README.mdx",
    "content": "---\nsidebar_position: 1\nsummary: Guide to handling Teams-specific activities like chat messages, card actions, and installs using the fluent router API.\ntitle: Listening to Activities\n---\n\n# Listening To Activities\n\nAn **Activity** is the Teams‑specific payload that flows between the user and your bot.\nWhere _events_ describe high‑level happenings inside your app, _activities_ are the raw Teams messages such as chat text, card actions, installs, or invoke calls.\n\n<LanguageInclude section=\"intro\" />\n\n```mermaid\nflowchart LR\n    Teams[\"Teams\"]:::less-interesting\n    Server[\"App Server\"]:::interesting\n    ActivityRouter[\"Activity Router (app.on())\"]:::interesting\n    Handlers[\"Your Activity Handlers\"]:::interesting\n\n    Teams --> |Events| Server\n    Server --> |Activity Event| ActivityRouter\n    ActivityRouter --> |handler invoked| Handlers\n\n    classDef interesting fill:#b1650f,stroke:#333,stroke-width:4px;\n    classDef less-interesting fill:#666,stroke:#333,stroke-width:4px;\n```\n\nHere is an example of a basic message handler:\n\n<LanguageInclude section=\"basic-example\" />\n\n<LanguageInclude section=\"example-explanation\" />\n\n## Middleware pattern\n\n<LanguageInclude section=\"middleware-intro\" />\n\n<LanguageInclude section=\"middleware-examples\" />\n\n:::info\nJust like other middlewares, if you stop the chain by not calling `next()`, the activity will not be passed to the next handler. The order of registration for the handlers also matters as that determines how the handlers will be called.\n:::\n\n<LanguageInclude section=\"activity-reference-footer\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/on-activity/_category_.json",
    "content": "{\n  \"label\": \"Listening To Activities\",\n  \"position\": 3,\n  \"description\": \"Guide to handling Teams-specific activities like chat messages, card actions, and installs using the fluent router API.\",\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/on-activity/activity-ref.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: Activity Type Reference\ntitle: Activity Type Reference\nsummary: Complete reference guide for all activity types and routes available in Teams SDK applications, including core activities and configuration routes.\nlanguages: ['typescript']\nsuppressLanguageIncludeWarning: true\n---\n\n# Activity Type Reference\n\nThe application supports a number of activity types:\n\n## Core Activity Routes\n\n| Route            | Responsibility                                                                                  |\n| ---------------- | ----------------------------------------------------------------------------------------------- |\n| `message`        | User messages the app                                                                           |\n| `typing`         | Sends a typing indicator to indicate the app got the user's message and is computing a response |\n| `deleteUserData` | Triggered when a user requests their data to be deleted according to privacy regulations        |\n| `mention`        | Triggered when the bot is @mentioned in a conversation                                          |\n\n## Configuration Routes\n\n| Route           | Invoke Path     | Responsibility                                                |\n| --------------- | --------------- | ------------------------------------------------------------- |\n| `config.open`   | `config/fetch`  | When app is installed, the user may configure it via a dialog |\n| `config.submit` | `config/submit` | Configuration dialog submission                               |\n| `tab.open`      | `tab/fetch`     | Initializes tab configuration experiences                     |\n| `tab.submit`    | `tab/submit`    | Processes tab configuration submissions                       |\n\n## Dialog Routes\n\n| Route           | Invoke Path   | Responsibility               |\n| --------------- | ------------- | ---------------------------- |\n| `dialog.open`   | `task/fetch`  | Opens a dialog               |\n| `dialog.submit` | `task/submit` | Processes dialog submissions |\n\n## Authentication Routes\n\n| Route                   | Invoke Path            | Responsibility                                |\n| ----------------------- | ---------------------- | --------------------------------------------- |\n| `signin.token-exchange` | `signin/tokenExchange` | When a token exchange happens during SSO Auth |\n| `signin.verify-state`   | `signin/verifyState`   | When a verification passes after OAuth        |\n\n## Message Interaction Routes\n\n| Route             | Invoke Path                       | Responsibility                                                          |\n| ----------------- | --------------------------------- | ----------------------------------------------------------------------- |\n| `message.execute` | `actionableMessage/executeAction` | An action was executed on a message                                     |\n| `message.submit`  | `message/submitAction`            | Handles message action submissions                                      |\n| `card.action`     | `adaptiveCard/action`             | Triggered when a user interacts with an Adaptive Card button or control |\n\n## File Handling Routes\n\n| Route                  | Responsibility                                               |\n| ---------------------- | ------------------------------------------------------------ |\n| `file.consent`         | Manages file sharing permission workflows in Teams           |\n| `file.consent.accept`  | Triggered when user accepts a file consent card for sharing  |\n| `file.consent.decline` | Triggered when user declines a file consent card for sharing |\n\n## Message Extension Routes\n\n| Route                             | Invoke Path                            | Responsibility                                        |\n| --------------------------------- | -------------------------------------- | ----------------------------------------------------- |\n| `message.ext.query-link`          | `composeExtension/queryLink`           | A link unfurling request for an installed application |\n| `message.ext.anon-query-link`     | `composeExtension/anonymousQueryLink`  | An anonymous link unfurling request                   |\n| `message.ext.query`               | `composeExtension/query`               | Message extension search query                        |\n| `message.ext.select-item`         | `composeExtension/selectItem`          | Message extension item selection                      |\n| `message.ext.submit`              | `composeExtension/submitAction`        | Message extension action submission                   |\n| `message.ext.open`                | `composeExtension/fetchTask`           | Message extension task fetching for an action         |\n| `message.ext.query-settings-url`  | `composeExtension/querySettingUrl`     | Retrieves configuration URLs for message extensions   |\n| `message.ext.setting`             | `composeExtension/setting`             | Processes message extension settings changes          |\n| `message.ext.card-button-clicked` | `composeExtension/onCardButtonClicked` | Card button click handling in message extensions      |\n| `message.ext.edit`                | N/A                                    | Processes edits to message extension previews         |\n| `message.ext.send`                | N/A                                    | Handles sending of message extension content          |\n\n## Lifecycle Routes\n\n| Route            | Responsibility                                              |\n| ---------------- | ----------------------------------------------------------- |\n| `install.add`    | Triggered when the app is newly installed to a team or chat |\n| `install.remove` | Triggered when the app is uninstalled from a team or chat   |\n| `install.update` | Triggered when the app is updated in a team or chat         |\n| `handoff.action` | Manages handoffs from a different agent to your application |\n\n## Conversation Update Routes\n\n| Route             | Responsibility                                                                       |\n| ----------------- | ------------------------------------------------------------------------------------ |\n| `membersAdded`    | Triggered when new users join a team or are added to a chat where the bot is active  |\n| `membersRemoved`  | Triggered when users leave a team or are removed from a chat where the bot is active |\n| `channelCreated`  | Triggered when a new channel is created in a team where the bot is installed         |\n| `channelRenamed`  | Triggered when a channel is renamed in a team where the bot is installed             |\n| `channelDeleted`  | Triggered when a channel is deleted from a team where the bot is installed           |\n| `channelRestored` | Triggered when a previously deleted channel is restored                              |\n| `teamArchived`    | Triggered when a team is archived                                                    |\n| `teamDeleted`     | Triggered when a team is deleted where the bot is installed                          |\n| `teamHardDeleted` | Triggered when a team is permanently deleted (beyond recovery)                       |\n| `teamRenamed`     | Triggered when a team is renamed where the bot is installed                          |\n| `teamRestored`    | Triggered when a previously deleted team is restored                                 |\n| `teamUnarchived`  | Triggered when a team is unarchived                                                  |\n| `messageUpdate`   | Triggered when a message is edited in a conversation with the bot                    |\n| `messageDelete`   | Triggered when a message is deleted in a conversation with the bot                   |\n\n## Meeting Routes\n\n| Route                     | Invoke Path                                         | Responsibility                                                             |\n| ------------------------- | --------------------------------------------------- | -------------------------------------------------------------------------- |\n| `meetingStart`            | `application/vnd.microsoft.meetingStart`            | Triggered at the beginning of a Teams meeting where the bot is present     |\n| `meetingEnd`              | `application/vnd.microsoft.meetingEnd`              | Triggered at the end of a Teams meeting where the bot is present           |\n| `meetingParticipantJoin`  | `application/vnd.microsoft.meetingParticipantJoin`  | Triggered when participants join a Teams meeting where the bot is present  |\n| `meetingParticipantLeave` | `application/vnd.microsoft.meetingParticipantLeave` | Triggered when participants leave a Teams meeting where the bot is present |\n| `readReceipt`             | `application/vnd.microsoft.readReceipt`             | Tracks when messages are read by users                                     |\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/on-event.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: Listening to Events\ntitle: Listening to Events\nsummary: Understanding how to listen to and handle events in Teams SDK applications, including user actions and application server events.\n---\n\n# Listening To Events\n\nAn **event** is a foundational concept in building agents — it represents something noteworthy happening either on Microsoft Teams or within your application. These events can originate from the user (e.g. installing or uninstalling your app, sending a message, submitting a form), or from your application server (e.g. startup, error in a handler).\n\n<LanguageInclude section=\"mermaid-diagram\" />\n\nThe Teams SDK makes it easy to subscribe to these events and respond appropriately. You can register event handlers to take custom actions when specific events occur — such as logging errors, triggering workflows, or sending follow-up messages.\n\nHere are the events that you can start building handlers for:\n\n<LanguageInclude section=\"events-table\" />\n\n### Example 1\n\nWe can subscribe to errors that occur in the app.\n\n<LanguageInclude section=\"example-1\" />\n\n### Example 2\n\n<LanguageInclude section=\"example-2\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/sending-messages/README.mdx",
    "content": "---\nsidebar_position: 4\nsummary: Guide to sending messages from your Teams SDK agent, including replies, proactive messages, and different message types.\ntitle: Sending Messages\n---\n\n# Sending Messages\n\nSending messages is a core part of an agent's functionality. With all activity handlers, a `send` method is provided which allows your handlers to send a message back to the user to the relevant conversation.\n\n<LanguageInclude section=\"basic-message-example\" />\n\nIn the above example, the handler gets a `message` activity, and uses the `send` method to send a reply to the user.\n\n<LanguageInclude section=\"signin-example\" />\n\nYou are not restricted to only replying to `message` activities. In the above example, the handler is listening to <LanguageInclude section=\"signin-event-name\" /> events, which are sent when a user successfully signs in.\n\n:::tip\nThis shows an example of sending a text message. Additionally, you are able to send back things like [adaptive cards](../../in-depth-guides/adaptive-cards) by using the same `send` method. Look at the [adaptive card](../../in-depth-guides/adaptive-cards) section for more details.\n:::\n\n## Streaming\n\nYou may also stream messages to the user which can be useful for long messages, or AI generated messages. The SDK makes this simple for you by providing a `stream` function which you can use to send messages in chunks.\n\n<LanguageInclude section=\"streaming-example\" />\n\n:::note\nStreaming is currently only supported in 1:1 conversations, not group chats or channels\n:::\n\n![Animated image showing agent response text incrementally appearing in the chat window.](/screenshots/streaming-chat.gif)\n\n## @Mention\n\nSending a message at `@mentions` a user is as simple including the details of the user using the <LanguageInclude section=\"mention-method-name\" /> method\n\n<LanguageInclude section=\"mention-example\" />\n\n## Targeted Messages\n\n:::info[Preview]\nTargeted messages are currently in preview.\n:::\n\nTargeted messages, also known as ephemeral messages, are delivered to a specific user in a shared conversation. From a single user's perspective, they appear as regular inline messages in a conversation. Other participants won't see these messages, making them useful for authentication flows, help or error responses, personal reminders, or sharing contextual information without cluttering the group conversation.\n\nTo send a targeted message when responding to an incoming activity, use the <LanguageInclude section=\"targeted-method-name\" /> method with the recipient account and set the targeting flag to true.\n\n<LanguageInclude section=\"targeted-send-example\" />\n\n### Targeted messages in preview\n<LanguageInclude section=\"targeted-preview-note\" />\n\n## Reactions\n\n:::info[Preview]\nReactions are currently in preview.\n:::\n\nReactions allow your agent to add or remove emoji reactions on messages in a conversation. The reactions client is available via the API client.\n\n### Reactions in preview \n\n<LanguageInclude section=\"reactions-preview-note\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/sending-messages/_category_.json",
    "content": "{\n  \"label\": \"Sending Messages\",\n  \"position\": 5,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/essentials/sending-messages/proactive-messaging.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: 'Proactive Messaging'\ntitle: 'Proactive Messaging'\nsummary: Learn how to send proactive messages to users without waiting for them to initiate the conversation, including storing conversation IDs and sending notifications.\n---\n\n# Proactive Messaging\n\nIn [Sending Messages](./), you were shown how to respond to an event when it happens. However, there are times when you want to send a message to the user without them sending a message first. This is called proactive messaging. You can do this by using the `send` method in the `app` instance. This approach is useful for sending notifications or reminders to the user.\n\nThe main thing to note is that you need to have the <LanguageInclude section=\"conversation-id-field\" /> of the chat or channel that you want to send the message to. It's a good idea to store this value somewhere from an activity handler so that you can use it for proactive messaging later.\n\n<LanguageInclude section=\"install-handler-example\" />\n\nThen, when you want to send a proactive message, you can retrieve the <LanguageInclude section=\"conversation-id-field\" /> from storage and use it to send the message.\n\n<LanguageInclude section=\"send-proactive-example\" />\n\n:::tip\nIn this example, you see how to get the <LanguageInclude section=\"conversation-id-field\" /> using one of the activity handlers. This is a good place to store the conversation id, but you can also do this in other places like when the user installs the app or when they sign in. The important thing is that you have the conversation id stored somewhere so you can use it later.\n:::\n\n## Targeted Proactive Messages\n\n:::info[Preview]\nTargeted messages are currently in preview.\n:::\n\nTargeted messages, also known as ephemeral messages, are delivered to a specific user in a shared conversation. From a single user's perspective, they appear as regular inline messages in a conversation. Other participants won't see these messages.\n\nWhen sending targeted messages proactively, you must explicitly specify the recipient account.\n\n<LanguageInclude section=\"targeted-proactive-example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/README.mdx",
    "content": "---\ntitle: 🚀 Getting Started\nsidebar_position: 1\nsummary: Getting started guide for Teams SDK covering application setup, structure, and local development.\nllms: ignore-file\n---\n\n# 🚀 Getting Started\n\n<LanguageInclude section=\"warning\" />\n\nThis guide will help you set up your first Teams SDK application in <LanguageInclude section=\"language-name\" />. You'll learn the basics of creating an application, understanding its structure, and running it locally. By the end of this guide, you'll have a solid foundation to build upon as you explore more advanced features and capabilities of the SDK.\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/_LLMs.mdx",
    "content": "---\ntitle: LLMs.txt\nsummary: Links to LLM context files that provide coding assistants with documentation for the Teams SDK.\nllms: ignore\n---\n\n# LLMs.txt\n\nA common practice to speed up development is using Coding Assistants. To better facilitate this usage, you can provide your coding assistant sufficient context about this SDK by linking your assistant to the SDK's llms.txt files for <LanguageInclude section=\"language-name\" />:\n\n<LanguageInclude section=\"file-links\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/_category_.json",
    "content": "{\n  \"label\": \"Getting Started\",\n  \"position\": 1,\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/code-basics.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: Code Basics\ntitle: Code Basics\nsummary: Understanding the structure and key components of a Teams SDK application including the Application class, dependency injection, and project organization.\n---\n\n<LanguageInclude section=\"imports\" />\n\n# Code Basics\n\nAfter following the guidance in [the quickstart](quickstart) to create your first Teams application, let's review its structure and key components. This knowledge can help you build more complex applications as you progress.\n\n## Project Structure\n\nWhen you create a new Teams application, it generates a directory with this basic structure:\n\n<LanguageInclude section=\"project-structure\" />\n\n<LanguageInclude section=\"project-structure-description\" />\n\n## Core Components\n\nLet's break down the simple application from the [quickstart](quickstart) into its core components.\n\n### The App Class\n\nThe heart of an application is the `App` class. This class handles all incoming activities and manages the application's lifecycle. It also acts as a way to host your application service.\n\n<LanguageInclude section=\"app-class-code\" />\n\nThe app configuration includes a variety of options that allow you to customize its behavior, including controlling the underlying server, authentication, and other settings.\n\n### Plugins\n\nPlugins are a core part of the Teams SDK. They allow you to hook into various lifecycles of the application. The lifecycles include server events (start, stop, initialize, etc.), and also Teams Activity events <LanguageInclude section=\"plugin-events\" />. In fact, the [DevTools](/developer-tools/devtools) application you already have running is a plugin too. It allows you to inspect and debug your application in real-time.\n\n:::warning\nDevTools is a plugin that should only be used in development mode. It should not be used in production applications since it offers no authentication and allows your application to be accessed by anyone.\n\n**Be sure to remove the DevTools plugin from your production code.**\n:::\n\n### Message Handling\n\nTeams applications respond to various types of activities. The most basic is handling messages:\n\n<LanguageInclude section=\"message-handling-code\" />\n\nThis code:\n\n1. <LanguageInclude section=\"message-handling-step1\" />\n2. Sends a typing indicator, which renders as an animated ellipsis (…) in the chat.\n3. <LanguageInclude section=\"message-handling-step3\" />\n\n<LanguageInclude section=\"message-handling-info\" />\n\n### Application Lifecycle\n\nYour application starts when you run:\n\n<LanguageInclude section=\"app-lifecycle-code\" />\n\nThis code initializes your application server and, when configured for Teams, also authenticates it to be ready for sending and receiving messages.\n\n## Next Steps\n\nNow that you understand the basic structure of your Teams application, you're ready to [run it in Teams](running-in-teams). You will learn about Microsoft 365 Agents Toolkit and other important tools that help you with deployment and testing your application.\n\nAfter that, you can:\n\n- Add more activity handlers for different types of interactions. See [Listening to Activities](../essentials/on-activity) for more details.\n- Integrate with external services using the [API Client](../essentials/api).\n- Add interactive [cards](../in-depth-guides/adaptive-cards) and [dialogs](../in-depth-guides/dialogs).\n- Implement [AI](../in-depth-guides/ai).\n\nContinue on to the next page to learn about these advanced features.\n\n## Other Resources\n\n- [Essentials](../essentials)\n- [Teams concepts](/teams)\n- [Teams developer tools](/developer-tools)\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/quickstart.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: Quickstart\ntitle: Quickstart\nsummary: Quick start guide for Teams SDK using the Teams CLI to create and run your first agent.\n---\n\n# Quickstart\n\nGet started with Teams SDK quickly using the Teams CLI.\n\n## Set up a new project\n\n### Prerequisites\n\n<LanguageInclude section=\"prerequisites\" />\n\n## Instructions\n\n### Use the Teams CLI\n\nUse your terminal to run the Teams CLI using npx:\n\n```sh\nnpx @microsoft/teams.cli --version\n```\n\n:::info\n_The [Teams CLI](/developer-tools/cli) is a command-line tool that helps you create and manage Teams applications. It provides a set of commands to simplify the development process._<br /><br />\nUsing `npx` allows you to run the Teams CLI without installing it globally. You can verify it works by running the version command above.\n:::\n\n## Creating Your First Agent\n\nLet's begin by creating a simple echo agent that responds to messages. Run:\n\n<LanguageInclude section=\"create-command\" />\n\nThis command:\n\n<LanguageInclude section=\"create-explanation\" />\n\n> The `echo` template creates a basic agent that repeats back any message it receives - perfect for learning the fundamentals.\n\n## Running your agent\n\n<LanguageInclude section=\"running-steps\" />\n\n<LanguageInclude section=\"console-output\" />\n\nWhen the application starts, you'll see:\n\n1. An HTTP server starting up (on port `3978`). This is the main server which handles incoming requests and serves the agent application.\n2. A devtools server starting up (on port `3979`). This is a developer server that provides a web interface for debugging and testing your agent quickly, without having to deploy it to Teams.\n\n:::info\nThe DevTools server runs on a separate port to avoid conflicts with your main application server. This allows you to test your agent locally while keeping the main server available for Teams integration.\n:::\n\nNow, navigate to the devtools server by opening your browser and navigating to [http://localhost:3979/devtools](http://localhost:3979/devtools). You should see a simple interface where you can interact with your agent. Try sending it a message!\n\n![Screenshot of DevTools showing user prompt 'hello!' and agent response 'you said hello!'.](/screenshots/devtools-echo-chat.png)\n\n## Add to an Existing Project\n\nIf you already have a project and want to add Teams support, install the SDK directly:\n\n<LanguageInclude section=\"manual-install\" />\n\nThen initialize the Teams app with your existing server:\n\n<LanguageInclude section=\"manual-code\" />\n\n`app.initialize()` registers the Teams endpoint on your server without starting a new one — you keep full control of your server lifecycle.\n\n<LanguageInclude section=\"manual-more\" />\n\n## Next steps\n\nAfter creating and running your first agent, read about [the code basics](code-basics) to better understand its components and structure.\n\nOtherwise, if you want to run your agent in Teams, you can check out the [Running in Teams](running-in-teams) guide.\n\n## Resources\n\n- [Teams CLI documentation](/developer-tools/cli)\n- [Teams DevTools documentation](/developer-tools/devtools)\n- [Teams manifest schema](https://learn.microsoft.com/en-us/microsoftteams/platform/resources/schema/manifest-schema)\n- [Teams sideloading](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/deploy-and-publish/apps-upload)\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/running-in-teams/README.mdx",
    "content": "---\nsidebar_position: 3\ntitle: 'Running in Teams'\nsummary: Guide to deploying and testing your locally running agent in Microsoft Teams using the Microsoft 365 Agents Toolkit.\nllms: ignore\n---\n\n# Running In Teams\n\nNow that you completed [the quickstart](../quickstart) and your agent is running locally, let's deploy it to Microsoft Teams for testing. This guide will walk you through the process.\n\n## Microsoft 365 Agents Toolkit\n\nMicrosoft 365 Agents Toolkit is a powerful tool that simplifies deploying and debugging Teams applications. It automates tasks like managing the Teams app manifest, configuring authentication, provisioning, and deployment. If you'd like to learn about these concepts, check out [Teams core concepts](/teams/core-concepts).\n\n### Install Microsoft 365 Agents Toolkit\n\nFirst, you'll need to install the Agents Toolkit IDE extension:\n\n- Visit the [Microsoft 365 Agents Toolkit installation guide](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/install-teams-toolkit) to install on your preferred IDE.\n\n## Adding Teams configuration files via `teams` CLI\n\nTo configure your agent for Teams, run the following command in the terminal inside your quote-agent folder:\n\n:::tip\n(if you have `teams` CLI installed globally, use `teams` instead of `npx`)\n:::\n\n```bash\nnpx @microsoft/teams.cli config add atk.basic\n```\n\n:::tip\nThe `atk.basic` configuration is a basic setup for Agents Toolkit. It includes the necessary files and configuration to get started with Teams development.<br/>\nExplore more advanced configurations as needed with `npx @microsoft/teams.cli config --help`.<br />\n:::\n\nThis [CLI](/developer-tools/cli) command adds configuration files required by Agents Toolkit, including:\n\n- Environment setup in the `env` folder and root `.env` file\n- Teams app manifest in the `appPackage` folder (if not already present)\n- Debug instructions in `.vscode/launch.json` and `.vscode/tasks.json`\n- Agents Toolkit automation files to your project (e.g. `teamsapp.local.yml`)\n\n| Tool Name                    | Command | Description                                                                                                                           |\n| ---------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------- |\n| Teams SDK CLI                | `teams` | A command-line tool for setting up and utilizing the Teams SDK, including integration with Microsoft 365 Agents Toolkit when desired. |\n| Microsoft 365 Agents Toolkit | `atk`   | A tool for managing provisioning, deployment, and in-client debugging for Teams applications.                                         |\n\n## Debugging in Teams\n\nAfter installing Agents Toolkit and adding the configuration:\n\n1. **Open** your agent's project in your IDE.\n2. **Open the Microsoft 365 Agents Toolkit extension panel** (usually on the left sidebar).\n3. **Log in** to your Microsoft 365 and Azure accounts in the Agents Toolkit extension.\n4. **Select \"Local\"** under Environment Settings of the Agents Toolkit extension.\n5. **Click on Debug (Chrome) or Debug (Edge)** to start debugging via the 'play' button.\n\n![Screenshot of Microsoft 365 Agents Toolkit with 'Environment' section expanded and 'local' selected.](/screenshots/agents-toolkit.png)\n\nWhen debugging starts, the Agents Toolkit will:\n\n- **Build** your application\n- **Start a [devtunnel](/teams/core-concepts#devtunnel)** that will assign a temporary public URL to your local server\n- **Provision the Teams app** for your tenant so that it can be installed and be authenticated on Teams\n- **Set up the local variables** necessary for your agent to run in Teams in `env/.env.local` and `env/env.local.user`. This includes propagating the app manifest with your newly provisioned resources.\n- **Start** the local server.\n- **Package your app manifest** into a Teams application zip package and the manifest json with variables inserted in `appPackage/build`.\n- **Launch Teams** in an incognito window in your browser.\n- **Upload the package** to Teams and signal it to sideload (install) the app just for your use.\n\nIf you set up Agents Toolkit via the Teams SDK CLI, you should see something like the following in your terminal:\n\n<LanguageInclude section=\"terminal-output\" />\n\n## Testing your agent\n\nAfter the debugging session starts:\n\n1. Teams will open in your browser\n2. You'll be prompted to sign in (if not already)\n3. Teams will ask permission to install the app\n4. Once installed, you can start chatting with your agent!\n\n![Screenshot of `quote-agent-local` agent running in Teams.](/screenshots/example-on-teams.png)\n\nCongratulations! Now you have a fully functional agent running in Microsoft Teams. Interact with it just like any other Teams app and explore the rest of the documentation to build more complex agents.\n\n:::tip\nIf you want to monitor the activities and events in your app, you can still use the [DevTools plugin](/developer-tools/devtools)! Note that the DevTools server is running on port 3979. You can open it in your browser to interact with your agent and monitor activities in real time.\n:::\n\n## Troubleshooting\n\nFor deployment and resource management we recommend the Microsoft 365 Agents Toolkit. For authentication-related issues, refer to our [Authentication Troubleshooting](/teams/app-authentication/troubleshooting) guide.\n\nIf you prefer to set everything up by hand, follow our [Manual Configuration](/teams/configuration/manual-configuration) guide. The Teams SDK itself doesn't handle deployment or Azure resources, so you'll need to rely on the general [Microsoft Teams deployment documentation](https://learn.microsoft.com/en-us/microsoftteams/deploy-overview) for in-depth help.\n\n## Next steps\n\nNow that your agent is running in Teams, you can learn more [essential concepts](../../essentials) to understand how to build more complex agents. Explore the [in-depth guides](../../in-depth-guides) for advanced topics like authentication, message extensions, and more.\n\n## Resources\n\n- [Teams CLI documentation](/developer-tools/cli)\n- [Microsoft 365 Agents Toolkit documentation](https://learn.microsoft.com/en-us/microsoft-365/developer/overview-m365-agents-toolkit?toc=%2Fmicrosoftteams%2Fplatform%2Ftoc.json&bc=%2Fmicrosoftteams%2Fplatform%2Fbreadcrumb%2Ftoc.json)\n- [Microsoft 365 Agents Toolkit CLI documentation](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/microsoft-365-agents-toolkit-cli)\n- [Teams CLI GitHub repository](https://github.com/OfficeDev/Teams-Toolkit)\n- [Microsoft Teams deployment documentation](https://learn.microsoft.com/en-us/microsoftteams/deploy-overview)\n"
  },
  {
    "path": "teams.md/src/pages/templates/getting-started/running-in-teams/_category_.json",
    "content": "{\n  \"position\": 3,\n  \"label\": \"Running in Teams\",\n  \"collapsible\": true,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/README.mdx",
    "content": "---\ntitle: In-Depth Guides\nsidebar_position: 3\nsummary: Advanced guides covering complex topics like AI integration, adaptive cards, dialogs, message extensions, and user authentication.\n---\n\n# In-Depth Guides\n\n<LanguageInclude section=\"intro\" />\n\nThis section provides comprehensive technical guides for integration with useful Teams features. Learn how to implement AI-powered bots, create adaptive cards, manage authentication flows, and build sophisticated message extensions. Each guide includes practical examples and best practices for production applications.\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/_category_.json",
    "content": "{\n  \"label\": \"In-Depth Guides\",\n  \"position\": 3,\n  \"collapsible\": true,\n  \"collapsed\": false\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/adaptive-cards/README.mdx",
    "content": "---\ntitle: Adaptive Cards\nsidebar_position: 1\nsummary: Introduction to Adaptive Cards in Teams SDK applications for creating rich, interactive user experiences across various scenarios.\n---\n\n# Adaptive Cards\n\n<LanguageInclude section=\"intro\" />\n\nAdaptive Cards provide a flexible, cross-platform content format for creating rich, interactive experiences. They consist of a customizable body of card elements combined with optional action sets, all fully serializable for delivery to clients. Through a powerful combination of text, graphics, and interactive buttons, Adaptive Cards enable compelling user experiences across various platforms.\n\nThe Adaptive Card framework is widely implemented throughout Microsoft's ecosystem, with significant integration in Microsoft Teams. Within Teams, Adaptive Cards power numerous key scenarios including:\n\n- Rich interactive messages\n- Dialogs\n- Message Extensions\n- Link Unfurling\n- Configuration forms\n- And many more application contexts\n\nMastering Adaptive Cards is essential for creating sophisticated, engaging experiences that leverage the full capabilities of the Teams platform. This guide will help you learn how to use them in this SDK.\n\nFor a more comprehensive guide on Adaptive Cards, see the [official documentation](https://adaptivecards.microsoft.com/).\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/adaptive-cards/_category_.json",
    "content": "{\n  \"label\": \"Adaptive Cards\",\n  \"position\": 1,\n  \"collapsible\": true,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/adaptive-cards/building-adaptive-cards.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: Building Adaptive Cards\ntitle: Building Adaptive Cards\nsummary: Guide to building Adaptive Cards with builder helpers for type-safe, maintainable UI development.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Building Adaptive Cards\n\nAdaptive Cards are JSON payloads that describe rich, interactive UI fragments.\n\n<LanguageInclude section=\"intro-description\" />\n\n## The Builder Pattern\n\n<LanguageInclude section=\"builder-description\" />\n\nEach helper wraps raw JSON and provides fluent, chainable methods that keep your code concise and readable.\n\n<LanguageInclude section=\"builder-example\" />\n\nBenefits:\n\n| Benefit     | Description                                                                   |\n| ----------- | ----------------------------------------------------------------------------- |\n| Readability | No deep JSON trees—just chain simple methods.                                 |\n| Re‑use      | Extract snippets to functions or classes and share across cards.              |\n| Safety      | Builders validate every property against the Adaptive Card schema (see next). |\n\n<LanguageInclude section=\"source-code-note\" />\n\n## Type‑safe Authoring & IntelliSense\n\nThe package bundles the **Adaptive Card v1.5 schema** as strict <LanguageInclude section=\"language-name\" /> types.\nWhile coding you get:\n\n- **Autocomplete** for every element and attribute.\n- **In‑editor validation**—invalid enum values or missing required properties produce build errors.\n- Automatic upgrades when the schema evolves; simply update the package.\n\n<LanguageInclude section=\"type-safety-example\" />\n\n## The Visual Designer\n\nPrefer a drag‑and‑drop approach? Use [Microsoft's Adaptive Card Designer](https://adaptivecards.microsoft.com/designer.html):\n\n1. Add elements visually until the card looks right.\n2. Copy the JSON payload from the editor pane.\n3. Paste the JSON into your project **or** convert it to builder calls:\n\n<LanguageInclude section=\"designer-example\" />\n\nThis method leverages the full Adaptive Card schema and ensures that the payload adheres strictly to <LanguageInclude section=\"card-interface\" />.\n\n:::tip\nYou can use a combination of raw JSON and builder helpers depending on whatever you find easier.\n:::\n\n## End‑to‑end Example – Task Form Card\n\nBelow is a complete example showing a task management form.\n\n<LanguageInclude section=\"example-intro\" />\n\n<LanguageInclude section=\"task-form-example\" />\n\n## Additional Resources\n\n- [**Official Adaptive Card Documentation**](https://adaptivecards.microsoft.com/)\n- [**Adaptive Cards Designer**](https://adaptivecards.microsoft.com/designer.html)\n\n### Summary\n\n- Use **builder helpers** for readable, maintainable card code.\n- Enjoy **full type safety** and IDE assistance.\n- Prototype quickly in the **visual designer** and refine with builders.\n\nHappy card building! 🎉\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/adaptive-cards/executing-actions.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: Executing Actions\ntitle: Executing Actions\nsummary: How to implement interactive elements in Adaptive Cards through actions like buttons, links, and input submission triggers.\n---\n\n# Executing Actions\n\nAdaptive Cards support interactive elements through **actions**—buttons, links, and input submission triggers that respond to user interaction.\nYou can use these to collect form input, trigger workflows, show task modules, open URLs, and more.\n\n## Action Types\n\nThe Teams SDK supports several action types for different interaction patterns:\n\n| Action Type               | Purpose                | Description                                                                  |\n| ------------------------- | ---------------------- | ---------------------------------------------------------------------------- |\n| `Action.Execute`          | Server‑side processing | Send data to your bot for processing. Best for forms & multi‑step workflows. |\n| `Action.Submit`           | Simple data submission | Legacy action type. Prefer `Execute` for new projects.                       |\n| `Action.OpenUrl`          | External navigation    | Open a URL in the user's browser.                                            |\n| `Action.ShowCard`         | Progressive disclosure | Display a nested card when clicked.                                          |\n| `Action.ToggleVisibility` | UI state management    | Show/hide card elements dynamically.                                         |\n\n:::info\nFor complete reference, see the [official documentation](https://adaptivecards.microsoft.com/?topic=Action.Execute).\n:::\n\n## Creating Actions with the SDK\n\n### Single Actions\n\nThe SDK provides builder helpers that abstract the underlying JSON. For example:\n\n<LanguageInclude section=\"single-action-example\" />\n\n### Action Sets\n\nGroup actions together using `ActionSet`:\n\n<LanguageInclude section=\"action-set-example\" />\n\n### Raw JSON Alternative\n\nJust like when building cards, if you prefer to work with raw JSON, you can do just that. <LanguageInclude section=\"json-safety-note\" />\n\n<LanguageInclude section=\"raw-json-example\" />\n\n## Working with Input Values\n\n### Associating data with the cards\n\nSometimes you want to send a card and have it be associated with some data. Set the `data` value to be sent back to the client so you can associate it with a particular entity.\n\n<LanguageInclude section=\"input-association-example\" />\n\n### Input Validation\n\nInput Controls provide ways for you to validate. More details can be found on the Adaptive Cards [documentation](https://adaptivecards.microsoft.com/?topic=input-validation).\n\n<LanguageInclude section=\"input-validation-example\" />\n\n## Server Handlers\n\n### Basic Structure\n\nCard actions arrive as `card.action` activities in your app. These give you access to the validated input values plus any `data` values you had configured to be sent back to you.\n\n<LanguageInclude section=\"server-handler-example\" />\n\n<LanguageInclude section=\"data-typing-note\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/README.mdx",
    "content": "---\ntitle: 🤖 AI\nsidebar_position: 5\nsummary: Overview of AI components in Teams SDK, including Prompts for orchestration and Models for LLM interfaces.\n---\n\n# 🤖 AI\n\nThe AI packages in this SDK are designed to make it easier to build applications with LLMs.\nThe <LanguageInclude section=\"package-name\" /> has two main components:\n\n## 📦 Prompts\n\nA `Prompt` is the component that orchestrates everything, it handles state management,\nfunction definitions, and invokes the model/template when needed. This layer abstracts many of\nthe complexities of the Models to provide a common interface.\n\n## 🧠 Models\n\nA `Model` is the component that interfaces with the LLM, being given some `input` and returning the `output`.\nThis layer deals with any of the nuances of the particular Models being used.\n\nIt is in the model implementation that the individual LLM features (i.e. streaming/tools etc.)\nare made compatible with the more general features of the <LanguageInclude section=\"package-name\" />.\n\n:::note\nYou are not restricted to use the <LanguageInclude section=\"package-name\" /> to build your Teams Agent applications. You can use models directly if you choose. These packages are there to simplify the interactions with the models and Teams.\n:::\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/_category_.json",
    "content": "{\n  \"label\": \"🤖 AI\",\n  \"key\": \"ai-guide\",\n  \"position\": 5,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/a2a/README.mdx",
    "content": "---\nsummary: Overview of the experimental A2A (Agent-to-Agent) protocol for enabling programmatic communication between AI agents.\nlanguages: ['typescript', 'python']\ntitle: A2A Protocol\n---\n\n# A2A (Agent-to-Agent) Protocol\n\n<LanguageInclude section=\"note\" />\n\n[What is A2A?](https://a2a-protocol.org/latest/)\n\nA2A (Agent-to-Agent) is a protocol designed to enable agents to communicate and collaborate programmatically. This package allows you to integrate the A2A protocol into your Teams app, making your agent accessible to other A2A clients and enabling your app to interact with other A2A servers.\n\n<LanguageInclude section=\"install\" />\n\n## What does this package do?\n\n- **A2A Server**: Enables your Teams agent to act as an A2A server, exposing its capabilities to other agents through the `/a2a` endpoint and serving an agent card at `/a2a/.well-known/agent-card.json`.\n- **A2A Client**: Allows your Teams app to proactively reach out to other A2A servers as a client, either through direct `AgentManager` usage or integrated with `ChatPrompt` for LLM-driven interactions.\n\n## High-level Architecture\n\n### A2A Server\n\n```mermaid\nflowchart RL\n    A_S[TeamsApp]\n    B[A2APlugin]\n    D[External A2A Client]\n\n\n    D -- \"task/send\" message --> A_S\n    subgraph A2A Server\n        direction LR\n        A_S --> B\n    end\n    B -- AgentCard --> D\n    B -- \"task/send\" response --> D\n```\n\n### A2A Client\n\n```mermaid\nflowchart LR\n    A_C[TeamsApp]\n    C[A2AClientPlugin]\n    E[External A2A Server]\n    U[Teams User]\n\n    U --> A_C\n    subgraph A2A Client\n        direction LR\n        A_C -- message --> C\n        C -- response from server --> A_C\n    end\n    C -- message task/send --> E\n    E -- AgentCard --> C\n    E -- task/send response --> C\n```\n\n## Protocol Details\n\nFor detailed information about the A2A protocol, including agent card structure, message formats, and protocol specifications, see the official [A2A Protocol Documentation](https://a2a-protocol.org/latest/specification/).\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/a2a/_category_.json",
    "content": "{\n  \"label\": \"A2A (Agent-to-Agent)\",\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/a2a/a2a-client.mdx",
    "content": "---\nsidebar_position: 2\ntitle: A2A Client\nsidebar_label: A2A Client\nsummary: How to implement an A2A client to proactively send tasks to A2A servers using the AgentManager.\nlanguages: ['typescript', 'python']\n---\n\n# A2A Client\n\n## What is an A2A Client?\n\nAn A2A client is an agent or application that can proactively send tasks to A2A servers and interact with them using the A2A protocol.\n\n## Using `A2AClient` Directly\n\nFor direct control over A2A interactions, you can use the `A2AClient` from the SDK:\n\n<LanguageInclude section=\"direct-client\" />\n\n## Using `A2AClientPlugin` with ChatPrompt\n\nA2A is most effective when used with an LLM. The `A2AClientPlugin` can be added to your chat prompt to allow interaction with A2A agents. Once added, the plugin will automatically configure the system prompt and tool calls to determine if the a2a server is needed for a particular task, and if so, it will do the work of orchestrating the call to the A2A server.\n\n<LanguageInclude section=\"client-plugin\" />\n\nTo send a message:\n\n<LanguageInclude section=\"send-message\" />\n\n### Advanced `A2AClientPlugin` Configuration\n\nYou can customize how the client interacts with A2A agents by providing custom builders:\n\n<LanguageInclude section=\"advanced-config\" />\n\n## Sequence Diagram\n\nHere's how the A2A client works with `ChatPrompt` and `A2AClientPlugin`:\n\n<LanguageInclude section=\"sequence-diagram\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/a2a/a2a-server.mdx",
    "content": "---\nsidebar_position: 1\ntitle: A2A Server\nsummary: How to implement an A2A server to expose your Teams app capabilities to other agents using the A2A protocol.\nlanguages: ['typescript', 'python']\n---\n\nimport FileCodeBlock from '@site/src/components/FileCodeBlock';\n\n# A2A Server\n\n## What is an A2A Server?\n\nAn A2A server is an agent that exposes its capabilities to other agents using the A2A protocol. With this package, you can make your Teams app accessible to A2A clients.\n\n## Adding the `A2APlugin`\n\nTo enable A2A server functionality, add the `A2APlugin` to your Teams app and provide an <LanguageInclude section=\"agent-card\" />:\n\n<LanguageInclude section=\"plugin-example\" />\n\n## Agent Card Exposure\n\nThe plugin automatically exposes your agent card at the path `/a2a/.well-known/agent-card.json`.\n\n## Handling A2A Requests\n\nHandle incoming A2A requests by adding an event handler for the `a2a:message` event. You may use `accumulateArtifacts` to iteratively accumulate artifacts for the task, or simply `respond` with the final result.\n\n<LanguageInclude section=\"event-handler\" />\n\n:::note\n\n- You must have only a single handler that calls `respond`.\n- You **must** call `respond` as the last step in your handler. This resolves the open request to the caller.\n\n:::\n\n## Sequence Diagram\n\n```mermaid\nsequenceDiagram\n    participant A2A Client\n    participant App\n    participant A2APlugin\n    participant YourEventHandler\n\n    A2A Client->>App: /task/send\n    App->>A2APlugin: Call A2APlugin\n    A2APlugin->>YourEventHandler: Call your event handler a2a:message\n    YourEventHandler->>A2APlugin: Call respond\n    A2APlugin->>A2A Client: Return response\n```\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/best-practices.mdx",
    "content": "---\nsidebar_position: 5\nsidebar_label: Best Practices\ntitle: Best Practices\nsummary: Best practices for AI integration in Teams applications, including AI-generated message indicators, feedback collection for prompt improvement, and citation handling to ensure transparency and accuracy in AI responses.\n---\n\n# Best Practices\n\nWhen sending messages using AI, Teams recommends a number of best practices to help with both user and developer experience.\n\n## AI-Generated Indicator\n\nWhen sending messages using AI, Teams recommends including an indicator that the message was generated by AI. <LanguageInclude section=\"ai-generated-method\" /> This will help users understand that the message was generated by AI, and not by a human and can help with trust and transparency.\n\n<LanguageInclude section=\"ai-generated-code\" />\n\n![Screenshot of outgoing agent message to user marked with 'AI generated' badge.](/screenshots/ai-generated.gif)\n\n## Gather feedback to improve prompts\n\nAI Generated messages are not always perfect. Prompts can have gaps, and can sometimes lead to unexpected results. To help improve the prompts, Teams recommends gathering feedback from users on the AI-generated messages. See [Feedback](../feedback) for more information on how to gather feedback.\n\nThis does involve thinking through a pipeline for gathering feedback and then automatically, or manually, updating prompts based on the feedback. The feedback system is an point of entry to your eval pipeline.\n\n## Citations\n\nAI generated messages can hallucinate even if messages are grounded in real data. To help with this, Teams recommends including citations in the AI Generated messages. <LanguageInclude section=\"citations-method\" />\n\n:::warning\nCitations are added with a `position` property. This property value needs to also be included in the message text as `[<position>]`. If there is a citation that's added without the associated value in the message text, Teams will not render the citation\n:::\n\n<LanguageInclude section=\"citations-code\" />\n\n![Animated screenshot showing user hovering over a footnote citation in agent response, and a pop-up showing explanatory text.](/screenshots/citation.gif)\n\n## Suggested actions\n\nSuggested actions help users with ideas of what to ask next, based on the previous response or conversation. Teams recommends including suggested actions in your messages. <LanguageInclude section=\"suggested-actions-method\" /> See [Suggested actions](https://learn.microsoft.com/microsoftteams/platform/bots/how-to/conversations/prompt-suggestions) for more information on suggested actions.\n\n<LanguageInclude section=\"suggested-actions-code\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/chat.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: 💬 Chat Generation\ntitle: 💬 Chat Generation\nsummary: Comprehensive guide to implementing chat generation with LLMs in Teams, covering setup with ChatPrompt and Model objects, basic message handling, and streaming responses for improved user experience.\n---\n\n# 💬 Chat Generation\n\nBefore going through this guide, please make sure you have completed the [setup and prerequisites](./setup-and-prereqs.mdx) guide.\n\n# Setup\n\nThe basic setup involves creating a `ChatPrompt` and giving it the `Model` you want to use.\n\n```mermaid\nflowchart LR\n    Prompt\n\n    subgraph Application\n        Send --> Prompt\n        UserMessage[\"User Message<br/>Hi how are you?\"] --> Send\n        Send --> Content[\"Content<br/>I am doing great! How can I help you?\"]\n\n        subgraph Setup\n            Messages --> Prompt\n            Instructions --> Prompt\n            Options[\"Other options...\"] --> Prompt\n\n            Prompt --> Model\n        end\n    end\n\n    subgraph LLMProvider\n        Model --> AOAI[\"Azure Open AI\"]\n        Model --> OAI[\"Open AI\"]\n        Model --> Anthropic[\"Claude\"]\n        Model --> OtherModels[\"...\"]\n    end\n```\n\n## Simple chat generation\n\nChat generation is the the most basic way of interacting with an LLM model. It involves setting up your ChatPrompt, the Model, and sending it the message.\n\n<LanguageInclude section=\"simple-chat-setup\" />\n\n<LanguageInclude section=\"simple-chat-code\" />\n\n<LanguageInclude section=\"declarative-approach\" />\n\n<LanguageInclude section=\"simple-chat-notes\" />\n\n<LanguageInclude section=\"additional-concepts\" />\n\n## Streaming chat responses\n\nLLMs can take a while to generate a response, so often streaming the response leads to a better, more responsive user experience.\n\n:::warning\nStreaming is only currently supported for single 1:1 chats, and not for groups or channels.\n:::\n\n<LanguageInclude section=\"streaming-code\" />\n\n![Animated image showing agent response text incrementally appearing in the chat window.](/screenshots/streaming-chat.gif)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/function-calling.mdx",
    "content": "---\nsidebar_position: 3\nsidebar_label: Function Calling\ntitle: Function Calling\nsummary: How to implement function calling in AI models, allowing the LLM to execute functions as part of its response generation.\n---\n\n# Function / Tool calling\n\nIt's possible to hook up functions that the LLM can decide to call if it thinks it can help with the task at hand. This is done by <LanguageInclude section=\"adding-functions\" />.\n\n<LanguageInclude section=\"sequence-diagram\" />\n\n<LanguageInclude section=\"single-function-example\" />\n\n<LanguageInclude section=\"multiple-functions\" />\n\n<LanguageInclude section=\"advanced-features\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/keeping-state.mdx",
    "content": "---\nsidebar_position: 4\nsidebar_label: Keeping State\ntitle: Keeping State\nsummary: Guide to managing conversation state in LLM interactions, explaining how to maintain chat history using ChatPrompt's state management capabilities and implementing custom persistence strategies for multi-conversation scenarios.\n---\n\n# Keeping State\n\nBy default, LLMs are not stateful. This means that they do not remember previous messages or context when generating a response.\nIt's common practice to keep state of the conversation history in your application and pass it to the LLM each time you make a request.\n\nBy default, the `ChatPrompt` instance will create a temporary in-memory store to keep track of the conversation history. This is beneficial\nwhen you want to use it to generate an LLM response, but not persist the conversation history. But in other cases, you may want to keep the conversation history\n\n:::warning\nBy reusing the same `ChatPrompt` class instance across multiple conversations will lead to the conversation history being shared across all conversations. Which is usually not the desired behavior.\n:::\n\nTo avoid this, you need to get messages from your persistent (or in-memory) store and pass it in to the `ChatPrompt`.\n\n:::note\nThe `ChatPrompt` class will modify the messages object that's passed into it. So if you want to manually manage it, you need to make a copy of the messages object before passing it in.\n:::\n\n## State Initialization\n\nHere's how to initialize and manage conversation state for multiple conversations:\n\n<LanguageInclude section=\"state-initialization\" />\n\n## Usage Example\n\n<LanguageInclude section=\"usage-example\" />\n\n<LanguageInclude section=\"additional-info\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/mcp/README.mdx",
    "content": "---\nsidebar_position: 6\ntitle: MCP\nsummary: Overview of Model Context Protocol (MCP) integration in Teams SDK for dynamic function and tool loading.\nsuppressLanguageIncludeWarning: true\n---\n\n# MCP\n\nTeams SDK has optional packages which support the [Model Context Protocol (MCP)](https://modelcontextprotocol.io/introduction) as a service or client. This allows you to use MCP to call functions and tools in your application.\n\nMCP servers and MCP clients dynamically load function definitions and tools.\n\nWhen building Servers, this could mean that you can introduce new tools as part of your application, and the MCP clients that are connected to it will automatically start consuming those tools.\n\nWhen building Clients, this could mean that you can connect to other MCP servers and your application has the flexibility to improve as the MCP servers its connected to evolve over time.\n\n:::tip\nThe guides here can be used to build a server and a client that can leverage each other. That means you can build a server that has the ability to do complex things for the client agent.\n:::\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/mcp/_category_.json",
    "content": "{\n  \"label\": \"MCP\",\n  \"key\": \"mcp-guide\",\n  \"position\": 6,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/mcp/mcp-client.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: MCP Client\ntitle: MCP Client\nsummary: How to implement an MCP client to leverage remote MCP servers and their tools in your AI agent application.\n---\n\n# MCP Client\n\nYou are able to leverage other MCP servers that expose tools via the <LanguageInclude section=\"protocol-name\" /> as part of your application. This allows your AI agent to use remote tools to accomplish tasks.\n\n<LanguageInclude section=\"install\" />\n\n:::info\nTake a look at [Function calling](../function-calling) to understand how the `ChatPrompt` leverages tools to enhance the LLM's capabilities. MCP extends this functionality by allowing remote tools, that may or may not be developed or maintained by you, to be used by your application.\n:::\n\n## Remote MCP Server\n\nThe first thing that's needed is access to a **remote** MCP server. MCP Servers (at present) come using two main types protocols:\n\n1. StandardIO - This is a _local_ MCP server, which runs on your machine. An MCP client may connect to this server, and use standard input and outputs to communicate with it. Since our application is running remotely, this is not something that we want to use\n2. <LanguageInclude section=\"remote-protocol\" /> - This is a _remote_ MCP server. An MCP client may\n   send it requests and the server responds in the expected MCP protocol.\n\nFor hooking up to your <LanguageInclude section=\"server-setup\" /> server, you will need to know the URL of the server, and if applicable, <LanguageInclude section=\"auth-requirements\" /> that must be included as part of the header.\n\n## MCP Client Plugin\n\nThe <LanguageInclude section=\"plugin-class\" /> integrates directly with the `ChatPrompt` <LanguageInclude section=\"integration-method\" />. When the `ChatPrompt`'s <LanguageInclude section=\"send-method\" /> function is called, it calls the external MCP server and loads up all the tools that are available to it.\n\nOnce loaded, it treats these tools like any functions that are available to the `ChatPrompt` object. If the LLM then decides to call one of these remote MCP tools, the MCP Client plugin will call the remote MCP server and return the result back to the LLM. The LLM can then use this result in its response.\n\n<LanguageInclude section=\"basic-example\" />\n\n<LanguageInclude section=\"custom-headers\" />\n\n<LanguageInclude section=\"multiple-servers\" />\n\n<LanguageInclude section=\"mcp-server-note\" />\n\n<LanguageInclude section=\"example-gif\" />\n\n<LanguageInclude section=\"pokemon-example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/mcp/mcp-server.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: MCP Server\ntitle: MCP Server\nsummary: How to convert your Teams app into an MCP server using the McpPlugin to expose tools, resources, and prompts to other MCP applications.\nlanguages: ['typescript', 'python']\n---\n\n# MCP Server\n\n<LanguageInclude section=\"intro\" />\n\n<LanguageInclude section=\"install\" />\n\n<LanguageInclude section=\"configuration\" />\n\n<LanguageInclude section=\"path-note\" />\n\n<LanguageInclude section=\"app-integration\" />\n\n<LanguageInclude section=\"devtools-tip\" />\n\n<LanguageInclude section=\"devtools-gif\" />\n\n## Piping messages to the user\n\nSince your agent is provisioned to work on Teams, one very helpful feature is to use this server as a way to send messages to the user. This can be helpful in various scenarios:\n\n1. Human in the loop - if the server or an MCP client needs to confirm something with the user, it is able to do so.\n2. Notifications - the server can be used as a way to send notifications to the user.\n\nHere is an example of how to do this. Configure your plugin so that:\n\n1. It can validate if the incoming request is allowed to send messages to the user\n2. It fetches the correct conversation ID for the given user.\n3. It sends a proactive message to the user. See [Proactive Messaging](../../../essentials/sending-messages/proactive-messaging) for more details.\n\n<LanguageInclude section=\"proactive-messaging\" />\n\n<LanguageInclude section=\"message-handler\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/ai/setup-and-prereqs.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: Setup & Prerequisites\ntitle: Setup & Prerequisites\nsummary: Prerequisites and setup guide for integrating LLMs into Teams SDK applications, including API keys and configuration.\n---\n\n# Setup & Prerequisites\n\nThere are a few prerequisites to getting started with integrating LLMs into your application:\n\n- LLM API Key - To generate messages using an LLM, you will need to have an API Key for the LLM you are using.\n  - [Azure OpenAI](https://azure.microsoft.com/en-us/products/ai-services/openai-service)\n  - [OpenAI](https://platform.openai.com/)\n\n<LanguageInclude section=\"package-install\" />\n\n- In your application, you should include your keys in a secure way. <LanguageInclude section=\"config-method\" />\n\n<LanguageInclude section=\"project-structure\" />\n\n### Azure OpenAI\n\nYou will need to deploy a model in Azure OpenAI. View the [resource creation guide](https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/create-resource?pivots=web-portal#deploy-a-model 'Azure OpenAI Model Deployment Guide') for more information on how to do this.\n\n<LanguageInclude section=\"azure-openai-config\" />\n\n<LanguageInclude section=\"azure-openai-info\" />\n\n### OpenAI\n\nYou will need to create an OpenAI account and get an API key. View the [OpenAI Quickstart Guide](https://platform.openai.com/docs/quickstart/build-your-application 'OpenAI Quickstart Guide') for how to do this.\n\n<LanguageInclude section=\"openai-config\" />\n\n<LanguageInclude section=\"additional-notes\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/dialogs/README.mdx",
    "content": "---\nsidebar_position: 2\ntitle: 'Dialogs'\nsuppressLanguageIncludeWarning: true\n---\n\n# Dialogs\n\nDialogs are a helpful paradigm in Teams which improve interactions between your agent and users. When dialogs are **invoked**, they pop open a window for a user in the Teams client. The content of the dialog can be supplied by the agent application.\n\n:::note\nIn Teams client v1, dialogs were called task modules. They may occasionaly be used synonymously.\n:::\n\n## Key benefits\n\n1. Dialogs pop open for a user in the Teams client. This means in group-settings, dialog actions are not visible to other users in the channel, reducing clutter.\n\n2. Interactions like filling out complex forms, or multi-step forms where each step depends on the previous step are excellent use cases for dialogs.\n\n3. The content for the dialog can be hard-coded in, or fetched at runtime. This makes them extremely flexible and powerful.\n\n## Resources\n\n- [Task Modules](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/what-are-task-modules)\n- [Invoking Task Modules](https://learn.microsoft.com/en-us/microsoftteams/platform/task-modules-and-cards/task-modules/invoking-task-modules)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/dialogs/_category_.json",
    "content": "{\n  \"position\": 2,\n  \"label\": \"Dialogs (Task Modules)\",\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/dialogs/creating-dialogs.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: 'Creating Dialogs'\ntitle: 'Creating Dialogs'\n---\n\n# Creating Dialogs\n\n:::tip\nIf you're not familiar with how to build Adaptive Cards, check out [the cards guide](../adaptive-cards). Understanding their basics is a prerequisite for this guide.\n:::\n\n## Entry Point\n\n<LanguageInclude section=\"entry-point-intro\" />\n\n<LanguageInclude section=\"entry-point-code\" />\n\n## Handling Dialog Open Events\n\n<LanguageInclude section=\"dialog-open-intro\" />\n\n<LanguageInclude section=\"dialog-open-code\" />\n\n### Rendering A Card\n\nYou can render an Adaptive Card in a dialog by returning a card response.\n\n<LanguageInclude section=\"rendering-card-code\" />\n\n:::info\nThe action type for submitting a dialog must be `Action.Submit`. This is a requirement of the Teams client. If you use a different action type, the dialog will not be submitted and the agent will not receive the submission event.\n:::\n\n### Rendering A Webpage\n\nYou can render a webpage in a dialog as well. There are some security requirements to be aware of:\n\n1. The webpage must be hosted on a domain that is allow-listed as `validDomains` in the Teams app [manifest](/teams/manifest) for the agent\n2. The webpage must also host the [teams-js client library](https://www.npmjs.com/package/@microsoft/teams-js). The reason for this is that for security purposes, the Teams client will not render arbitrary webpages. As such, the webpage must explicitly opt-in to being rendered in the Teams client. Setting up the teams-js client library handles this for you.\n\n<LanguageInclude section=\"rendering-webpage-code\" />\n\n<LanguageInclude section=\"embedded-web-content\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/dialogs/handling-dialog-submissions.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: Handling Dialog Submissions\ntitle: 'Handling Dialog Submissions'\nsummary: Guide to processing dialog submissions in Teams applications, showing how to handle form data from both Adaptive Cards and web pages using dialog submission event handlers.\n---\n\n# Handling Dialog Submissions\n\n<LanguageInclude section=\"event-intro\" />\n\nIn this example, we show how to handle dialog submissions from an Adaptive Card form:\n\n<LanguageInclude section=\"adaptive-card-example\" />\n\nSimilarly, handling dialog submissions from rendered webpages is also possible:\n\n<LanguageInclude section=\"webpage-example\" />\n\n<LanguageInclude section=\"complete-example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/dialogs/handling-multi-step-forms.mdx",
    "content": "---\nsidebar_position: 3\nsidebar_label: Handling Multi-Step Forms\ntitle: 'Handling Multi-Step Forms'\nsummary: Tutorial on implementing multi-step dialogs in Teams, demonstrating how to create dynamic form flows that adapt based on user input, with examples of handling state between steps and conditional navigation.\n---\n\n# Handling Multi-Step Forms\n\nDialogs can become complex yet powerful with multi-step forms. These forms can alter the flow of the survey depending on the user's input or customize subsequent steps based on previous answers.\n\n<LanguageInclude section=\"initial-setup\" />\n\n<LanguageInclude section=\"initial-card\" />\n\n<LanguageInclude section=\"submission-handler\" />\n\n<LanguageInclude section=\"complete-example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/feedback.mdx",
    "content": "---\nsidebar_position: 7\ntitle: Feedback\nsidebar_label: Feedback\nsummary: Guide to implementing user feedback functionality in Teams applications, covering feedback UI components, event handling, and storage mechanisms for gathering and managing user responses to improve application performance.\n---\n\n# Feedback\n\nUser feedback is essential for the improvement of any application. Teams provides specialized UI components to help facilitate the gathering of feedback from users.\n\n![Animated image showing user selecting the thumbs-up button on an agent response and a dialog opening asking 'What did you like?'. The user types 'Nice' and hits Submit.](/screenshots/feedback.gif)\n\n## Storage\n\nOnce you receive a feedback event, you can choose to store it in some persistent storage. In the example below, we are storing it in an in-memory store.\n\n<LanguageInclude section=\"storage\" />\n\n## Including Feedback Buttons\n\nWhen sending a message that you want feedback in, simply add feedback functionality to the message you are sending.\n\n<LanguageInclude section=\"including-feedback\" />\n\n## Handling the feedback\n\nOnce the user decides to like/dislike the message, you can handle the feedback in a received event. Once received, you can choose to include it in your persistent store.\n\n<LanguageInclude section=\"handling-feedback\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/meeting-events.mdx",
    "content": "---\nsidebar_position: 8\ntitle: Meeting Events\nsidebar_label: Meeting Events\nsummary: Guide to handling meeting events in Teams applications, covering meeting lifecycle events such as meeting start, meeting end, participant join, and participant leave events.\n---\n\n# Meeting Events\n\nMicrosoft Teams provides meeting events that allow your application to respond to various meeting lifecycle changes. Your app can listen to events like when a meeting starts, meeting ends, and participant activities to create rich, interactive experiences.\n\n## Overview\n\nMeeting events enable your application to:\n- Send notifications when meetings start or end\n- Track participant activity (join/leave events)\n- Display relevant information or cards based on meeting context\n- Integrate with meeting workflows\n\n## Configuring Your Bot\n\nThere are a few requirements in the Teams app manifest (`manifest.json`) to support these events.\n\n1. The scopes section must include `team`, and `groupChat`\n\n```json\nbots\": [\n        {\n            \"botId\": \"\",\n            \"scopes\": [\n                \"team\",\n                \"personal\",\n                \"groupChat\"\n            ],\n            \"isNotificationOnly\": false\n        }\n    ]\n```\n\n2. In the authorization section, make sure to specify the following resource-specific permissions:\n\n```json\n \"authorization\":{\n        \"permissions\":{\n            \"resourceSpecific\":[\n                {\n                    \"name\":\"OnlineMeetingParticipant.Read.Chat\",\n                    \"type\":\"Application\"\n                },\n                {\n                    \"name\":\"ChannelMeeting.ReadBasic.Group\",\n                    \"type\":\"Application\"\n                },\n                {\n                    \"name\":\"OnlineMeeting.ReadBasic.Chat\",\n                    \"type\":\"Application\"\n                }\n                ]\n            }\n        }\n```\n\n3. In the Teams Developer Portal, for your `Bot`, make sure the `Meeting Event Subscriptions` are checked off. This enables you to receive the Meeting Participant events. For these events, you must create your Bot via TDP.\n\n## Meeting Start Event\n\nWhen a meeting starts, your app can handle the `meetingStart` event to send a notification or card to the meeting chat.\n\n<LanguageInclude section=\"meeting-start\" />\n\n## Meeting End Event\n\nWhen a meeting ends, your app can handle the `meetingEnd` event to send a summary or follow-up information.\n\n<LanguageInclude section=\"meeting-end\" />\n\n## Participant Join Event\n\nWhen a participant joins a meeting, your app can handle the `meetingParticipantJoin` event to welcome them or display their role.\n\n<LanguageInclude section=\"participant-join\" />\n\n## Participant Leave Event\n\nWhen a participant leaves a meeting, your app can handle the `meetingParticipantLeave` event to notify others.\n\n<LanguageInclude section=\"participant-leave\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/message-extensions/README.mdx",
    "content": "---\ntitle: Message Extensions\nsidebar_position: 3\nsummary: Overview of message extensions for enhancing user productivity with quick access to information and actions within Teams.\nsuppressLanguageIncludeWarning: true\n---\n\n# 📖 Message Extensions\n\nMessage extensions (or Compose Extensions) allow your application to hook into messages that users can send or perform actions on messages that users have already sent. They enhance user productivity by providing quick access to information and actions directly within the Teams interface. Users can search or initiate actions from the compose message area, the command box, or directly from a message, with the results returned as richly formatted cards that make information more accessible and actionable.\n\nThere are two types of message extensions: [API-based](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/api-based-overview) and [Bot-based](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/build-bot-based-message-extension?tabs=search-commands). API-based message extensions use an OpenAPI specification that Teams directly queries, requiring no additional application to build or maintain, but offering less customization. Bot-based message extensions require building an application to handle queries, providing more flexibility and customization options. This SDK supports bot-based message extensions only.\n\n## Resources\n\n- [What are message extensions?](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/what-are-messaging-extensions?tabs=desktop)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/message-extensions/_category_.json",
    "content": "{\n  \"label\": \"📖 Message Extensions\",\n  \"position\": 3,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/message-extensions/action-commands.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: 'Action Commands'\ntitle: 'Action Commands'\nsummary: Learn how to create action commands for message extensions that present modal dialogs to collect or display information in Teams.\n---\n\n# Action commands\n\nAction commands allow you to present your users with a modal pop-up called a dialog in Teams. The dialog collects or displays information, processes the interaction, and sends the information back to Teams compose box.\n\n## Action command invocation locations\n\nThere are three different areas action commands can be invoked from:\n\n1. Compose Area\n2. Compose Box\n3. Message\n\n### Compose Area and Box\n\n![Screenshot of Teams with outlines around the 'Compose Box' (for typing messages) and the 'Compose Area' (the menu option next to the compose box that provides a search bar for actions and apps).](/screenshots/compose-area.png)\n\n### Message action command\n\n![Screenshot of message extension response in Teams. By selecting the '...' button, a menu has opened with 'More actions' option in which they can select from a list of available message extension actions.](/screenshots/message.png)\n\n:::tip\nSee the [Invoke Locations](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/define-action-command?tabs=Teams-toolkit%2Cdotnet#select-action-command-invoke-locations) guide to learn more about the different entry points for action commands.\n:::\n\n## Setting up your Teams app manifest\n\nTo use action commands you have define them in the Teams app manifest. Here is an example:\n\n```json\n\"composeExtensions\": [\n    {\n        \"botId\": \"${{BOT_ID}}\",\n        \"commands\": [\n            {\n            \"id\": \"createCard\",\n            \"type\": \"action\",\n            \"context\": [\n                \"compose\",\n                \"commandBox\"\n            ],\n            \"description\": \"Command to run action to create a card from the compose box.\",\n            \"title\": \"Create Card\",\n            \"parameters\": [\n                {\n                    \"name\": \"title\",\n                    \"title\": \"Card title\",\n                    \"description\": \"Title for the card\",\n                    \"inputType\": \"text\"\n                },\n                {\n                    \"name\": \"subTitle\",\n                    \"title\": \"Subtitle\",\n                    \"description\": \"Subtitle for the card\",\n                    \"inputType\": \"text\"\n                },\n                {\n                    \"name\": \"text\",\n                    \"title\": \"Text\",\n                    \"description\": \"Text for the card\",\n                    \"inputType\": \"textarea\"\n                }\n            ]\n            },\n            {\n                \"id\": \"getMessageDetails\",\n                \"type\": \"action\",\n                \"context\": [\n                    \"message\"\n                ],\n                \"description\": \"Command to run action on message context.\",\n                \"title\": \"Get Message Details\"\n            },\n            {\n                \"id\": \"fetchConversationMembers\",\n                \"description\": \"Fetch the conversation members\",\n                \"title\": \"Fetch Conversation Members\",\n                \"type\": \"action\",\n                \"fetchTask\": true,\n                \"context\": [\n                    \"compose\"\n                ]\n            },\n        ]\n    }\n]\n```\n\nHere we have defining three different commands:\n\n1. `createCard` - that can be invoked from either the `compose` or `commandBox` areas. Upon invocation a dialog will popup asking the user to fill the `title`, `subTitle`, and `text`.\n\n![Screenshot of a message extension dialog with the editable fields 'Card title', 'Subtitle', and 'Text'.](/screenshots/parameters.png)\n\n2. `getMessageDetails` - It is invoked from the `message` overflow menu. Upon invocation the message payload will be sent to the app which will then return the details like `createdDate`, etc.\n\n![Screenshot of the 'More actions' message extension menu expanded with 'Get Message Details' option selected.](/screenshots/message-command.png)\n\n3. `fetchConversationMembers` - It is invoked from the `compose` area. Upon invocation the app will return an adaptive card in the form of a dialog with the conversation roster.\n\n![Screenshot of the 'Fetch Conversation Members' option exposed from the message extension menu '...' option.](/screenshots/fetch-conversation-members.png)\n\n## Handle submission\n\n<LanguageInclude section=\"handle-submission-intro\" />\n\n<LanguageInclude section=\"handle-submission-code\" />\n\n### Create card\n\n<LanguageInclude section=\"create-card-function\" />\n\n### Create message details card\n\n<LanguageInclude section=\"create-message-details-function\" />\n\n## Handle opening adaptive card dialog\n\n<LanguageInclude section=\"handle-dialog-intro\" />\n\n<LanguageInclude section=\"handle-dialog-code\" />\n\n### Create conversation members card\n\n<LanguageInclude section=\"create-conversation-members-function\" />\n\n## Resources\n\n- [Action commands](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/define-action-command?tabs=Teams-toolkit%2Cdotnet)\n- [Returning Adaptive Card Previews in Task Modules](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/action-commands/respond-to-task-module-submit?tabs=dotnet%2Cdotnet-1#bot-response-with-adaptive-card)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/message-extensions/link-unfurling.mdx",
    "content": "---\nsidebar_position: 4\nsidebar_label: 'Link Unfurling'\ntitle: 'Link Unfurling'\nsummary: Enable your app to respond when users paste URLs by creating preview cards with additional information and actions.\n---\n\n# 🔗 Link unfurling\n\nLink unfurling lets your app respond when users paste URLs into Teams. When a URL from your registered domain is pasted, your app receives the URL and can return a card with additional information or actions. This works like a search command where the URL acts as the search term.\n\n:::note\nUsers can use link unfurling even before they discover or install your app in Teams. This is called [Zero install link unfurling](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?tabs=desktop%2Cjson%2Cadvantages#zero-install-for-link-unfurling). In this scenario, your app will receive a `message.ext.anon-query-link` activity instead of the usual `message.ext.query-link`.\n:::\n\n## Setting up your Teams app manifest\n\n### Configure message handlers\n\n```json\n\"composeExtensions\": [\n    {\n        \"botId\": \"${{BOT_ID}}\",\n        \"messageHandlers\": [\n            {\n                \"type\": \"link\",\n                \"value\": {\n                    \"domains\": [\n                        \"www.test.com\"\n                    ]\n                }\n            }\n        ]\n    }\n]\n```\n\n### How link unfurling works\n\nWhen a user pastes a URL from your registered domain (like `www.test.com`) into the Teams compose box, your app will receive a notification. Your app can then respond by returning an adaptive card that displays a preview of the linked content. This preview card appears before the user sends their message in the compose box, allowing them to see how the link will be displayed to others.\n\n```mermaid\nflowchart TD\n    A1[\"User pastes a URL (e.g., www\\.test\\.com) in Teams compose box\"]\n    B1([Microsoft Teams])\n    C1[\"Your App\"]\n    D1[\"Adaptive Card Preview\"]\n\n    A1 --> B1\n    B1 -->|Sends URL paste notification| C1\n    C1 -->|Returns card and preview| B1\n    B1 --> D1\n\n    %% Styling for readability and compatibility\n    style B1 fill:#2E86AB,stroke:#1B4F72,stroke-width:2px,color:#ffffff\n    style C1 fill:#28B463,stroke:#1D8348,stroke-width:2px,color:#ffffff\n    style D1 fill:#F39C12,stroke:#D68910,stroke-width:2px,color:#ffffff\n```\n\n## Implementing link unfurling\n\n### Handle the query link event\n\nHandle link unfurling when a URL from your registered domain is submitted into the Teams compose box.\n\n<LanguageInclude section=\"handle-link-unfurling-code\" />\n\n### Create the unfurl card\n\n<LanguageInclude section=\"create-link-unfurl-function\" />\n\n### User experience flow\n\nThe link unfurling response includes both a full adaptive card and a preview card. The preview card appears in the compose box when a user pastes a URL:\n\n![Screenshot showing a preview card for an unfurled URL in the Teams compose box.](/screenshots/link-unfurl-preview.png)\n\nThe user can expand the preview card by clicking on the _expand_ button on the top right.\n\n![Screenshot of Teams compose box with an outline around the unfurled link card labeled 'Adaptive Card'.](/screenshots/link-unfurl-card.png)\n\nThe user can then choose to send either the preview or the full adaptive card as a message.\n\n## Resources\n\n- [Link unfurling](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?tabs=desktop%2Cjson%2Cadvantages)\n- [Zero install link unfurling](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/link-unfurling?tabs=desktop%2Cjson%2Cadvantages#zero-install-for-link-unfurling)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/message-extensions/search-commands.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: 'Search Commands'\ntitle: 'Search Commands'\nsummary: Create search commands that allow users to search external systems and insert results as cards in Teams messages.\n---\n\n# 🔍 Search commands\n\nMessage extension search commands allow users to search external systems and insert the results of that search into a message in the form of a card.\n\n## Search command invocation locations\n\nThere are two different areas search commands can be invoked from:\n\n1. Compose Area\n2. Compose Box\n\n### Compose Area and Box\n\n![Screenshot of Teams with outlines around the 'Compose Box' (for typing messages) and the 'Compose Area' (the menu option next to the compose box that provides a search bar for actions and apps).](/screenshots/compose-area.png)\n\n## Setting up your Teams app manifest\n\nTo use search commands you have to define them in the Teams app manifest. Here is an example:\n\n```json\n\"composeExtensions\": [\n    {\n        \"botId\": \"${{BOT_ID}}\",\n        \"commands\": [\n            {\n                \"id\": \"searchQuery\",\n                \"context\": [\n                    \"compose\",\n                    \"commandBox\"\n                ],\n                \"description\": \"Test command to run query\",\n                \"title\": \"Search query\",\n                \"type\": \"query\",\n                \"parameters\": [\n                    {\n                        \"name\": \"searchQuery\",\n                        \"title\": \"Search Query\",\n                        \"description\": \"Your search query\",\n                        \"inputType\": \"text\"\n                    }\n                ]\n            }\n        ]\n    }\n]\n```\n\nHere we are defining the `searchQuery` search (or query) command.\n\n## Handle submission\n\nHandle the search query submission when the `searchQuery` search command is invoked.\n\n<LanguageInclude section=\"handle-submission-code\" />\n\n<LanguageInclude section=\"create-dummy-cards-function\" />\n\nThe search results include both a full adaptive card and a preview card. The preview card appears as a list item in the search command area:\n\n![Screenshot of Teams showing a message extensions search menu open with list of search results displayed as preview cards.](/screenshots/preview-card.png)\n\nWhen a user clicks on a list item the dummy adaptive card is added to the compose box:\n\n![Screenshot of Teams showing the selected adaptive card added to the compose box.](/screenshots/card-in-compose.png)\n\nTo implement custom actions when a user clicks on a search result item, you can add the `tap` property to the preview card. This allows you to handle the click event with custom logic:\n\n<LanguageInclude section=\"select-item-code\" />\n\n## Resources\n\n- [Search command](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/search-commands/define-search-command?tabs=Teams-toolkit%2Cdotnet)\n- [Just-In-Time Install](https://learn.microsoft.com/en-us/microsoftteams/platform/messaging-extensions/how-to/search-commands/universal-actions-for-search-based-message-extensions#just-in-time-install)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/message-extensions/settings.mdx",
    "content": "---\nsidebar_position: 3\nsidebar_label: 'Settings'\ntitle: 'Settings'\nsummary: Add configurable settings pages to your message extensions to allow users to customize app behavior.\nignore: true\n---\n\nimport SettingsImgUrl from '@site/static/screenshots/settings.png';\n\n# ⚙️ Settings\n\nYou can add a settings page that allows users to configure settings for your app.\n\nThe user can access the settings by right-clicking the app item in the compose box.\n\n<br />\n<img src={SettingsImgUrl} height=\"300px\" alt=\"Settings\" />\n\nThis guide will show how to enable user access to settings, as well as setting up a page that looks like this:\n\n![Settings Page](/screenshots/settings-page.png)\n\n## 1. Update the Teams Manifest\n\nSet the `canUpdateConfiguration` field to `true` in the desired message extension under `composeExtensions`.\n\n```json\n\"composeExtensions\": [\n    {\n        \"botId\": \"${{BOT_ID}}\",\n        \"canUpdateConfiguration\": true,\n        ...\n    }\n]\n```\n\n## 2. Serve the settings `html` page\n\nThis is the code snippet for the settings `html` page:\n\n<LanguageInclude section=\"html-code\" />\n\nSave it in the `index.html` file in the same folder as where your app is initialized.\n\nYou can serve it by adding the following code to your app:\n\n<LanguageInclude section=\"serve-code\" />\n\n<LanguageInclude section=\"tabs-note\" />\n\n## 3. Specify the URL to the settings page\n\nTo enable the settings page, your app needs to handle the `message.ext.query-settings-url` activity that Teams sends when a user right-clicks the app in the compose box. Your app must respond with the URL to your settings page. Here's how to implement this:\n\n<LanguageInclude section=\"query-settings-code\" />\n\n## 4. Handle Form Submission\n\nWhen a user submits the settings form, Teams sends a `message.ext.setting` activity with the selected option in the `activity.value.state` property. Handle it to save the user's selection:\n\n<LanguageInclude section=\"handle-submission-code\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/observability/README.mdx",
    "content": "---\ntitle: Observability\nsidebar_position: 8\nsummary: Monitor and track your Teams app performance with logging, middleware, and observability tools.\nsuppressLanguageIncludeWarning: true\n---\n\n# Observability\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/observability/_category_.json",
    "content": "{\n  \"label\": \"Observability\",\n  \"position\": 8,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/observability/logging.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: '🗃️ Custom Logger'\ntitle: '🗃️ Custom Logger'\nsummary: Configure custom loggers in your Teams app to control log levels and output destinations.\n---\n\n# 🗃️ Custom Logger\n\nThe `App` will provide a default logger, but you can also provide your own.\nThe default `Logger` instance will be set to <LanguageInclude section=\"default-logger\" /> from the <LanguageInclude section=\"package-name\" /> package.\n\n<LanguageInclude section=\"custom-logger-example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/observability/middleware.mdx",
    "content": "---\nsidebar_position: 1\ntitle: 'Middleware'\nsummary: Create middleware for logging, validation, and other cross-cutting concerns using the app.use method.\n---\n\n# Middleware\n\nMiddleware is a useful tool for logging, validation, and more.\nYou can easily register your own middleware using the <LanguageInclude section=\"app-use-method\" /> method.\n\nBelow is an example of a middleware that will log the elapse time of all handlers that come after it.\n\n<LanguageInclude section=\"middleware-example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/server/_category_.json",
    "content": "{\n  \"label\": \"Server\",\n  \"position\": 9,\n  \"collapsible\": true,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/server/http-server.mdx",
    "content": "---\nsidebar_position: 2\ntitle: Self-Managing Your Server\nsidebar_label: Self-Managing Your Server\nlanguages: ['typescript', 'python']\nsummary: How to self-manage the HTTP server — bring your own Express, FastAPI, or any framework by implementing the HttpServerAdapter interface.\n---\n\n# Self-Managing Your Server\n\nBy default, `app.start()` spins up an HTTP server, registers the Teams endpoint, and manages the full lifecycle for you. Under the hood, the SDK uses <LanguageInclude section=\"default-framework\" /> as its built-in HTTP framework. But if you need to self-manage your server — because you have an existing app, need custom server configuration (TLS, workers, middleware), or use a different HTTP framework — the SDK supports that through the `HttpServerAdapter` interface.\n\n## How It Works\n\nThe SDK splits HTTP handling into two layers:\n\n- **HttpServer** handles Teams protocol concerns: JWT authentication, activity parsing, and routing to your handlers.\n- **HttpServerAdapter** handles framework concerns: translating between your HTTP framework's request/response model and the SDK's pure handler pattern.\n\n```mermaid\nflowchart LR\n    Teams[\"Teams Service\"] -->|HTTP POST| Adapter[\"HttpServerAdapter<br/>(your framework)\"]\n    Adapter -->|\"{ body, headers }\"| HttpServer[\"HttpServer<br/>(auth + parse)\"]\n    HttpServer --> Handlers[\"Your App Handlers\"]\n    Handlers -->|\"{ status, body }\"| HttpServer\n    HttpServer --> Adapter\n    Adapter -->|\"HTTP Response\"| Teams\n```\n\nThe adapter interface is intentionally simple — implement `registerRoute` and the SDK handles the rest.\n\n## The Adapter Interface\n\n<LanguageInclude section=\"adapter-interface\" />\n\n- **`registerRoute`** — Required. Routes are registered dynamically (`/api/messages`, `/api/functions/{name}`, etc.).\n- **`serveStatic`** — Optional. Only needed for tabs or static pages.\n- **`start` / `stop`** — Optional. Omit when you manage the server lifecycle yourself.\n\n## Self-Managing Your Server\n\nTo add Teams to an existing server:\n\n1. Create your server with your own routes and middleware.\n2. Wrap it in an adapter (or use the built-in one with your server instance).\n3. Call `app.initialize()` — this registers the Teams routes on your server. Do **not** call `app.start()`.\n4. Start the server yourself.\n\n<LanguageInclude section=\"self-managed\" />\n\n## Using a Different Framework\n\nIf you use a framework other than the built-in default, implement the adapter interface for your framework. The core work is in `registerRoute` — translate incoming requests to `{ body, headers }`, call the handler, and write the response back. Since you manage the server lifecycle yourself, `start`/`stop` aren't needed. And `serveStatic` is only required if you serve tabs or static pages.\n\n<LanguageInclude section=\"custom-adapter\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/server/static-pages.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: Static Pages\ntitle: Static Pages\nsummary: Shows how to host web apps.\n---\n\n# Static Pages\n\nThe `App` class lets you host web apps in the agent. This can be used for an efficient inner loop when building a complex app using Microsoft 365 Agents Toolkit, as it lets you build, deploy, and sideload both an agent and a Tab app inside of Teams in a single step. It's also useful in production scenarios, as it makes it straight-forward to host a simple experience such as an agent configuration page or a Dialog.\n\nTo host a static tab web app, call the <LanguageInclude section=\"method-name\" /> function and provide an app name and a path to a folder containing an `index.html` file to be served up.\n\n<LanguageInclude section=\"code-example\" />\n\nThis registers a route that is hosted at <LanguageInclude section=\"route-pattern\" />.\n\n## Additional resources\n\n<LanguageInclude section=\"additional-resources\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/README.mdx",
    "content": "---\ntitle: Tabs\nsidebar_position: 6\nsummary: Build Teams tab apps with Graph integration, authentication, and remote agent function calling capabilities.\nlanguages: ['typescript', 'csharp']\n---\n\n# Tabs\n\nTabs are host-aware webpages embedded in Microsoft Teams, Outlook, and Microsoft 365. Tabs are commonly implemented as Single Page Applications that use the Teams [JavaScript client library](https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/using-teams-client-library) (TeamsJS) to interact with the app host.\n\n<LanguageInclude section=\"overview\" />\n\n## Resources\n\n- [Tabs overview](https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/what-are-tabs?tabs=personal)\n- [Teams JavaScript client library](https://learn.microsoft.com/en-us/microsoftteams/platform/tabs/how-to/using-teams-client-library)\n- [Microsoft Graph overview](https://learn.microsoft.com/en-us/graph/overview)\n- [Microsoft Authentication Library (MSAL)](https://learn.microsoft.com/en-us/entra/identity-platform/msal-overview)\n- [Nested App Authentication (NAA)](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/nested-authentication)\n\n<LanguageInclude section=\"additional-resources\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/_category_.json",
    "content": "{\n  \"label\": \"Tabs\",\n  \"position\": 6,\n  \"collapsible\": true,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/app-options.mdx",
    "content": "---\nsidebar_position: 3\ntitle: 'App Options'\nsummary: Configure app settings for observability, MSAL authentication, and remote agent function calling.\nlanguages: ['typescript']\nsuppressLanguageIncludeWarning: true\n---\n\n# App Options\n\nThe app options offer various settings that you can use to customize observability, Microsoft Authentication Library (MSAL) configuration, and\nremote agent function calling. Each setting is optional, with the app using a reasonable default as needed.\n\n## Logger\n\nIf no logger is specified in the app options, the app will create a [ConsoleLogger](../observability/logging). You can however provide your own logger implementation to control log level and destination.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\nimport { ConsoleLogger } from '@microsoft/teams.common';\n\nconst app = new App(clientId, {\n  logger: new ConsoleLogger('myTabApp', { level: 'debug' }),\n});\n\nawait app.start();\n```\n\n## Remote API options\n\nThe remote API options let you control which endpoint that `app.exec()` make a request to, as well as the default resource name to use when requesting an MSAL token to attach to the request.\n\n### Base URL\n\nThe `baseUrl` value is used to provide the URL where the remote API is hosted. This can be omitted if the tab app is hosted on the same domain as the remote agent.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId, {\n  remoteApiOptions: {\n    baseUrl: 'https://agent1.contoso.com',\n  },\n});\nawait app.start();\n\n// this requests a token for 'api://<clientId>/access_as_user' and attaches\n// that to a request to https://agent1.contoso.com/api/functions/my-function\nawait app.exec('my-function');\n```\n\n### Remote app resource\n\nThe `remoteAppResource` value is used to control the default resource name used when building a token request for the Entra token to include when invoking the function. This can be omitted if the tab app and the remote agent app are in the same AAD app, but should be provided if they're in different apps or the agent requires scopes for a different resource than the default `api://<clientId>/access_as_user`.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId, {\n  remoteApiOptions: {\n    baseUrl: 'https://agent1.contoso.com',\n    remoteAppResource: 'api://agent1ClientId',\n  },\n});\nawait app.start();\n\n// this requests a token for 'api://agent1ClientId/access_as_user' and attaches that\n// to a request to https://agent1.contoso.com/api/functions/my-function\nawait app.exec('my-function');\n```\n\n## MSAL options\n\nThe MSAL options let you control how the Microsoft Authentication Library (MSAL) is initialized and used, and how the user is prompted for scope consent as the app starts.\n\n### MSAL instance and configuration\n\nYou have three options to control the MSAL instance used by the app.\n\n- Provide a pre-configured and pre-initialized MSAL IPublicClientApplication.\n- Provide a custom MSAL configuration for the app to use when creating an MSAL IPublicClientApplication instance.\n- Provide neither, and let the app create IPublicClientApplication from a default MSAL configuration.\n\n#### Default behavior\n\nIf the app options contain neither an MSAL instance nor an MSAL configuration, the app constructs a simple MSAL configuration that is suitable for multi-tenant apps and that connects the MSAL logger callbacks to the app logger.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId);\n\nawait app.start();\n// app.msalInstance is now available, and any logging is forwarded from\n// MSAL to the app.log instance.\n```\n\n#### Providing a custom MSAL configuration\n\nMSAL offers a rich set of configuration options, and you can provide your own configuration as an app option.\n\n```typescript\nimport * as msal from '@azure/msal-browser';\nimport { App } from '@microsoft/teams.client';\n\nconst configuration: msal.Configuration = {\n  /* custom MSAL configuration options */\n};\n\nconst app = new App(clientId, { msalOptions: { configuration } });\n\nawait app.start();\n```\n\n#### Providing a pre-configured MSAL IPublicClientApplication\n\nMSAL cautions against an app using multiple IPublicClientApp instances at the same time. If you're already using MSAL, you can provide a pre-created MSAL instance to use as an app option.\n\n```typescript\nimport * as msal from '@azure/msal-browser';\nimport { App } from '@microsoft/teams.client';\n\nconst msalInstance =\n  await msal.createNestablePublicClientApplication(/* custom MSAL configuration */);\nawait msalInstance.initialize();\n\nconst app = new App(clientId, { msalOptions: { msalInstance } });\n\nawait app.start();\n```\n\nIf you need multiple app instances in order to call functions in several agents, you can re-use the MSAL instance from one as you construct another.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\n// let app1 create & initialize an MSAL IPublicClientApplication\nconst app1 = new App(clientId, {\n  remoteApiOptions: {\n    baseUrl: 'https://agent1.contoso.com',\n    remoteAppResource: 'api://agent1AppClientId',\n  },\n});\nawait app1.start();\n\n// let app2 re-use the MSAL IPublicClientApplication from app1\nconst app2 = new App(clientId, {\n  remoteApiOptions: {\n    baseUrl: 'https://agent2.contoso.com',\n    remoteAppResource: 'api://agent2AppClientId',\n  },\n  msalOptions: { msalInstance: app1.msalInstance },\n});\n```\n\n### Scope consent pre-warming\n\nThe MSAL options let you control whether and how the user is prompted to give the app permission for any necessary scope as the app starts. This option can be used to reduce the number of consent prompts the user sees while using the app, and to help make sure the app gets consent for the resource it needs to function.\n\nWith this option, you can either pre-warm a specific set of scopes or disable pre-warming altogether. If no setting is provided, the default behavior is to prompt the user for the Graph scopes listed in the app manifest, unless they've already consented to at least on Graph scope.\n\nFor more details on how and when to prompt for scope consent, see the [Graph](./graph) documentation.\n\n#### Default behavior\n\nIf the app is started without specifying any option to control scope pre-warming, the `.default` scope is pre-warmed. This means that in a first-run experience, the user would be prompted to consent for all Graph permissions listed in the app manifest. However, if the user has consented to at least one Graph permission, any one at all, no prompt appears.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId);\n\n// if the user hasn't already given consent for any scope at\n// all, this will prompt them\nawait app.start();\n```\n\n:::info\nThe user can decline the prompt and the app will still continue to run. However, the user will again be prompted next time they launch the app.\n:::\n\n#### Pre-warm a specific set of scopes\n\nIf your app requires a specific set of scopes in order to run well, you can list those in the set of scopes to pre-warm.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId, {\n  msalOptions: { prewarmScopes: ['User.Read', 'Chat.ReadBasic'] },\n});\n\n// if the user hasn't already given consent for each listed scope,\n// this will prompt them\nawait app.start();\n```\n\n:::info\nThe user can decline the prompt and the app will still continue to run. However, the user will again be prompted next time they launch the app.\n:::\n\n#### Disabling pre-warming\n\nScope pre-warming can be disabled if needed. This is useful if your app doesn't use graph APIs, or if you want more control over the consent prompt.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App(clientId, {\n  msalOptions: { prewarmScopes: false },\n});\n\n// this will not raise any consent prompt\nawait app.start();\n\n// this will prompt for the '.default' scope if the user hasn't already\n// consented to any scope\nconst top10Chats = await app.graph.call(endpoints.chats.list, { $top: 10 });\n```\n\n:::info\nEven if pre-warming is disabled and the user is not prompted to consent, a prompt for the `.default` scope will appear when invoking any graph API.\n:::\n\n## References\n\n[MSAL Configuration](https://learn.microsoft.com/en-us/entra/identity-platform/msal-client-application-configuration)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/functions/README.mdx",
    "content": "---\nsidebar_position: 5\ntitle: 'Functions'\nsummary: Details on how to register REST endpoints that can be called from Tab apps.\nlanguages: ['typescript', 'csharp']\n---\n\n# Functions\n\n<LanguageInclude section=\"overview\" />\n\n<LanguageInclude section=\"example\" />\n\n<LanguageInclude section=\"validation-warning\" />\n\n<LanguageInclude section=\"return-values\" />\n\n## Function context\n\n<LanguageInclude section=\"context-intro\" />\n\n<LanguageInclude section=\"context-table\" />\n\n<LanguageInclude section=\"context-validation\" />\n\n<LanguageInclude section=\"context-helpers\" />\n\n## Additional resources\n\n<LanguageInclude section=\"additional-resources\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/functions/function-calling.mdx",
    "content": "---\nsidebar_position: 1\ntitle: 'Executing Functions'\nsummary: Call remote agent functions from tab apps with authentication and custom headers using the exec() method.\nlanguages: ['typescript']\nsuppressLanguageIncludeWarning: true\n---\n\n# Executing Functions\n\nThe client App exposes an `exec()` method that can be used to call functions implemented in an agent created with this SDK. The function call uses the `app.http` client to make a request, attaching a bearer token created from the `app.msalInstance` MSAL public client application, so that the remote function can authenticate and authorize the caller.\n\nThe `exec()` method supports passing arguments and provides options to attach custom request headers and/or controlling the MSAL token scope.\n\n## Invoking a remote function\n\nWhen the tab app and the remote agent are deployed to the same location and in the same AAD app, it's simple to construct the client app and call the function.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId);\nawait app.start();\n\n// this requests a token for 'api://<clientId>/access_as_user' and attaches\n// that to an HTTP POST request to /api/functions/my-function\nconst result = await app.exec<string>('my-function');\n```\n\nIf the deployment is more complex, the [AppOptions](../app-options) can be used to influence the URL as well as the scope in the token.\n\n## Function arguments\n\nAny argument for the remote function can be provided as an object.\n\n```typescript\nconst args = { arg1: 'value1', arg2: 'value2' };\nconst result = await app.exec('my-function', args);\n```\n\n## Request headers\n\nBy default, the HTTP request will include a header with a bearer token as well as headers that give contextual information about the state of the app, such as which channel or team or chat or meeting the tab is active in.\n\nIf needed, you can add additional headers to the `requestHeaders` option field. This may be handy to provide additional context to the remote function, such as a logging correlation ID.\n\n```typescript\nconst requestHeaders = {\n  'x-custom-correlation-id': 'aaaa0000-bb11-2222-33cc-444444dddddd',\n};\n\n// custom headers when the function does not take arguments\nconst result = await app.exec('my-function', undefined, { requestHeaders });\n\n// custom headers when the function takes arguments\nconst args = { arg1: 'value1', arg2: 'value2' };\nconst result = await app.exec('my-other-function', args, { requestHeaders });\n```\n\n## Request bearer token\n\nBy default, the HTTP request will include a header with a bearer token acquired by requesting an `access_as_user` permission. The resource used for the request depends on the `remoteApiOptions.remoteAppResource` [AppOption](../app-options). If this app option is not provided, the token is requested for the scope `api://<clientId>/access_as_user`. If this option is provided, the token is requested for the scope `<remoteApiOptions.remoteAppResource>/access_as_user`.\n\nWhen calling a function that requires a different permission or scope, the `exec` options let you override the behavior.\n\nTo specify a custom permission, set the permission field in the `exec` options.\n\n```typescript\n// with this option, the exec() call will request a token for either\n// api://<clientId>/my_custom_permission or\n// <remoteApiOptions.remoteAppResource>/my_custom_permission,\n// depending on the app options used.\nconst options = {\n  permission: 'my_custom_permission',\n};\n\n// custom permission when the function does not take arguments\nconst result = await app.exec('my-function', undefined, options);\n\n// custom permission when the function takes arguments\nconst args = { arg1: 'value1', arg2: 'value2' };\nconst result = await app.exec('my-other-function', args, options);\n```\n\nSometimes you may need even more control. You might for need a scope for a different resource than your default when calling a particular remote agent function. In these cases you can provide the exact token request object you need as part of the `exec` options.\n\n```typescript\n// with this option, the exec() call will request a token for exactly\n// api://my-custom-resources/my_custom_scope, regardless of which app\n// options were used to construct the app.\nconst options = {\n  msalTokenRequest: {\n    scopes: ['api://my-custom-resources/my_custom_scope'],\n  },\n};\n\n// custom token request when the function does not take arguments\nconst result = await app.exec('my-function', undefined, options);\n\n// custom token request when the function takes arguments\nconst args = { arg1: 'value1', arg2: 'value2' };\nconst result = await app.exec('my-other-function', args, options);\n```\n\n## Ensuring user consent\n\nThe `exec()` function supports incremental, just-in-time consent such that the user is prompted to consent during the `exec()` call, if they haven't already consented earlier.\n\nIf you find that you'd rather test for consent or request consent before making the `exec()` call, the `hasConsentForScopes` and `ensureConsentForScopes` can be used. More details about those are given in the [Graph](../graph) section.\n\n## References\n\n- [Graph API overview](https://learn.microsoft.com/en-us/graph/api/overview)\n- [Graph API permissions overview](https://learn.microsoft.com/en-us/graph/permissions-reference)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/getting-started.mdx",
    "content": "---\nsidebar_position: 1\ntitle: 'Getting Started'\nsummary: Set up new tab app projects or add Teams client capabilities to existing tab applications.\nlanguages: ['typescript']\nsuppressLanguageIncludeWarning: true\n---\n\n# Getting started\n\nTo use this package, you can either set up a new project using the Teams CLI, or add it to an existing tab app project.\n\n## Setting up a new project\n\nThe Teams CLI contains a Microsoft 365 Agents Toolkit configuration and a template to easily scaffold a new tab app with a callable remote function. To set this up, first install the Teams CLI as outlined in the [Quickstart](../../getting-started/quickstart) guide. Then, create the app by running:\n\n```sh\nnpx @microsoft/teams.cli@latest new typescript my-first-tab-app --atk embed --template tab\n```\n\nWhen the app is created, you can use the Agents Toolkit to run and debug it inside of Teams from your local machine, same as for any other Agents Toolkit tab app.\n\n## Adding to an existing project\n\nThis package is set up to integrate well with existing Tab apps. The main consideration is that the AAD app must be configured to support Nested App Authentication (NAA). Otherwise it will not be possible to acquire the bearer token needed to call Microsoft Graph APIs or remote agent functions.\n\nAfter verifying that the app is configured for NAA, simply use your package manager to add a dependency on `@microsoft/teams.client` and then proceed with [Starting the app](./using-the-app).\n\nIf you're already using a current version of TeamsJS, that's fine. This package works well with TeamsJS.\n\nIf you're already using Microsoft Authentication Library (MSAL) in an NAA enabled app, that's great! The [App options](./app-options) page shows how you can use a single common MSAL instance.\n\n## Resources\n\n- [Running and debugging local apps in Agents Toolkit](https://learn.microsoft.com/en-us/microsoftteams/platform/toolkit/debug-local?tabs=Windows)\n- [Configuring an app for Nested App Authentication](https://learn.microsoft.com/en-us/microsoftteams/platform/concepts/authentication/nested-authentication#configure-naa)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/graph.mdx",
    "content": "---\nsidebar_position: 4\ntitle: 'Microsoft Graph'\nsummary: Access Microsoft Graph APIs with type-safe client and manage user consent for permissions.\nlanguages: ['typescript']\nsuppressLanguageIncludeWarning: true\n---\n\n# Microsoft Graph Client\n\nThe client App exposes a `graph` property that gives type-safe access to Microsoft Graph functions. When graph functions are invoked, the app attaches an MSAL bearer token to the request so that the call can be authenticated and authorized.\n\n## Invoking Graph functions\n\nAfter constructing and starting an App instance, you can invoke any graph function by using the `app.graph` client.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App(clientId);\nawait app.start();\n\nconst top10Chats = await app.graph.call(endpoints.chats.list, { $top: 10 });\n```\n\nFor best result, it's wise to ensure that the user has consented to a permission required by the graph API before attempting to invoke it. Otherwise, the call is likely to be rejected by the graph server.\n\n## Graph APIs and permissions\n\nDifferent graph APIs have different permission requirements. The app developer should make sure that consent is granted before invoking a graph API. To help request and test for consent, the client App offers three methods:\n\n- Pre-warming while starting the app.\n- Requesting consent if not already granted.\n- Testing for consent without prompting.\n\n### Pre-warming while starting the app\n\nThe App constructor takes an option that lets you control how scope consent is requested while starting the app. For more details on this option, see the [App options](./app-options) documentation.\n\n### Requesting consent if not already granted\n\nThe app provides an `ensureConsentForScopes` method that tests if the user has consented to a certain set of scopes and prompts them if consent isn't yet granted.\n\nThe method returns a promise that resolves to true if the user has already provided consent to all listed scopes; and to false if the user declines the prompt.\n\nThis method is useful for building an incremental, just-in-time, consent model, or to fully control how consent is pre-warmed.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App(clientId, {\n  msalOptions: { prewarmScopes: ['User.Read'] },\n});\n\n// this will prompt for the User.Read scope if not already granted\nawait app.start();\n\n// this will prompt for Chat.ReadBasic if not already granted\nconst canReadChat = await app.ensureConsentForScopes(['Chat.ReadBasic']);\n\nif (canReadChat) {\n  const top10Chats = await app.graph.call(endpoints.chats.list, { $top: 10 });\n  // ... do something useful ...\n}\n```\n\n#### Testing for consent without prompting\n\nThe app also provides a `hasConsentForScopes` method to test for consent without raising a prompt. This is handy to enable or disable features based on user choice, or to provide friendly messaging before raising a prompt with `ensureConsentForScopes`.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App(clientId);\n\n// this will prompt for the '.default' scope if the user hasn't already\n// consented to any scope\nawait app.start();\n\n// this will not raise a prompt under any circumstance\nconst canReadChat = await app.hasConsentForScopes(['Chat.ReadBasic']);\n\nif (canReadChat) {\n  const top10Chats = await app.graph.call(endpoints.chats.list, { $top: 10 });\n  // ... do something useful ...\n}\n```\n\n## References\n\n- [Graph API overview](https://learn.microsoft.com/en-us/graph/api/overview)\n- [Graph API permissions overview](https://learn.microsoft.com/en-us/graph/permissions-reference)\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/tabs/using-the-app.mdx",
    "content": "---\nsidebar_position: 2\ntitle: 'Using the App'\nsummary: Initialize and use the Teams client App to call Graph APIs and remote agent functions.\nlanguages: ['typescript']\nsuppressLanguageIncludeWarning: true\n---\n\n# Using The App\n\nThe `@microsoft/teams.client` App class helps solve common challenges when building Single Page Applications hosted in Microsoft Teams, Outlook, and Microsoft 365. It is the client-side counterpart to the `@microsoft/teams.app` App that you can use to build AI agents.\n\nThese two App classes are designed to work well together. For instance, when you use the `@microsoft/teams.app` App to expose a server-side function, you can then use the `@microsoft/teams.client` App `exec` method to easily invoke that function, as the client-side app knows how to construct an HTTP request that the server-side app can process. It can issue a request to the right URL, with the expected payload and contextual headers. The client-side app even includes a bearer token that the server side app uses to authenticate the caller.\n\n# Starting the app\n\nTo use the `@microsoft/teams.client` package, you first create an App instance and then call `app.start()`.\n\n```typescript\nimport { App } from '@microsoft/teams.client';\n\nconst app = new App(clientId);\nawait app.start();\n```\n\nThe app constructor strives to make it easy to get started on a new app, while still being flexible enough that it can integrate easily with existing apps.\n\nThe constructor takes two arguments: a required app client ID, and an optional `AppOptions` argument. The app client ID is the AAD app registration **Application (client) ID**. The options can be used to customize observability, Microsoft Authentication Library (MSAL) configuration, and\nremote agent function calling.\n\nFor more details on the app options, see the [App options](./app-options) page.\n\n## What happens during start\n\nThe app constructor does the following:\n\n- it creates an app logger, if none is provided in the app options.\n- it creates an http client used to call the remote agent.\n- it creates a graph client that can be used as soon as the app is started.\n\nThe `app.start()` call does the following:\n\n- it initializes TeamsJS.\n- it creates an MSAL instance, if none is provided in the app options.\n- it connects the MSAL instance to the graph client.\n- it prompts the user for MSAL token consent, if needed and if pre-warming is not disabled through the app options.\n\n## Using the app\n\nWhen the `app.start()` call has completed, you can use the app instance to call Graph APIs and to call remote agent functions using the `exec()` function, or directly by using the `app.http` HTTP client. TeamsJS is now initialized, so you can interact with the hosting app. The `app.msalInstance` is now populated, in case you need to use the same MSAL for other purposes.\n\n```typescript\nimport * as teamsJs from '@microsoft/teams-js';\nimport { App } from '@microsoft/teams.client';\nimport * as endpoints from '@microsoft/teams.graph-endpoints';\n\nconst app = new App(clientId);\nawait app.start();\n\n// you can now get the TeamsJS context...\nconst context = await teamsJs.app.getContext();\n\n// ...call Graph end points...\nconst presenceResult = await app.graph.call(endpoints.me.presence.get);\n\n// ...and call remote agent functions...\nconst agentResult = await app.exec<string>('hello-world');\n```\n"
  },
  {
    "path": "teams.md/src/pages/templates/in-depth-guides/user-authentication.mdx",
    "content": "---\nsidebar_position: 4\nsidebar_label: 🔒 User Authentication\ntitle: 🔒 User Authentication\nsummary: API guide to implement User Authentication with SSO in Teams Apps.\n---\n\n# 🔒 User Authentication\n\nAt times agents must access secured online resources on behalf of the user, such as checking email, checking on flight status, or placing an order. To enable this, the user must authenticate their identity and grant consent for the application to access these resources. This process results in the application receiving a token, which the application can then use to access the permitted resources on the user's behalf.\n\n:::info\nThis is an advanced guide. It is highly recommended that you are familiar with [Teams Core Concepts](/teams/core-concepts) before attempting this guide.\n:::\n\n:::warning\nUser authentication does not work with the developer tools setup. You have to run the app in Teams. Follow these [instructions](/typescript/getting-started/running-in-teams#debugging-in-teams) to run your app in Teams.\n:::\n\n:::info\nIt is possible to authenticate the user into [other auth providers](https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-concept-identity-providers?view=azure-bot-service-4.0&tabs=adv2%2Cga2#other-identity-providers) like Facebook, Github, Google, Dropbox, and so on.\n:::\n\nOnce you have configured your Azure Bot resource OAuth settings, as described in the [official documentation](https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-concept-authentication?view=azure-bot-service-4.0), add the following code to your `App`:\n\n## Project Setup\n\n### Create an app with the `graph` template\n\n:::tip\nSkip this step if you want to add the auth configurations to an existing app.\n:::\n\n<LanguageInclude section=\"create-project\" />\n\n### Add Agents Toolkit auth configuration\n\nOpen your terminal with the project folder set as the current working directory and run the following command:\n\n```sh\nnpx @microsoft/teams.cli config add atk.oauth\n```\n\nThe `atk.oauth` configuration is a basic setup for Agents Toolkit along with configurations to authenticate the user with Microsoft Entra ID to access Microsoft Graph APIs.\n\nThis [CLI](/developer-tools/cli) command adds configuration files required by Agents Toolkit, including:\n\n- Azure Application Entra ID manifest file `aad.manifest.json`.\n- Azure bicep files to provision Azure bot in `infra/` folder.\n\n:::info\nAgents Toolkit, in the debugging flow, will deploy the `aad.manifest.json` and `infra/azure.local.bicep` file to provision the Application Entra ID and Azure bot with oauth configurations.\n:::\n\n## Configure the OAuth connection\n\n<LanguageInclude section=\"configure-oauth\" />\n\n:::tip\nMake sure you use the same name you used when creating the OAuth connection in the Azure Bot Service resource.\n:::\n\n:::note\nIn many templates, `graph` is the default name of the OAuth connection, but you can change that by supplying a different connection name in your app configuration.\n:::\n\n## Signing In\n\n:::note\nThis uses the Single Sign-On (SSO) authentication flow. To learn more about all the available flows and their differences see the [official documentation](https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-concept-authentication?view=azure-bot-service-4.0).\n:::\n\nYou must call the `signin` method inside your route handler, for example: to signin when receiving the `/signin` message:\n\n<LanguageInclude section=\"signing-in\" />\n\n## Subscribe to the SignIn event\n\nYou can subscribe to the `signin` event, that will be triggered once the OAuth flow completes.\n\n<LanguageInclude section=\"signin-event\" />\n\n## Start using the graph client\n\nFrom this point, you can use the `IsSignedIn` flag and the `userGraph` client to query graph, for example to reply to the `/whoami` message, or in any other route.\n\n:::note\nThe default OAuth configuration requests the `User.ReadBasic.All` permission. It is possible to request other permissions by modifying the App Registration for the bot on Azure.\n:::\n\n<LanguageInclude section=\"using-graph\" />\n\n## Signing Out\n\nYou can signout by calling the `signout` method, this will remove the token from the User Token service cache\n\n<LanguageInclude section=\"signing-out\" />\n\n## Handling Sign-In Failures\n\nWhen using SSO, if the token exchange fails Teams sends a `signin/failure` invoke activity to your app. The SDK includes a built-in default handler that logs a warning with actionable troubleshooting guidance. You can optionally register your own handler to customize the behavior:\n\n<LanguageInclude section=\"signin-failure\" />\n\n:::tip\nThe most common failure codes are `installedappnotfound` (bot app not installed for the user) and `resourcematchfailed` (Token Exchange URL doesn't match the Application ID URI). See [SSO Setup - Troubleshooting](/teams/user-authentication/sso-setup#troubleshooting-sso) for a full list of failure codes and troubleshooting steps.\n:::\n\n<LanguageInclude section=\"regional-bot\" />\n\n## Resources\n\n[User Authentication Basics](https://learn.microsoft.com/en-us/azure/bot-service/bot-builder-concept-authentication?view=azure-bot-service-4.0)\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/README.mdx",
    "content": "---\nsidebar_position: 4\ntitle: Migrations\nlanguages: ['typescript', 'python']\nsummary: Migration guides for transitioning from older versions and frameworks to Teams SDK.\nllms: ignore\nsuppressLanguageIncludeWarning: true\n---\n\n# Migrations\n\nMigration guides for transitioning from older versions and frameworks to Teams SDK.\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/_category_.json",
    "content": "{\n  \"collapsed\": true,\n  \"label\": \"Migrations\"\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/README.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: From BotBuilder\ntitle: From BotBuilder\nlanguages: ['typescript', 'csharp', 'python']\nsummary: Migration guide from BotBuilder to Teams SDK, including the BotBuilder plugin for compatibility with existing activity handlers and adapters.\nllms: ignore\n---\n\n# From BotBuilder\n\nThis new iteration of Teams SDK has been rebuilt from the ground up. \nTo ease the migration process, we've introduced a plugin <LanguageInclude content={{\"typescript\": \"`@microsoft/teams.botbuilder`\", \"csharp\": \"`Microsoft.Teams.Plugins.AspNetCore.BotBuilder`\", \"python\": \"microsoft-teams-botbuilder\"}} /> that\nallows you to continue using BotBuilder components like `ActivityHandler` and `CloudAdapter`\nto receive, process and send activities within the new Teams SDK abstractions.\n\n## Why a Plugin?\n\nThe plugin exists to bridge BotBuilder and the new Teams SDK,\nletting developers keep their existing BotBuilder activity handlers while gradually moving to the new Teams SDK App handlers.\nIt enables incremental migration and smooth adoption of new SDK features.\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/_category_.json",
    "content": "{\n  \"label\": \"BotBuilder Migration\",\n  \"position\": 1,\n  \"collapsed\": true\n}\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/integration.mdx",
    "content": "---\nsidebar_position: 1\nlanguages: ['typescript', 'csharp', 'python']\ntitle: Using the BotBuilder Plugin\nsummary: How to migrate BotBuilder adapters to Teams SDK plugins for handling bot communication and middleware.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Using the BotBuilder Plugin\n\n# Adapters\nA BotBuilder `CloudAdapter` is responsible for managing communication between a bot and its users.\nIt serves as the entry point for incoming activities and forwards them to the registered `ActivityHandler` for processing. \nYou can customize the adapter to add middleware for logging, authentication, and define error handling.\n\nThe `BotBuilderPlugin` provided within the Teams SDK, connects the SDK with the BotBuilder framework.\nIt can either use an existing `CloudAdapter` or create a new default one, allowing activities to be processed through BotBuilder\nwhile still handling events via the Teams SDK App framework.\n\n# Activity Handlers\nThe BotBuilder `ActivityHandler` contains the actual bot logic for processing messages or events\nsimilar to how the Teams SDK `App` routes messages and events. You can override any number of methods,\nsuch as <LanguageInclude content={{\"typescript\": \"`OnMembersAdded`\", \"csharp\": \"`OnMembersAddedAsync`\", \"python\": \"`on_members_added_activity`\"}} />\nor <LanguageInclude content={{\"typescript\": \"`onMessage`\", \"csharp\": \"`OnMessageActivityAsync`\", \"python\": \"`on_message_activity`\"}} /> ,\nto handle different activity types.\n\n# Turn Context\nEach incoming activity is wrapped in a `TurnContext`, which represents the context of a single turn in the conversation.\nTurnContext provides access to:\n- The incoming activity (message, event).\n- Services for sending responses back to the user.\n- Conversation, user, and channel metadata.\n\nTeams SDK has <LanguageInclude content={{\"typescript\": \"`IActivityContext`\", \"csharp\": \"`IActivityContext`\", \"python\": \"`ActivityContext`\"}} /> for the same purpose.\n\n# How it all comes together\n\nThe `CloudAdapter` creates the `TurnContext`, and the `ActivityHandler` uses it to read the activity and send responses.\n\nWith the `BotBuilderPlugin`, when a message or activity is received:\n1. The BotBuilder ActivityHandler runs first, handling the activity according to standard Bot Framework logic.\n2. The Teams SDK app based activity handlers execute afterward, allowing Teams SDK logic to execute.\n\n:::info\nThis snippet shows how to use the `BotBuilderPlugin` to send and receive activities using botbuilder instead of the default Teams SDK http plugin.\n:::\n\n<LanguageInclude section=\"example\" />\n\nIn this example:\n- <LanguageInclude content={{\"typescript\": \"`adapter.ts`\", \"csharp\": \"`BotBuilderAdapter.cs`\", \"python\": \"`adapter.py`\"}} /> defines a `CloudAdapter` to\nhandle incoming activities, and can include middleware support or error handling.\n- <LanguageInclude content={{\"typescript\": \"`activity-handler.ts`\", \"csharp\": \"`Bot.cs`\", \"python\": \"`activity_handler.py`\"}} /> defines the `ActivityHandler` and contains the core bot logic,\nhandling incoming messages and sending responses via the `TurnContext`.\n- <LanguageInclude content={{\"typescript\": \"`index.ts`\", \"csharp\": \"`Program.cs`\", \"python\": \"`app.py`\"}} /> sets up a Teams SDK `app`\nand registers the `BotBuilderPlugin` with your adapter and activity handler. It also defines a native Teams SDK activity handler that responds to messages.\n\nIn the ouptut below, \nThe first line comes from the BotBuilder ActivityHandler. The second line comes from the Teams SDK message activity handler.\nThis shows that both handlers can process the same message sequentially when using the BotBuilder Plugin.\nThis strategy can now be used to incrementally migrate from BotBuilder to the Teams SDK.\n\n```\nhi from botbuilder...\nhi from teams...\n```"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/proactive-activities.mdx",
    "content": "---\nsidebar_position: 2\nsidebar_label: Proactive Activities\ntitle: Proactive Activities\nlanguages: ['typescript', 'csharp', 'python']\nsummary: Migrate from BotBuilder's complex conversation reference handling to Teams SDK's simple conversation ID-based proactive messaging.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Proactive Activities\n\nThe BotBuilder proactive message flow requires storing a conversation reference. \nIn Teams SDK, we expose a <LanguageInclude content={{\"typescript\": \"`send`\", \"csharp\": \"`SendAsync`\", \"python\": \"`send`\"}} /> method in the `App` class, almost identical to the one \npassed into our activity handlers through our context. This method accepts a <LanguageInclude content={{\"typescript\": \"`conversationId`\", \"csharp\": \"`conversationId`\", \"python\": \"`conversation_id`\"}} />, so storing just that is enough!\n\n<LanguageInclude section=\"example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/sending-activities.mdx",
    "content": "---\nsidebar_position: 1\nsidebar_label: Sending Activities\ntitle: Sending Activities\nlanguages: ['typescript', 'csharp', 'python']\nsummary: Migrate from BotBuilder's TurnContext activity sending to Teams SDK's simplified send method with better Adaptive Card support.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Sending Activities\n\nBotBuilder's pattern for sending activities via its `TurnContext` is similar to that in Teams SDK,\nbut one key difference is that sending adaptive cards doesn't require constructing the entire activity yourself.\n\n<LanguageInclude section=\"examples\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/the-api-client.mdx",
    "content": "---\nsidebar_position: 3\nsidebar_label: The API Client\ntitle: The API Client\nlanguages: ['typescript', 'csharp', 'python']\nsummary: Replace BotBuilder's static TeamsInfo class with Teams SDK's injected ApiClient for cleaner API interactions.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# The API Client\n\nBotBuilder exposes a static class `TeamsInfo` that allows you to query the api. In Teams SDK\nwe pass an instance of our `ApiClient` into all our activity handlers through the context.\n\n:::tip\nThe Teams SDK `ApiClient` uses a fluent API pattern that makes it easier to discover available methods through IDE autocompletion.\n:::\n\n<LanguageInclude section=\"example\" />\n\n## Mapping TeamsInfo APIs to Teams SDK ApiClient Methods\n\nThe following table shows common BotBuilder `TeamsInfo` methods and their equivalent Teams SDK `ApiClient` methods:\n\n<LanguageInclude section=\"api-table\" />"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/botbuilder/user-authentication.mdx",
    "content": "---\nsidebar_position: 4\nsidebar_label: User Authentication\ntitle: User Authentication\nlanguages: ['typescript', 'csharp', 'python']\nsummary: Migrate from BotBuilder's complex OAuthPrompt dialogs to Teams SDK's simple signin/signout methods.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# User Authentication\n\nBotBuilder uses its `dialogs` for authentication via the `OAuthPrompt`. Teams SDK doesn't have any\nequivalent feature for dialogs, but we do support auth flows in our own way via our <LanguageInclude content={{\"typescript\": \"`signin` and `signout`\", \"csharp\": \"`SignIn` and `SignOut`\", \"python\": \"`sign_in` and `sign_out`\"}} /> methods.\n\n<LanguageInclude section=\"example\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/slack-bolt.mdx",
    "content": "---\nsidebar_position: 2\ntitle: Porting your Slack Bolt bot\nlanguages: ['typescript']\nsummary: Migration & port guide from Slack Bolt to Teams SDK, highlighting the key changes and upgrade steps.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Porting your Slack Bolt bot\n\nThis guide will help you migrate or port your existing Slack Bolt application to the Teams SDK. We'll cover the key conceptual similarities and differences between Slack and Teams APIs, and provide code examples to help you port things over in no time. Let's get started!\n\n## Introduction\n\nSimilar to Slack Bolt, the Teams SDK is designed to interface with the Teams backend APIs for building conversational applications. Both SDKs provide abstractions for handling incoming events, messages, and interactions, as well as sending responses back to users. If you already have a Slack bot, the concepts should feel familiar.\n\nHowever, there are some key differences, such as with app installation. In Slack, apps are installed to workspaces via OAuth, whereas in Teams they are installed via the Teams App Store. Additionally, Teams apps can be installed personally by individual users, or to collaborative scopes like group chats, channels, and meetings.\n\nLet's take a look at some similarities and differences between Slack and Teams concepts:\n\n| Concept      | Teams      | Slack      |\n| ------------- | ------------- | ------------- |\n| **Installation** | Installed via app store on per-scope basis | Installed via OAuth to Workspace |\n| **Quickstart** | New projects created via Agent Toolkit CLI, Visual Studio extension, or VS Code extension. | New projects created via Slack CLI. |\n| **App manifest** | Authored via JSON, Agent Toolkit, and/or via Teams Developer Portal. Must also setup Azure Bot and Azure App Registration resources, which can be done via Azure Portal or Agent Toolkit. | Authored via JSON, YAML, or app management page. |\n| **Messaging endpoint** | Set in Azure Bot resource. | Set in Slack app manifest. |\n| **App authentication** | Entra App Registration is authorized during Teams app installation. Teams SDK fetches Entra app token internally when sending messages. Core app features like messaging use this auth type internally within the SDK. | App stores Slack bot token after user authorizes application-delegated scopes (e.g.,`incoming-webhook,commands`). Core app features like messaging use this auth type internally within the SDK. |\n| **User authentication for REST APIs** | User Entra tokens can be obtained using Teams SSO. Graph REST APIs are integrated into the Teams SDK. Tokens are stored and refreshed by Azure Bot Token service. | User Slack tokens can be obtained using OAuth 2.0. Slack REST APIs are integrated into Slack Bolt. Tokens must be stored and refreshed by application. |\n| **Authentication with external services** | Obtain user access tokens for external services using OAuth 2.0. Tokens are stored and refreshed by Azure Bot Token service. | Users authenticate to external services using OAuth 2.0, perhaps initiated via account binding (see below). Tokens must be stored and refreshed by application. |\n| **Account linking** | `Activity` events include `Activity.from.id`, which is the user's AAD object ID. If you authenticate your external service with OAuth 2.0, these accounts are implicitly bound via the Azure Token Service, but you can also follow a similar flow as what Slack recommends. | Slack recommends following their [Binding accounts across services](https://docs.slack.dev/authentication/binding-accounts-across-services) guide. |\n| **Cards** | Rich UI elements in messages using Adaptive Cards. | Rich UI elements in messages using Block Kit. |\n| **Files** | Files can be attached or downloaded using SharePoint / OneDrive Graph APIs. | Files can be attached or downloaded via Slack's files APIs. |\n| **Targeted messages** | Teams does not currently support targeted messages. | Slack supports targeted ephemeral messages. |\n| **Slash commands** | Teams supports [slash commands](https://learn.microsoft.com/microsoftteams/platform/bots/how-to/create-a-bot-commands-menu) that are declared in the app manifest. Unlike Slack, commands are sent as messages and thus are visible to all users in the conversation. Listen for new commands using `app.message` handler, either via `app.message('/command')` or `app.message(regexp)`. | Slack Bolt has a dedicated `app.command` handler for commands in the Slack app manifest. Slash commands are not displayed to other users in collaborative contexts. |\n| **Workflows** | Teams Workflows is not integrated with the Teams SDK. To integrate with Teams Workflows, you must create a [custom Power Platform Connector](https://learn.microsoft.com/connectors/custom-connectors/). | Slack Workflows are integrated with Slack Bolt. |\n| **UI dialogs** | Adaptive Cards can include actions that open [UI dialogs](../in-depth-guides/dialogs) with an embedded website or another adaptive card. Dialogs must be opened via an adaptive card action and thus cannot be opened directly via a slash command. | BlockKit UI modals can be opened via slash commands (using `client.views.open`) or BlockKit actions. |\n| **AI strategy** | Teams has unique AI-native features for things like user feedback, AI-generated labels, prompt suggestions, streaming, and citations. We also feature an optional `ChatPrompt` class to simplify integrating LLMs into your bot. Leverage grounded search via the [Microsoft 365 Copilot Retrieval Graph API](https://learn.microsoft.com/microsoft-365-copilot/extensibility/api/ai-services/retrieval/overview). AI features are generally designed for use in any conversation type. | Slack has a dedicated `Assistant` class for AI interactions in a dedicated agent side panel view, which differs from Teams's strategy of using existing bot interaction patterns. Can still use AI in other conversation types using standard bot APIs. Can use Slack Data Access API for grounded search. |\n| **AI user feedback** | User feedback buttons are natively rendered in Teams with dedicated APIs for handling feedback. After user gives positive or negative feedback, a modal is opened where additional information (e.g., plain text response) can be captured. | Slack uses a dedicated `feedback_buttons` BlockKit element type and `app.action('feedback')` for attaching user feedback (positive vs. negative) to messages. |\n\n## Configuring your application\n\nFirst, setup a new Teams application, as shown in [Teams Integration](../../../../docs/main/teams/README.md).\n\n## Installing Teams SDK\n\n<LanguageInclude section=\"installation\" />\n\n## Configure application\n\n<LanguageInclude section=\"application\" />\n\n## Migrate message handlers\n\nIn Slack, there are message handlers for events with different subtypes (e.g., undefined subtype is a regular message, `event.subtype == 'file_share'` is a file share message, etc.). In Teams, there are different `Activity` handers for different types of events that are enumerated via the `ActivityTypes` enum (e.g., `app.activity(ActivityTypes.Message)`), with some `Activity` types having tailored APIs within the SDK (e.g., `app.message`). These concepts are roughly similar, though the naming conventions and syntax differ.\n\n### Message handlers\n\n<LanguageInclude section=\"message-handlers\" />\n\n## BlockKit -> Adaptive Cards\n\nTo include Rich UI in messages sent by your bot, Slack's Block Kit is equivalent to Teams's Adaptive Cards.\n\n<LanguageInclude section=\"adaptive-cards\" />\n\nLearn more in the [Adaptive Cards guide](../in-depth-guides/adaptive-cards).\n\n## User authentication\n\nThere are two primary types of user authentication for Teams and Slack: authentication for Slack & Graph REST APIs, and authentication for external services. Let's take a look at each of these in turn.\n\n### User-delegated REST APIs\n\nIn Slack, if you want to use Slack REST APIs that require user-delegated scopes, you need to implement an OAuth 2.0 installation flow in your application to obtain and store Slack user tokens, even if the app was already installed by another user. In Teams, you can leverage Teams SSO to obtain user Entra tokens for calling Graph REST APIs. The Teams SDK integrates with Teams SSO and Azure Bot Token Service to handle token acquisition, storage, and refresh automatically for you.\n\nFirst, follow the instructions in the [Teams SSO guide](../../../../docs/main/teams/user-authentication/sso-setup.mdx).\n\nThen, configure the authentication in your code.\n\n<LanguageInclude section=\"graph\" />\n\n### User authentication for external services\n\nIn Slack, you can access external services by implementing an account binding flow using OAuth 2.0 as documented [here](https://docs.slack.dev/authentication/binding-accounts-across-services). In Teams, you can access external services by implementing an OAuth 2.0 flow, with the Azure Bot Token Service handling token acquisition, storage, and refresh for you.\n\nFirst, setup your OAuth 2.0 connection settings in the [Azure Portal](https://portal.azure.com/) for your Azure Bot resource.\n\n![Screenshot showing Azure Bot custom OAuth connection settings.](/screenshots/abs-custom-oauth-connection.png)\n\nThen, add the authentication code to your application to get the relevant user token and call your external service.\n\n<LanguageInclude section=\"3p-auth\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/v1.mdx",
    "content": "---\nsidebar_position: 2\ntitle: Migrating from Teams SDK v1\nlanguages: ['typescript', 'python']\nsummary: Migration guide from Teams SDK v1 to v2 highlighting the key changes and upgrade steps.\n---\n\nimport Tabs from '@theme/Tabs';\nimport TabItem from '@theme/TabItem';\n\n# Migrating from Teams SDK v1\n\nWelcome, fellow agent developer! You've made it through a full major release of Teams SDK, and now you want to take the plunge into v2. In this guide, we'll walk you through everything you need to know, from migrating core features like message handlers and auth, to optional AI features like `ActionPlanner`. <LanguageInclude section=\"botbuilder-adapter-note\" />\n\n## Installing Teams SDK\n\n<LanguageInclude section=\"installation\" />\n\n## Migrate Application class\n\nFirst, migrate your `Application` class from v1 to the new `App` class.\n\n<LanguageInclude section=\"app-migration\" />\n\n## Migrate activity handlers\n\nBoth v1 and v2 are built atop incoming `Activity` requests, which trigger handlers in your code when specific type of activities are received. The syntax for how you register different types of `Activity` handlers differs <LanguageInclude section=\"activity-handlers-intro\" /> between the v1 and v2 versions of our SDK.\n\n### Message handlers\n\n<LanguageInclude section=\"message-handlers\" />\n\n### Task modules\n\n<LanguageInclude section=\"task-modules-note\" />\n\n<LanguageInclude section=\"task-modules\" />\n\nLearn more in the [Dialogs guide](../in-depth-guides/dialogs).\n\n## Adaptive cards\n\nIn Teams SDK v2, cards have much more rich type validation than existed in v1. However, assuming your cards were valid, it should be easy to migrate to v2.\n\n<LanguageInclude section=\"adaptive-cards\" />\n\nLearn more in the [Adaptive Cards guide](../in-depth-guides/adaptive-cards).\n\n## Authentication\n\nMost agents feature authentication for user identification, interacting with APIs, etc. Whether your Teams SDK app used Entra SSO or custom OAuth, porting to v2 should be simple.\n\n<LanguageInclude section=\"authentication\" />\n\n## AI\n\n<LanguageInclude section=\"ai-content\" />\n\n### Feedback\n\nIf you supported feedback for AI generated messages, migrating is simple.\n\n<LanguageInclude section=\"feedback\" />\n\nYou can learn more about feedback in Teams SDK in the [Feedback guide](../in-depth-guides/feedback).\n\n<LanguageInclude section=\"botbuilder-plugin\" />\n"
  },
  {
    "path": "teams.md/src/pages/templates/migrations/v2-previews.mdx",
    "content": "---\nsidebar_position: 3\nsidebar_label: From V2 Previews\ntitle: From V2 Previews\nlanguages: ['typescript']\n---\n\n# From V2 Previews\n\n<LanguageInclude section=\"content\" />\n"
  },
  {
    "path": "teams.md/src/pages/typescript.tsx",
    "content": "import { Redirect } from '@docusaurus/router';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\n\nexport default function Typescript() {\n  const baseUrl = useBaseUrl('/');\n  return <Redirect to={`${baseUrl}typescript/getting-started`} />;\n}\n"
  },
  {
    "path": "teams.md/src/scripts/scaffold.js",
    "content": "#!/usr/bin/env node\n\nconst fs = require('fs');\nconst path = require('path');\n\nfunction ensureDirSync(dir) {\n  if (!fs.existsSync(dir)) {\n    fs.mkdirSync(dir, { recursive: true });\n  }\n}\n\nfunction createFileIfNotExists(filePath, content = '') {\n  if (!fs.existsSync(filePath)) {\n    fs.writeFileSync(filePath, content);\n  }\n}\n\n\nfunction scaffold(userInput) {\n  // If path starts with src/ but is not a valid base, fail\n  if (/^src\\//.test(userInput) &&\n      !/^src\\/pages\\/templates\\//.test(userInput) &&\n      !/^src\\/components\\/include\\//.test(userInput)) {\n    console.error('Error: Path is outside of allowed base directories.');\n    process.exit(1);\n  }\n\n  // Normalize input\n  let relPath = userInput.replace(/^src\\/(pages\\/templates|components\\/include)\\/?/, '').replace(/^[/.]+/, '');\n  let isTemplates = false;\n  let isInclude = false;\n  // Detect if user input is for templates or include\n  if (/^(src\\/)?pages\\/templates\\//.test(userInput) || userInput.startsWith('templates/')) {\n    isTemplates = true;\n  } else if (/^(src\\/)?components\\/include\\//.test(userInput) || userInput.startsWith('include/')) {\n    isInclude = true;\n  }\n\n  // If user gave a full path, strip to relative\n  if (isTemplates) {\n    relPath = userInput.replace(/^(src\\/)?pages\\/templates\\/?/, '');\n    // Fail if userInput is not under templatesBase\n    const templatesBase = path.resolve(__dirname, '../pages/templates');\n    const absTarget = path.resolve(templatesBase, relPath);\n    if (!absTarget.startsWith(templatesBase)) {\n      console.error('Error: Path is outside of templates base directory.');\n      process.exit(1);\n    }\n  } else if (isInclude) {\n    relPath = userInput.replace(/^(src\\/)?components\\/include\\/?/, '');\n    // Fail if userInput is not under includeBase\n    const includeBase = path.resolve(__dirname, '../components/include');\n    const absTarget = path.resolve(includeBase, relPath);\n    if (!absTarget.startsWith(includeBase)) {\n      console.error('Error: Path is outside of include base directory.');\n      process.exit(1);\n    }\n  }\n\n  // Helper to walk and create files in every directory\n  function walkAndCreate(base, relPath, filesFn) {\n    const parts = relPath.split('/').filter(Boolean);\n    let curr = base;\n    for (const part of parts) {\n      curr = path.join(curr, part);\n      ensureDirSync(curr);\n      filesFn(curr);\n    }\n    return curr;\n  }\n\n  // If ambiguous, create both\n  if (!isTemplates && !isInclude) {\n    const templatesBase = path.join(__dirname, '../pages/templates');\n    const includeBase = path.join(__dirname, '../components/include');\n    const templatesTarget = walkAndCreate(templatesBase, relPath, (dir) => {\n      createFileIfNotExists(path.join(dir, '_category.json'), '{\\n  \"label\": \"New Category\"\\n}\\n');\n      createFileIfNotExists(path.join(dir, 'README.mdx'), '# New Template\\n');\n    });\n    const includeTarget = walkAndCreate(includeBase, relPath, (dir) => {\n      createFileIfNotExists(path.join(dir, 'typescript.incl.md'), '# Typescript Include\\n');\n    });\n    return {templatesTarget, includeTarget};\n  }\n  if (isTemplates) {\n    const templatesBase = path.join(__dirname, '../pages/templates');\n    const templatesTarget = walkAndCreate(templatesBase, relPath, (dir) => {\n      createFileIfNotExists(path.join(dir, '_category.json'), '{\\n  \"label\": \"New Category\"\\n}\\n');\n      createFileIfNotExists(path.join(dir, 'README.mdx'), '# New Template\\n');\n    });\n    return {templatesTarget};\n  }\n  if (isInclude) {\n    const includeBase = path.join(__dirname, '../components/include');\n    const includeTarget = walkAndCreate(includeBase, relPath, (dir) => {\n      createFileIfNotExists(path.join(dir, 'typescript.incl.md'), '# Typescript Include\\n');\n    });\n    return {includeTarget};\n  }\n}\n\nconst userPath = process.argv[2];\nfunction isValidPath(p) {\n  if (!p || typeof p !== 'string') return false;\n  if (p.startsWith('/') || p.includes('..') || p.trim() === '') return false;\n  return true;\n}\n\nif (!isValidPath(userPath)) {\n  console.error('Error: Invalid path. Use a relative path like \"templates/new/two\" or \"include/new/two\". Do not start with a slash or use \"..\".');\n  process.exit(1);\n}\n\nconst created = scaffold(userPath);\nif (created) {\n  if (created.templatesTarget && created.includeTarget) {\n    console.log(`Scaffolded:\\n  Template: ${created.templatesTarget}\\n  Include:  ${created.includeTarget}`);\n  } else if (created.templatesTarget) {\n    console.log(`Scaffolded Template: ${created.templatesTarget}`);\n  } else if (created.includeTarget) {\n    console.log(`Scaffolded Include: ${created.includeTarget}`);\n  }\n}\n"
  },
  {
    "path": "teams.md/src/theme/DocSidebarItems/index.tsx",
    "content": "import React, { useMemo, useRef } from 'react';\nimport DocSidebarItemsOriginal from '@theme-original/DocSidebarItems';\nimport type { Props } from '@theme/DocSidebarItems';\nimport { useLocation } from '@docusaurus/router';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\n\nimport { useLanguagePreference } from '../../hooks/useLanguagePreference';\nimport { getLanguageFromPathStrict } from '../../utils/languageUtils';\n\nfunction buildItemsSignature(items: Props['items']): string {\n  if (!Array.isArray(items)) return '';\n  return items\n    .map((item) => {\n      if ('href' in item && typeof item.href === 'string') return `L:${item.href}`;\n      if ('items' in item && Array.isArray(item.items)) {\n        const children = item.items;\n        // String of all child hrefs in a category\n        const childSig = children\n          .map((ch) => ('href' in ch && typeof ch.href === 'string' ? ch.href : '#'))\n          .join(',');\n        return `C:[${childSig}]`;\n      }\n      return 'OTHER';\n    })\n    .join('|');\n}\n\nexport default function DocSidebarItems(props: Props): React.JSX.Element {\n  const location = useLocation();\n  const baseUrl = useBaseUrl('/');\n  const { language: preferredLanguage } = useLanguagePreference();\n\n  const sidebarRef = useRef<Map<string, Props['items']>>(new Map());\n\n  // Use language from URL if present, otherwise fall back to user preference\n  const urlLanguage = getLanguageFromPathStrict(location.pathname, baseUrl);\n  const currentLanguage = urlLanguage || preferredLanguage;\n\n  // Filter to show /docs/main content + current language content only\n  const filteredItems = useMemo(() => {\n    if (sidebarRef.current.has(currentLanguage)) {\n      return sidebarRef.current.get(currentLanguage)!;\n    }\n\n    const items = Array.isArray(props.items) ? props.items : [];\n\n    // Cache language detection per href\n    const langCache = new Map<string, string | null>();\n\n    const getCachedLanguage = (href: string) => {\n      if (!langCache.has(href)) {\n        langCache.set(href, getLanguageFromPathStrict(href, baseUrl));\n      }\n      return langCache.get(href);\n    };\n\n    const result = items.filter((item) => {\n      // Sidebar item properties\n      const itemHref = 'href' in item ? item.href : undefined;\n\n      // For category items, check if they have children that would indicate language content\n      if ('items' in item && item.items && Array.isArray(item.items)) {\n        // Check if any child item has a language-specific href\n        let hasLanguageContent = false;\n        let firstLanguageMatch: string | null = null;\n\n        for (const child of item.items) {\n          const h = 'href' in child && typeof child.href === 'string' ? child.href : undefined;\n\n          if (!h) {\n            continue;\n          }\n          const l = getCachedLanguage(h);\n\n          if (l !== null) {\n            hasLanguageContent = true;\n\n            if (firstLanguageMatch === null) {\n              firstLanguageMatch = l;\n              break;\n            }\n          }\n        }\n\n        // If this category contains language-specific content, check if it matches current language\n        if (hasLanguageContent) {\n          return firstLanguageMatch === currentLanguage;\n        }\n      }\n\n      // Check if this item corresponds to a language directory by examining its href\n      if (itemHref && typeof itemHref === 'string') {\n        // Use explicit language detection that returns null if no language found\n        const itemLanguage = getCachedLanguage(itemHref);\n\n        // If this item links to a language directory, only show if it matches current language\n        if (itemLanguage !== null) {\n          return itemLanguage === currentLanguage;\n        }\n      }\n\n      // For anything else (individual docs, categories without language context), keep them all\n      return true;\n    });\n\n    sidebarRef.current.set(currentLanguage, result);\n\n    return result;\n  }, [buildItemsSignature(props.items), baseUrl, currentLanguage]);\n\n  // Pass filtered items to original component\n  return <DocSidebarItemsOriginal {...props} items={filteredItems} />;\n}\n"
  },
  {
    "path": "teams.md/src/theme/Navbar/Content/index.tsx",
    "content": "import React from 'react';\nimport { useThemeConfig } from '@docusaurus/theme-common';\nimport { splitNavbarItems, useNavbarMobileSidebar } from '@docusaurus/theme-common/internal';\nimport NavbarItem from '@theme/NavbarItem';\nimport NavbarColorModeToggle from '@theme/Navbar/ColorModeToggle';\nimport NavbarLogo from '@theme/Navbar/Logo';\nimport NavbarMobileSidebarToggle from '@theme/Navbar/MobileSidebar/Toggle';\nimport NavbarSearch from '@theme/Navbar/Search';\nimport SearchBar from '@theme/SearchBar';\n\nimport LanguageDropdown from '../../../components/LanguageDropdown';\n\nfunction useNavbarItems() {\n  return useThemeConfig().navbar.items;\n}\n\nfunction NavbarItems({ items }) {\n  return (\n    <>\n      {items.map((item, i) => (\n        <NavbarItem {...item} key={i} />\n      ))}\n    </>\n  );\n}\n\nfunction NavbarContentLayout({ left, right }) {\n  return (\n    <div className=\"navbar__inner\">\n      <div className=\"navbar__items\">{left}</div>\n      <div className=\"navbar__items navbar__items--right\">{right}</div>\n    </div>\n  );\n}\n\nexport default function NavbarContent(): React.JSX.Element {\n  const mobileSidebar = useNavbarMobileSidebar();\n  const items = useNavbarItems();\n  const [leftItems, rightItems] = splitNavbarItems(items);\n\n  return (\n    <NavbarContentLayout\n      left={\n        <>\n          {!mobileSidebar.disabled && <NavbarMobileSidebarToggle />}\n          <NavbarLogo />\n          <LanguageDropdown className=\"navbar__item\" />\n          <NavbarItems items={leftItems} />\n        </>\n      }\n      right={\n        <>\n          <NavbarItems items={rightItems} />\n          <NavbarSearch>\n            <SearchBar />\n          </NavbarSearch>\n          <NavbarColorModeToggle className=\"colorModeToggle\" />\n        </>\n      }\n    />\n  );\n}\n"
  },
  {
    "path": "teams.md/src/theme/PaginatorNavLink/index.tsx",
    "content": "import React from 'react';\nimport PaginatorNavLinkOriginal from '@theme-original/PaginatorNavLink';\nimport type { Props } from '@theme/PaginatorNavLink';\nimport useBaseUrl from '@docusaurus/useBaseUrl';\n\nimport { useLanguagePreference } from '../../hooks/useLanguagePreference';\nimport { getLanguageFromPathStrict, replaceLanguageInPath } from '../../utils/languageUtils';\n\nexport default function PaginatorNavLink(props: Props): React.JSX.Element {\n  const baseUrl = useBaseUrl('/');\n  const { language: preferredLanguage } = useLanguagePreference();\n\n  // If no permalink, use original behavior\n  if (!props.permalink) {\n    return <PaginatorNavLinkOriginal {...props} />;\n  }\n\n  // Check if the permalink contains a language path\n  const languageInPermalink = getLanguageFromPathStrict(props.permalink, baseUrl);\n\n  // If the link contains a language path, update it to match current preferred language\n  if (languageInPermalink !== null && languageInPermalink !== preferredLanguage) {\n    const correctedPermalink = replaceLanguageInPath(props.permalink, baseUrl, preferredLanguage);\n    return <PaginatorNavLinkOriginal {...props} permalink={correctedPermalink} />;\n  }\n\n  // Otherwise use original\n  return <PaginatorNavLinkOriginal {...props} />;\n}\n"
  },
  {
    "path": "teams.md/src/theme/Root.tsx",
    "content": "import React, { ReactNode } from 'react';\nimport { LanguageProvider } from '../hooks/useLanguagePreference';\n\n// Provide language context to the entire docusaurus app\nexport default function Root({ children }: { children: ReactNode }): React.JSX.Element {\n  return <LanguageProvider>{children}</LanguageProvider>;\n}\n"
  },
  {
    "path": "teams.md/src/utils/languageUtils.ts",
    "content": "import { LANGUAGES, type Language } from '../constants/languages';\n\n/**\n * Creates the regex pattern for matching language paths\n * @param baseUrl - The base URL\n * @returns RegExp for matching language paths\n */\nfunction createLanguagePattern(baseUrl: string): RegExp {\n  return new RegExp(`^${baseUrl}(${LANGUAGES.join('|')})(/|$)`);\n}\n\n/**\n * Gets the current language from a URL pathname\n * @param pathname - The current pathname (e.g. from useLocation)\n * @param baseUrl - The base URL (e.g. from useBaseUrl)\n * @returns The detected language or 'typescript' as fallback\n */\nexport function getLanguageFromPath(pathname: string, baseUrl: string): Language {\n  const languagePattern = createLanguagePattern(baseUrl);\n  const match = pathname.match(languagePattern);\n  return match ? (match[1] as Language) : 'typescript';\n}\n\n/**\n * Detects if a URL contains a language and returns it, or null if none found\n * @param pathname - The pathname to check\n * @param baseUrl - The base URL\n * @returns The detected language or null\n */\nexport function getLanguageFromPathStrict(pathname: string, baseUrl: string): Language | null {\n  const languagePattern = createLanguagePattern(baseUrl);\n  const match = pathname.match(languagePattern);\n  return match ? (match[1] as Language) : null;\n}\n\n/**\n * Replaces the language in a URL path with a new language\n * @param pathname - The pathname to modify\n * @param baseUrl - The base URL\n * @param newLanguage - The new language to replace with\n * @returns The modified pathname, or original if no language was found\n */\nexport function replaceLanguageInPath(\n  pathname: string,\n  baseUrl: string,\n  newLanguage: Language\n): string {\n  const languagePattern = createLanguagePattern(baseUrl);\n  const match = pathname.match(languagePattern);\n\n  if (match) {\n    return pathname.replace(languagePattern, `${baseUrl}${newLanguage}/`);\n  }\n\n  return pathname;\n}\n\n/**\n * Converts a URL path to the manifest path format used for language availability checking\n * @param pathname - The full URL pathname\n * @param baseUrl - The base URL\n * @returns The manifest path format (removes base and language, handles root)\n */\nexport function getManifestPathFromUrl(pathname: string, baseUrl: string): string {\n  // Remove base url and language:\n  const urlPath = pathname.replace(baseUrl, '').replace(/^[^/]+\\//, '');\n  // Remove trailing slash; use '/' for root\n  return urlPath.replace(/\\/$/, '') || '/';\n}\n"
  },
  {
    "path": "teams.md/src/utils/normalizePath.ts",
    "content": "/**\n * For cross-platform compatibility, normalize paths to use forward slash\n */\nexport default function normalizePath(path: string) {\nreturn path.replace(/\\\\/g, '/');\n}"
  },
  {
    "path": "teams.md/src/utils/pageAvailability.ts",
    "content": "import { type Language, type LanguageAvailabilityMap } from '../constants/languages';\n\n// Cache for the missing pages data\nlet missingPagesCache: LanguageAvailabilityMap | null = null;\n\n/**\n * Check if a page exists for a specific language by consulting the missing pages manifest\n * @param pagePath - The manifest path\n * @param language - The target language to check\n * @returns Promise<boolean> - true: page available; else false\n */\nexport async function isPageAvailableForLanguage(pagePath: string, language: Language): Promise<boolean> {\n  if (!missingPagesCache) {\n    try {\n      const response = await fetch('/teams-sdk/missing-pages.json');\n      if (response.ok) {\n        missingPagesCache = await response.json();\n      } else {\n        missingPagesCache = {};\n      }\n    } catch {\n      missingPagesCache = {};\n    }\n  }\n\n  const unavailableLanguages = missingPagesCache[pagePath];\n  // If page is found, it's unavailable for the specified language(s)\n  return !unavailableLanguages || !unavailableLanguages.includes(language);\n}\n"
  },
  {
    "path": "teams.md/src/utils/readFileUtf8Normalized.ts",
    "content": "import * as fs from 'fs';\n\n/**\n * Read a UTF-8 file and normalize CRLF (\\r\\n) to LF (\\n).\n * This ensures downstream string operations don't see stray \\r characters.\n */\nexport default function readFileUtf8Normalized(filePath: string): string {\n  return fs.readFileSync(filePath, 'utf8').replace(/\\r\\n/g, '\\n');\n}\n"
  },
  {
    "path": "teams.md/static/.nojekyll",
    "content": ""
  },
  {
    "path": "teams.md/static/llms_docs/llms.txt",
    "content": "# Teams AI Library Documentation\n\nThis website contains documentation for interacting with Teams AI Library, an SDK built to simplify building agents and applications that will integrate with Microsoft Teams.\nIMPORTANT: This SDK is NOT built using BotFramework (which was an older iteration).\n\nLanguage Specific URLs:\n- [Typescript Documentation](https://microsoft.github.io/teams-ai/llms_docs/llms_typescript.txt)\n- [Dotnet (C#) Documentation](https://microsoft.github.io/teams-ai/llms_docs/llms_typescript.txt)\n- [Python Documentation](https://microsoft.github.io/teams-ai/llms_docs/llms_python.txt)\n"
  },
  {
    "path": "teams.md/static/missing-pages.json",
    "content": "{\n  \"essentials/on-activity/activity-ref\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"in-depth-guides/ai/a2a\": [\n    \"csharp\"\n  ],\n  \"in-depth-guides/ai/a2a/a2a-client\": [\n    \"csharp\"\n  ],\n  \"in-depth-guides/ai/a2a/a2a-server\": [\n    \"csharp\"\n  ],\n  \"in-depth-guides/ai/mcp/mcp-server\": [\n    \"csharp\"\n  ],\n  \"in-depth-guides/server/http-server\": [\n    \"csharp\"\n  ],\n  \"in-depth-guides/tabs\": [\n    \"python\"\n  ],\n  \"in-depth-guides/tabs/app-options\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"in-depth-guides/tabs/functions\": [\n    \"python\"\n  ],\n  \"in-depth-guides/tabs/functions/function-calling\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"in-depth-guides/tabs/getting-started\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"in-depth-guides/tabs/graph\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"in-depth-guides/tabs/using-the-app\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"migrations\": [\n    \"csharp\"\n  ],\n  \"migrations/slack-bolt\": [\n    \"csharp\",\n    \"python\"\n  ],\n  \"migrations/v1\": [\n    \"csharp\"\n  ],\n  \"migrations/v2-previews\": [\n    \"csharp\",\n    \"python\"\n  ]\n}\n"
  },
  {
    "path": "teams.md/static/scripts/clarity.js",
    "content": "(function(c,l,a,r,i,t,y){\n    c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};\n    t=l.createElement(r);t.async=1;t.src=\"https://www.clarity.ms/tag/\"+i;\n    y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);\n})(window, document, \"clarity\", \"script\", \"rdeztc00ot\");"
  },
  {
    "path": "teams.md/tsconfig.json",
    "content": "{\n  // This file is not used in compilation. It is here just for a nice editor experience.\n  \"extends\": \"@docusaurus/tsconfig\",\n  \"compilerOptions\": {\n    \"baseUrl\": \".\"\n  },\n  \"exclude\": [\".docusaurus\", \"build\"]\n}\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"ui\": \"tui\",\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"inputs\": [\"$TURBO_DEFAULT$\", \".env*\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\"]\n    },\n    \"lint\": {\n      \"dependsOn\": [\"^lint\"]\n    },\n    \"check-types\": {\n      \"dependsOn\": [\"^check-types\"]\n    },\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    }\n  }\n}\n"
  }
]