[
  {
    "path": ".devcontainer/devcontainer.json",
    "content": "// For format details, see https://aka.ms/devcontainer.json. For config options, see the\n// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu\n{\n\t\"name\": \"Ubuntu\",\n\t// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile\n\t\"image\": \"mcr.microsoft.com/devcontainers/base:jammy\",\n\t\"features\": {\n\t\t\"ghcr.io/devcontainers/features/dotnet:2\": {},\n\t\t\"ghcr.io/devcontainers/features/node:1\": {},\n\t\t\"ghcr.io/devcontainers/features/python\": \"3.12\",\n\t\t\"ghcr.io/devcontainers/features/azure-cli\": \"latest\"\n\t}\n\n\t// Features to add to the dev container. More info: https://containers.dev/features.\n\t// \"features\": {},\n\n\t// Use 'forwardPorts' to make a list of ports inside the container available locally.\n\t// \"forwardPorts\": [],\n\n\t// Use 'postCreateCommand' to run commands after the container is created.\n\t// \"postCreateCommand\": \"uname -a\",\n\n\t// Configure tool-specific properties.\n\t// \"customizations\": {},\n\n\t// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.\n\t// \"remoteUser\": \"root\"\n}\n"
  },
  {
    "path": ".github/CODEOWNERS",
    "content": "* @dargilco\n* @glecaros\n* @rohit-ganguly"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for more information:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n# https://containers.dev/guide/dependabot\n\nversion: 2\nupdates:\n - package-ecosystem: \"devcontainers\"\n   directory: \"/\"\n   schedule:\n     interval: weekly\n"
  },
  {
    "path": ".github/workflows/typescript-build.yml",
    "content": "name: TypeScript Build\n\non:\n  push:\n    branches:\n      - main\n    paths: \n      - 'sdk/js/packages/**'\n  pull_request:\n    branches:\n      - main\n    paths: \n      - 'sdk/js/packages/**'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Setup Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20.11.1'\n\n    - name: Install Dependencies\n      run: npm ci\n      working-directory: ./sdk/js/packages/client\n\n    - name: Run Linter\n      run: npm run lint\n      working-directory: ./sdk/js/packages/client\n\n    - name: Check Format\n      run: npm run check-format\n      working-directory: ./sdk/js/packages/client\n\n    - name: Build\n      run: npm run build\n      working-directory: ./sdk/js/packages/client\n\n    - name: Test\n      run: npm test\n      working-directory: ./sdk/js/packages/client\n\n    - name: Update Version\n      run: |\n        version=$(npm pkg get version | tr -d '\"')\n        npm pkg set version=\"${version}-beta.${GITHUB_RUN_NUMBER}\"\n      working-directory: ./sdk/js/packages/client\n\n    - name: Pack\n      run: npm pack\n      working-directory: ./sdk/js/packages/client\n\n    - name: Upload Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: npm-package\n        path: ./sdk/js/packages/client/*.tgz"
  },
  {
    "path": ".github/workflows/typescript-release.yml",
    "content": "name: TypeScript Release\n\non:\n  push:\n    tags:\n      - 'release/js/*'\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Setup Node.js\n      uses: actions/setup-node@v4\n      with:\n        node-version: '20.11.1'\n\n    - name: Install Dependencies\n      run: npm ci\n      working-directory: ./sdk/js/packages/client\n\n    - name: Run Linter\n      run: npm run lint\n      working-directory: ./sdk/js/packages/client\n\n    - name: Check Format\n      run: npm run check-format\n      working-directory: ./sdk/js/packages/client\n\n    - name: Build\n      run: npm run build\n      working-directory: ./sdk/js/packages/client\n\n    - name: Test\n      run: npm test\n      working-directory: ./sdk/js/packages/client\n\n    - name: Set Version\n      run: |\n          version=${GITHUB_REF#refs/tags/release/js/}\n          npm pkg set version=\"${version}\"\n      working-directory: ./sdk/js/packages/client\n\n    - name: Pack\n      run: npm pack\n      working-directory: ./sdk/js/packages/client\n\n    - name: Upload Artifact\n      uses: actions/upload-artifact@v4\n      with:\n        name: npm-package\n        path: ./sdk/js/packages/client/*.tgz\n\n  release:\n    needs: build\n    runs-on: ubuntu-latest\n    permissions: write-all\n    steps:\n    - name: Checkout code\n      uses: actions/checkout@v4\n\n    - name: Download Artifact\n      uses: actions/download-artifact@v4\n      with:\n        name: npm-package\n        path: ./packages/\n\n    - name: Create Release\n      env:\n        GH_TOKEN: ${{ github.token}}\n      run: |\n        tag_name=${GITHUB_REF#refs/tags/}\n        version=${tag_name#release/js/}\n        gh release create $tag_name -t \"Typescript Release $version\" ./packages/*.tgz      \n"
  },
  {
    "path": ".gitignore",
    "content": "## Ignore Visual Studio temporary files, build results, and\n## files generated by popular Visual Studio add-ons.\n##\n## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore\n\n# User-specific files\n*.rsuser\n*.suo\n*.user\n*.userosscache\n*.sln.docstates\n\n# User-specific files (MonoDevelop/Xamarin Studio)\n*.userprefs\n\n# Mono auto generated files\nmono_crash.*\n\n# Build results\n[Dd]ebug/\n[Dd]ebugPublic/\n[Rr]elease/\n[Rr]eleases/\nx64/\nx86/\n[Ww][Ii][Nn]32/\n[Aa][Rr][Mm]/\n[Aa][Rr][Mm]64/\nbld/\n[Bb]in/\n[Oo]bj/\n[Ll]og/\n[Ll]ogs/\n\n# Visual Studio 2015/2017 cache/options directory\n.vs/\n# Uncomment if you have tasks that create the project's static files in wwwroot\n#wwwroot/\n\n# Visual Studio 2017 auto generated files\nGenerated\\ Files/\n\n# MSTest test Results\n[Tt]est[Rr]esult*/\n[Bb]uild[Ll]og.*\n\n# NUnit\n*.VisualState.xml\nTestResult.xml\nnunit-*.xml\n\n# Build Results of an ATL Project\n[Dd]ebugPS/\n[Rr]eleasePS/\ndlldata.c\n\n# Benchmark Results\nBenchmarkDotNet.Artifacts/\n\n# .NET Core\nproject.lock.json\nproject.fragment.lock.json\nartifacts/\n\n# ASP.NET Scaffolding\nScaffoldingReadMe.txt\n\n# StyleCop\nStyleCopReport.xml\n\n# Files built by Visual Studio\n*_i.c\n*_p.c\n*_h.h\n*.ilk\n*.meta\n*.obj\n*.iobj\n*.pch\n*.pdb\n*.ipdb\n*.pgc\n*.pgd\n*.rsp\n*.sbr\n*.tlb\n*.tli\n*.tlh\n*.tmp\n*.tmp_proj\n*_wpftmp.csproj\n*.log\n*.tlog\n*.vspscc\n*.vssscc\n.builds\n*.pidb\n*.svclog\n*.scc\n\n# Chutzpah Test files\n_Chutzpah*\n\n# Visual C++ cache files\nipch/\n*.aps\n*.ncb\n*.opendb\n*.opensdf\n*.sdf\n*.cachefile\n*.VC.db\n*.VC.VC.opendb\n\n# Visual Studio profiler\n*.psess\n*.vsp\n*.vspx\n*.sap\n\n# Visual Studio Trace Files\n*.e2e\n\n# TFS 2012 Local Workspace\n$tf/\n\n# Guidance Automation Toolkit\n*.gpState\n\n# ReSharper is a .NET coding add-in\n_ReSharper*/\n*.[Rr]e[Ss]harper\n*.DotSettings.user\n\n# TeamCity is a build add-in\n_TeamCity*\n\n# DotCover is a Code Coverage Tool\n*.dotCover\n\n# AxoCover is a Code Coverage Tool\n.axoCover/*\n!.axoCover/settings.json\n\n# Coverlet is a free, cross platform Code Coverage Tool\ncoverage*.json\ncoverage*.xml\ncoverage*.info\n\n# Visual Studio code coverage results\n*.coverage\n*.coveragexml\n\n# NCrunch\n_NCrunch_*\n.*crunch*.local.xml\nnCrunchTemp_*\n\n# MightyMoose\n*.mm.*\nAutoTest.Net/\n\n# Web workbench (sass)\n.sass-cache/\n\n# Installshield output folder\n[Ee]xpress/\n\n# DocProject is a documentation generator add-in\nDocProject/buildhelp/\nDocProject/Help/*.HxT\nDocProject/Help/*.HxC\nDocProject/Help/*.hhc\nDocProject/Help/*.hhk\nDocProject/Help/*.hhp\nDocProject/Help/Html2\nDocProject/Help/html\n\n# Click-Once directory\npublish/\n\n# Publish Web Output\n*.[Pp]ublish.xml\n*.azurePubxml\n# Note: Comment the next line if you want to checkin your web deploy settings,\n# but database connection strings (with potential passwords) will be unencrypted\n*.pubxml\n*.publishproj\n\n# Microsoft Azure Web App publish settings. Comment the next line if you want to\n# checkin your Azure Web App publish settings, but sensitive information contained\n# in these scripts will be unencrypted\nPublishScripts/\n\n# NuGet Packages\n*.nupkg\n# NuGet Symbol Packages\n*.snupkg\n# The packages folder can be ignored because of Package Restore\n**/[Pp]ackages/*\n# except build/, which is used as an MSBuild target.\n!**/[Pp]ackages/build/\n# Uncomment if necessary however generally it will be regenerated when needed\n#!**/[Pp]ackages/repositories.config\n# NuGet v3's project.json files produces more ignorable files\n*.nuget.props\n*.nuget.targets\n\n# Microsoft Azure Build Output\ncsx/\n*.build.csdef\n\n# Microsoft Azure Emulator\necf/\nrcf/\n\n# Windows Store app package directories and files\nAppPackages/\nBundleArtifacts/\nPackage.StoreAssociation.xml\n_pkginfo.txt\n*.appx\n*.appxbundle\n*.appxupload\n\n# Visual Studio cache files\n# files ending in .cache can be ignored\n*.[Cc]ache\n# but keep track of directories ending in .cache\n!?*.[Cc]ache/\n\n# Others\nClientBin/\n~$*\n*~\n*.dbmdl\n*.dbproj.schemaview\n*.jfm\n*.pfx\n*.publishsettings\norleans.codegen.cs\n\n# Including strong name files can present a security risk\n# (https://github.com/github/gitignore/pull/2483#issue-259490424)\n#*.snk\n\n# Since there are multiple workflows, uncomment next line to ignore bower_components\n# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)\n#bower_components/\n\n# RIA/Silverlight projects\nGenerated_Code/\n\n# Backup & report files from converting an old project file\n# to a newer Visual Studio version. Backup files are not needed,\n# because we have git ;-)\n_UpgradeReport_Files/\nBackup*/\nUpgradeLog*.XML\nUpgradeLog*.htm\nServiceFabricBackup/\n*.rptproj.bak\n\n# SQL Server files\n*.mdf\n*.ldf\n*.ndf\n\n# Business Intelligence projects\n*.rdl.data\n*.bim.layout\n*.bim_*.settings\n*.rptproj.rsuser\n*- [Bb]ackup.rdl\n*- [Bb]ackup ([0-9]).rdl\n*- [Bb]ackup ([0-9][0-9]).rdl\n\n# Microsoft Fakes\nFakesAssemblies/\n\n# GhostDoc plugin setting file\n*.GhostDoc.xml\n\n# Node.js Tools for Visual Studio\n.ntvs_analysis.dat\nnode_modules/\n\n# Visual Studio 6 build log\n*.plg\n\n# Visual Studio 6 workspace options file\n*.opt\n\n# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)\n*.vbw\n\n# Visual Studio 6 auto-generated project file (contains which files were open etc.)\n*.vbp\n\n# Visual Studio 6 workspace and project file (working project files containing files to include in project)\n*.dsw\n*.dsp\n\n# Visual Studio 6 technical files\n*.ncb\n*.aps\n\n# Visual Studio LightSwitch build output\n**/*.HTMLClient/GeneratedArtifacts\n**/*.DesktopClient/GeneratedArtifacts\n**/*.DesktopClient/ModelManifest.xml\n**/*.Server/GeneratedArtifacts\n**/*.Server/ModelManifest.xml\n_Pvt_Extensions\n\n# Paket dependency manager\n.paket/paket.exe\npaket-files/\n\n# FAKE - F# Make\n.fake/\n\n# CodeRush personal settings\n.cr/personal\n\n# Python Tools for Visual Studio (PTVS)\n__pycache__/\n*.pyc\n\n# Cake - Uncomment if you are using it\n# tools/**\n# !tools/packages.config\n\n# Tabs Studio\n*.tss\n\n# Telerik's JustMock configuration file\n*.jmconfig\n\n# BizTalk build output\n*.btp.cs\n*.btm.cs\n*.odx.cs\n*.xsd.cs\n\n# OpenCover UI analysis results\nOpenCover/\n\n# Azure Stream Analytics local run output\nASALocalRun/\n\n# MSBuild Binary and Structured Log\n*.binlog\n\n# NVidia Nsight GPU debugger configuration file\n*.nvuser\n\n# MFractors (Xamarin productivity tool) working folder\n.mfractor/\n\n# Local History for Visual Studio\n.localhistory/\n\n# Visual Studio History (VSHistory) files\n.vshistory/\n\n# BeatPulse healthcheck temp database\nhealthchecksdb\n\n# Backup folder for Package Reference Convert tool in Visual Studio 2017\nMigrationBackup/\n\n# Ionide (cross platform F# VS Code tools) working folder\n.ionide/\n\n# Fody - auto-generated XML schema\nFodyWeavers.xsd\n\n# VS Code files for those working on multiple tools\n.vscode/*\n!.vscode/settings.json\n!.vscode/tasks.json\n!.vscode/launch.json\n!.vscode/extensions.json\n*.code-workspace\n\n# Local History for Visual Studio Code\n.history/\n\n# Windows Installer files from build outputs\n*.cab\n*.msi\n*.msix\n*.msm\n*.msp\n\n# JetBrains Rider\n*.sln.iml\n\n# For any other files you don't want Git to track\n.hide/\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Microsoft Open Source Code of Conduct\n\nThis project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\n\nResources:\n\n- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)\n- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\n- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "## Contributing\n\nAs an open source repository, the AI Chat Protocol SDK welcomes contributions in the form of issues and pull requests (PRs).  \n\nMost 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"
  },
  {
    "path": "LICENSE",
    "content": "    MIT License\n\n    Copyright (c) Microsoft Corporation.\n\n    Permission is hereby granted, free of charge, to any person obtaining a copy\n    of this software and associated documentation files (the \"Software\"), to deal\n    in the Software without restriction, including without limitation the rights\n    to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n    copies of the Software, and to permit persons to whom the Software is\n    furnished to do so, subject to the following conditions:\n\n    The above copyright notice and this permission notice shall be included in all\n    copies or substantial portions of the Software.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n    IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n    FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n    AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n    LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n    OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n    SOFTWARE\n"
  },
  {
    "path": "README.md",
    "content": "# Microsoft AI Chat Protocol\n\n[![NPM Package](https://img.shields.io/npm/v/@microsoft/ai-chat-protocol)](https://www.npmjs.com/package/@microsoft/ai-chat-protocol)\n[![TypeScript Build](https://github.com/microsoft/ai-chat-protocol/actions/workflows/typescript-build.yml/badge.svg)](https://github.com/microsoft/ai-chat-protocol/actions/workflows/typescript-build.yml)\n\nThe Microsoft [AI Chat Protocol SDK](/sdk) is a library for easily building AI Chat interfaces from services that follow the [AI Chat Protocol API Specification](https://aka.ms/chatprotocol), both of which are located in this repository.\n\nBy agreeing on a standard API contract, AI backend consumption and evaluation can be performed easily and consistently across different services regardless of the models, orchestration tooling, or design patterns used.\n\n*Note: we are currently in public preview. Your feedback is greatly appreciated as we get ready to be generally available!*\n\nWith the AI Chat Protocol, you will be able to:\n\n* Develop AI chat interfaces, components, and applications in JavaScript/TypeScript (more languages to follow!)\n* Consistently consume and evaluate AI inference backends and middle tiers with ease, either synchronously or by streaming\n* Easily incorporate HTTP middleware for logging, authentication, and more.\n\n**Please star the repo to show your support for this project!**\n\n## Getting Started\n\nOur comprehensive getting started guide is coming soon! Be sure to check out the samples and API specification for more details.\n\n* [Samples](/samples)\n* [API Specification](/spec)\n* [Samples on Azure](#samples-on-azure)\n\nTo take a look locally, install the library via npm:\n\n```bash\nnpm install @microsoft/ai-chat-protocol\n```\n\nCreate the client object:\n\n```javascript\nconst client = new AIChatProtocolClient(\"/api/chat\");\n```\n\nStream completions to your UI:\n\n```javascript\nlet sessionState = undefined;\n\n// add any logic to handle state here\nfunction setSessionState(value) {\n    sessionState = value;\n}\n\nconst message: AIChatMessage = {\n    role: \"user\",\n    content: \"Hello World!\",\n};\n\nconst result = await client.getStreamedCompletion([message], {\n    sessionState: sessionState,\n});\n\nfor await (const response of result) {\n    if (response.sessionState) {\n        //do something with the session state returned\n    }\n    if (response.delta.role) {\n        // do something with the information about the role\n    }\n    if (response.delta.content) { \n        // do something with the content of the message\n    }\n}\n```\n\n## Samples on Azure\n\nIf you're curious on samples hosted on Azure, the following samples utilize the AI Chat Protocol SDK on the frontend:\n\n* [Serverless AI Chat with RAG using LangChain.js](https://github.com/Azure-Samples/serverless-chat-langchainjs)\n* [Chat Application using Azure OpenAI (Python)](https://github.com/Azure-Samples/openai-chat-app-quickstart)\n* [OpenAI Chat Application with Microsoft Entra Authentication (Python) - Local](https://github.com/Azure-Samples//openai-chat-app-entra-auth-local)\n* [OpenAI Chat Application with Microsoft Entra Authentication (Python) - Builtin](https://github.com/Azure-Samples/openai-chat-app-entra-auth-builtin)\n* [OpenAI Chat App Frontend (Vanilla JS)](https://github.com/Azure-Samples/openai-chat-frontend-vanillajs)\n* [Chat Application using Azure OpenAI (Python)](https://github.com/Azure-Samples/openai-chat-app-quickstart)\n\nAdditionally, many Azure AI sample projects utilize the AI Chat Protocol API spec without the SDK, either because they don't have a frontend, or because they were made before the library's release:\n\n* [ChatGPT + Enterprise data with Azure OpenAI and AI Search in Python](https://github.com/Azure-samples/azure-search-openai-demo)\n* [ChatGPT + Enterprise data with Azure OpenAI and Azure AI Search in JavaScript](https://github.com/Azure-samples/azure-search-openai-javascript)\n* [Chat with GPT Modes - FastAPI backend](https://github.com/Azure-Samples/openai-chat-backend-fastapi)\n* [Evaluating a RAG Chat App](https://github.com/Azure-Samples/ai-rag-chat-evaluator)\n\n## Code of Conduct\n\nThis project has adopted the\n[Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).\nFor more information see the\n[Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)\nor contact [opencode@microsoft.com](mailto:opencode@microsoft.com)\nwith any additional questions or comments.\n\n## License\n\nCopyright (c) Microsoft Corporation. All rights reserved.\n\nLicensed under the [MIT](LICENSE) license.\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": "omnisharp.json",
    "content": "{\n    \"projectLoadTimeout\": 100,\n    \"maxProjectResults\": 1,\n    \"excludeSearchPatterns\": [\"samples/**/*\"]\n}"
  },
  {
    "path": "samples/README.md",
    "content": "# Microsoft AI Chat Protocol Samples\n\n> This directory contains basic starters for using the AI Chat Protocol. If you're interested in more in-depth, end-to-end samples hosted on Azure, visit this [link](https://aka.ms/aichat/templates).\n\nIf you'd like to run the samples, follow these steps:\n\n## Frontend\n\n1. Clone the repository to your machine.\n1. In one terminal, navigate to the `frontend/js/react` directory.\n1. In the `frontend/js/react` directory, run `npm install` to install your dependencies, including [`@microsoft/ai-chat-protocol`](https://www.npmjs.com/package/@microsoft/ai-chat-protocol).\n1. Next, run `npm run dev` to start your web application.\n\n## Backend\n\nThe backend directory has both a .NET and a JavaScript (Express) backend sample. Follow the steps below for the sample you'd like to run.\n\n### .NET (with Semantic Kernel)\n\n1. In one terminal, navigate to the `backend/csharp` directory.\n2. Set the following environment variables:\n    1. UseAzureOpenAI - either `true` or `false`\n        1. If using Azure OpenAI, set your `AzureDeployment` and `AzureEndpoint` according to this [guide](https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart?tabs=command-line%2Cpython-new&pivots=programming-language-python#retrieve-key-and-endpoint).\n        2. Sign into Azure using the Azure CLI (`az login`) or Azure Developer CLI (`azd auth login`).\n    2. If you're using OpenAI (*not* Azure OpenAI), set the environment variables `APIKey` and `Model`.\n3. Next, run `dotnet restore` to restore your dependencies and `dotnet run` to run the backend.\n\n### JavaScript (Express)\n\n1. In one terminal, navigate to the `backend/js` directory.\n2. In the `.env` file, update `AZURE_OPENAI_ENDPOINT` and `AZURE_OPENAI_DEPLOYMENT` according to this [guide](https://learn.microsoft.com/en-us/azure/ai-services/openai/quickstart?tabs=command-line%2Cpython-new&pivots=programming-language-python#retrieve-key-and-endpoint).\n3. Run `npm install` to install your dependencies.\n4. Run `npm run dev` to run the backend.\n"
  },
  {
    "path": "samples/backend/csharp/ChatProtocolBackend.csproj",
    "content": "<Project Sdk=\"Microsoft.NET.Sdk.Web\">\n\n  <PropertyGroup>\n    <TargetFramework>net8.0</TargetFramework>\n    <Nullable>enable</Nullable>\n    <ImplicitUsings>enable</ImplicitUsings>\n  </PropertyGroup>\n\n  <ItemGroup>\n    <PackageReference Include=\"Azure.Identity\" Version=\"1.11.4\" />\n    <PackageReference Include=\"Azure.Security.KeyVault.Secrets\" Version=\"4.6.0\" />  \n    <PackageReference Include=\"Microsoft.SemanticKernel\" Version=\"1.7.1\" />\n    <PackageReference Include=\"System.Text.Json\" Version=\"8.0.4\" />\n  </ItemGroup>\n\n</Project>\n"
  },
  {
    "path": "samples/backend/csharp/Controllers/ChatController.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json;\nusing Microsoft.AspNetCore.Mvc;\n\nusing Backend.Interfaces;\nusing Backend.Model;\nusing System.Text.RegularExpressions;\n\nnamespace Backend.Controllers;\n\n[ApiController, Route(\"api/[controller]\")]\npublic partial class ChatController : ControllerBase\n{\n    private readonly ISemanticKernelApp _semanticKernelApp;\n\n    public ChatController(ISemanticKernelApp semanticKernelApp)\n    {\n        _semanticKernelApp = semanticKernelApp;\n    }\n\n    [GeneratedRegex(@\"messages\\[(\\d+)\\]\\.files\\[(\\d+)\\]\")]\n    private static partial Regex MessageFilesRegex();\n\n    private (int MessageIndex, int FileIndex, IFormFile File) GetPosition(IFormFile formFile)\n    {\n        var match = MessageFilesRegex().Match(formFile.Name);\n        if (match.Success && int.TryParse(match.Groups[1].ValueSpan, out var messageIndex) && int.TryParse(match.Groups[2].ValueSpan, out var fileIndex))\n        {\n            return (messageIndex, fileIndex, formFile);\n        }\n\n        throw new ArgumentException(\"Malformed multipart request: Invalid file name.\");\n    }\n\n    private async Task<AIChatRequest> RequestFromMultipart(IFormFileCollection formFiles)\n    {\n        using var jsonFileStream = formFiles\n            .First(f => f.Name == \"json\")\n            .OpenReadStream();\n        if (jsonFileStream is null)\n        {\n            throw new Exception(\"Malformed multipart request: Missing json part.\");\n        }\n\n        var request = await JsonSerializer.DeserializeAsync<AIChatRequest>(jsonFileStream) ??\n            throw new Exception(\"Malformed multipart request: Invalid json part.\");\n        foreach (var (messageIndex, fileIndex, file) in formFiles.Where(f => f.Name != \"json\").Select(GetPosition).OrderBy(p => p.MessageIndex).ThenBy(p => p.FileIndex))\n        {\n            if (request.Messages.Count <= messageIndex)\n            {\n                throw new Exception(\"Malformed multipart request: Invalid message index.\");\n            }\n\n            var message = request.Messages[messageIndex];\n            message.Files ??= new List<AIChatFile>();\n            if (message.Files.Count != fileIndex)\n            {\n                throw new Exception(\"Malformed multipart request: Invalid file index.\");\n            }\n\n            using var fileStream = file.OpenReadStream();\n            var fileData = await BinaryData.FromStreamAsync(fileStream);\n            message.Files.Add(new AIChatFile\n            {\n                ContentType = file.ContentType,\n                Data = fileData\n            });\n\n        }\n        return request;\n    }\n\n    [HttpPost]\n    [Consumes(\"multipart/form-data\")]\n    public async Task<IActionResult> ProcessMessage(IFormFileCollection files)\n    {\n        try\n        {\n            var request = await RequestFromMultipart(files);\n            var session = request.SessionState switch\n            {\n                Guid sessionId => await _semanticKernelApp.GetSession(sessionId),\n                _ => await _semanticKernelApp.CreateSession(Guid.NewGuid())\n            };\n\n            return Ok(await session.ProcessRequest(request));\n        }\n        catch (Exception e)\n        {\n            return BadRequest(e.Message);\n        }\n    }\n\n    [HttpPost]\n    [Consumes(\"application/json\")]\n    public async Task<IActionResult> ProcessMessage(AIChatRequest request)\n    {\n        var session = request.SessionState switch\n        {\n            Guid sessionId => await _semanticKernelApp.GetSession(sessionId),\n            _ => await _semanticKernelApp.CreateSession(Guid.NewGuid())\n        };\n        var response = await session.ProcessRequest(request);\n        return Ok(response);\n    }\n\n    [HttpPost(\"stream\")]\n    [Consumes(\"application/json\")]\n    public async Task ProcessStreamingMessage(AIChatRequest request)\n    {\n        var session = request.SessionState switch\n        {\n            Guid sessionId => await _semanticKernelApp.GetSession(sessionId),\n            _ => await _semanticKernelApp.CreateSession(Guid.NewGuid())\n        };\n        var response = Response;\n        response.Headers.Append(\"Content-Type\", \"application/jsonl\");\n        await foreach (var delta in session.ProcessStreamingRequest(request))\n        {\n            await response.WriteAsync($\"{JsonSerializer.Serialize(delta)}\\r\\n\");\n            await response.Body.FlushAsync();\n        }\n    }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Converters/JsonCamelCaseEnumConverter.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json;\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Converters;\n\npublic class JsonCamelCaseEnumConverter<T> : JsonStringEnumConverter<T> where T : struct, Enum\n{\n    public JsonCamelCaseEnumConverter() : base(JsonNamingPolicy.CamelCase)\n    {\n    }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Interfaces/ISecretStore.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nnamespace Backend.Interfaces;\n\npublic interface ISecretStore\n{\n    Task<string> GetSecretAsync(string secretName, CancellationToken cancellationToken = default);\n}\n"
  },
  {
    "path": "samples/backend/csharp/Interfaces/ISemanticKernelApp.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nnamespace Backend.Interfaces;\n\npublic interface ISemanticKernelApp\n{\n    Task<ISemanticKernelSession> CreateSession(Guid sessionId);\n    Task<ISemanticKernelSession> GetSession(Guid sessionId);\n}\n"
  },
  {
    "path": "samples/backend/csharp/Interfaces/ISemanticKernelSession.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing Backend.Model;\n\nnamespace Backend.Interfaces;\npublic interface ISemanticKernelSession\n{\n    Guid Id { get; }\n    Task<AIChatCompletion> ProcessRequest(AIChatRequest request);\n    IAsyncEnumerable<AIChatCompletionDelta> ProcessStreamingRequest(AIChatRequest request);\n}\n"
  },
  {
    "path": "samples/backend/csharp/Interfaces/IStateStore.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nnamespace Backend.Interfaces;\n\npublic interface IStateStore<T>\n{\n    Task<T?> GetStateAsync(Guid sessionId);\n    Task SetStateAsync(Guid sessionId, T state);\n    Task RemoveStateAsync(Guid sessionId);\n}\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatCompletion.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Model;\n\npublic record AIChatCompletion([property: JsonPropertyName(\"message\")] AIChatMessage Message)\n{\n    [JsonInclude, JsonPropertyName(\"sessionState\"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n    public Guid? SessionState;\n\n    [JsonInclude, JsonPropertyName(\"context\"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n    public BinaryData? Context;\n}\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatCompletionDelta.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Model;\n\npublic record AIChatCompletionDelta([property: JsonPropertyName(\"delta\")] AIChatMessageDelta Delta)\n{\n    [JsonInclude, JsonPropertyName(\"sessionState\"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n    public Guid? SessionState;\n\n    [JsonInclude, JsonPropertyName(\"context\"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n    public BinaryData? Context;\n}\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatFile.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Model;\n\npublic struct AIChatFile\n{\n    [JsonPropertyName(\"contentType\")]\n    public string ContentType { get; set; }\n\n    [JsonPropertyName(\"data\")]\n    public BinaryData Data { get; set; }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatMessage.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Model;\n\npublic struct AIChatMessage\n{\n    [JsonPropertyName(\"content\")]\n    public string Content { get; set; }\n\n    [JsonPropertyName(\"role\")]\n    public AIChatRole Role { get; set; }\n\n    [JsonPropertyName(\"context\")]\n    public BinaryData? Context { get; set; }\n\n    [JsonPropertyName(\"files\")]\n    public IList<AIChatFile>? Files { get; set; }\n}\n\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatMessageDelta.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Model;\n\npublic struct AIChatMessageDelta\n{\n    [JsonPropertyName(\"content\")]\n    public string? Content { get; set; }\n\n    [JsonPropertyName(\"role\")]\n    public AIChatRole? Role { get; set; }\n\n    [JsonPropertyName(\"context\"), JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]\n    public BinaryData? Context { get; set; }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatRequest.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nnamespace Backend.Model;\n\npublic record AIChatRequest([property: JsonPropertyName(\"messages\")] IList<AIChatMessage> Messages)\n{\n    [JsonInclude, JsonPropertyName(\"sessionState\")]\n    public Guid? SessionState;\n\n    [JsonInclude, JsonPropertyName(\"context\")]\n    public BinaryData? Context;\n}\n"
  },
  {
    "path": "samples/backend/csharp/Model/AIChatRole.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text.Json.Serialization;\n\nusing Backend.Converters;\n\nnamespace Backend.Model;\n\n[JsonConverter(typeof(JsonCamelCaseEnumConverter<AIChatRole>))]\npublic enum AIChatRole\n{\n    System,\n    Assistant,\n    User\n}\n"
  },
  {
    "path": "samples/backend/csharp/Program.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\r\n// Licensed under the MIT License.\r\n\r\nusing System.Text.Json;\r\nusing System.Text.Json.Serialization;\r\n\r\nusing Backend.Interfaces;\r\nusing Backend.Model;\r\nusing Backend.Services;\r\n\r\nvar builder = WebApplication.CreateBuilder(args);\r\n\r\nbuilder.Services.AddSingleton<IStateStore<string>>(new InMemoryStore<string>());\r\nbuilder.Services.AddSingleton<ISecretStore>(new EnvVarSecretStore());\r\nbuilder.Services.AddSingleton<ISemanticKernelApp, SemanticKernelApp>();\r\n\r\nbuilder.Services\r\n    .AddControllers()\r\n    .AddJsonOptions(o => o.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter<AIChatRole>(JsonNamingPolicy.CamelCase)));\r\n\r\nvar app = builder.Build();\r\n\r\n// Configure the HTTP request pipeline.\r\nif (!app.Environment.IsDevelopment())\r\n{\r\n    app.UseExceptionHandler(\"/Error\");\r\n    // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.\r\n    app.UseHsts();\r\n    app.UseHttpsRedirection();\r\n}\r\n\r\napp.UseStaticFiles();\r\n\r\napp.UseRouting();\r\n\r\napp.UseAuthorization();\r\n\r\napp.MapControllers();\r\n\r\napp.Run();\r\n"
  },
  {
    "path": "samples/backend/csharp/Properties/launchSettings.json",
    "content": "{\r\n  \"$schema\": \"http://json.schemastore.org/launchsettings.json\",\r\n  \"iisSettings\": {\r\n    \"windowsAuthentication\": false,\r\n    \"anonymousAuthentication\": true,\r\n    \"iisExpress\": {\r\n      \"applicationUrl\": \"http://localhost:21376\",\r\n      \"sslPort\": 44324\r\n    }\r\n  },\r\n  \"profiles\": {    \r\n    \"http\": {\r\n      \"commandName\": \"Project\",\r\n      \"dotnetRunMessages\": true,\r\n      \"launchBrowser\": true,\r\n      \"applicationUrl\": \"http://localhost:3000\",\r\n      \"environmentVariables\": {\r\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\r\n      }\r\n    },\r\n    \"https\": {\r\n      \"commandName\": \"Project\",\r\n      \"dotnetRunMessages\": true,\r\n      \"launchBrowser\": true,\r\n      \"applicationUrl\": \"https://localhost:7281;http://localhost:5094\",\r\n      \"environmentVariables\": {\r\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\r\n      }\r\n    },\r\n    \"IIS Express\": {\r\n      \"commandName\": \"IISExpress\",\r\n      \"launchBrowser\": true,\r\n      \"environmentVariables\": {\r\n        \"ASPNETCORE_ENVIRONMENT\": \"Development\"\r\n      }\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "samples/backend/csharp/Services/EnvVarSecretStore.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing Backend.Interfaces;\n\nnamespace Backend.Services;\n\npublic class EnvVarSecretStore : ISecretStore\n{\n    public Task<string> GetSecretAsync(string secretName, CancellationToken cancellationToken)\n    {\n#if !DEBUG\n        throw new ApplicationException(\"EnvVarSecretStore should not be used in production.\");\n#else\n        return Task.FromResult(Environment.GetEnvironmentVariable(secretName) ?? \"\");\n#endif\n    }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Services/InMemoryStore.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing Backend.Interfaces;\n\npublic class InMemoryStore<T> : IStateStore<T>\n{\n    private readonly Dictionary<Guid, T> _store = new Dictionary<Guid, T>();\n\n    public Task<T?> GetStateAsync(Guid sessionId)\n    {\n        _store.TryGetValue(sessionId, out var state);\n        return Task.FromResult(state);\n    }\n\n    public Task SetStateAsync(Guid sessionId, T state)\n    {\n        _store[sessionId] = state;\n        return Task.CompletedTask;\n    }\n\n    public Task RemoveStateAsync(Guid sessionId)\n    {\n        _store.Remove(sessionId);\n        return Task.CompletedTask;\n    }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Services/KeyVaultSecretStore.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing Azure.Security.KeyVault.Secrets;\n\nusing Backend.Interfaces;\n\nnamespace Backend.Services;\n\npublic class KeyVaultSecretStore : ISecretStore\n{\n    private readonly SecretClient _secretClient;\n\n    public KeyVaultSecretStore(SecretClient secretClient)\n    {\n        _secretClient = secretClient;\n    }\n\n    public async Task<string> GetSecretAsync(string secretName, CancellationToken cancellationToken)\n    {\n        KeyVaultSecret secret = await _secretClient.GetSecretAsync(secretName, cancellationToken: cancellationToken);\n        return secret.Value;\n    }\n}\n"
  },
  {
    "path": "samples/backend/csharp/Services/SemanticKernelApp.cs",
    "content": "// Copyright (c) Microsoft Corporation. All rights reserved.\n// Licensed under the MIT License.\n\nusing System.Text;\nusing Azure.Identity;\nusing Microsoft.SemanticKernel;\n\nusing Backend.Interfaces;\nusing Backend.Model;\n\nnamespace Backend.Services;\n\ninternal record LLMConfig;\ninternal record OpenAIConfig(string Model, string Key): LLMConfig;\ninternal record AzureOpenAIConfig(string Deployment, string Endpoint): LLMConfig;\n\ninternal struct SemanticKernelConfig\n{\n    internal LLMConfig LLMConfig { get; private init; }\n\n    internal static async Task<SemanticKernelConfig> CreateAsync(ISecretStore secretStore, CancellationToken cancellationToken)\n    {\n        var useAzureOpenAI = await secretStore.GetSecretAsync(\"UseAzureOpenAI\", cancellationToken).ContinueWith(task => bool.Parse(task.Result));\n        if (useAzureOpenAI)\n        {\n            var azureDeployment = await secretStore.GetSecretAsync(\"AzureDeployment\", cancellationToken);\n            var azureEndpoint = await secretStore.GetSecretAsync(\"AzureEndpoint\", cancellationToken);\n\n            return new SemanticKernelConfig\n            {\n                LLMConfig = new AzureOpenAIConfig(azureDeployment, azureEndpoint),\n            };\n        }\n        else\n        {\n            var apiKey = await secretStore.GetSecretAsync(\"APIKey\", cancellationToken);\n            var model = await secretStore.GetSecretAsync(\"Model\", cancellationToken);\n            return new SemanticKernelConfig\n            {\n                LLMConfig = new OpenAIConfig(model, apiKey),\n            };\n        }\n    }\n}\n\ninternal class SemanticKernelSession : ISemanticKernelSession\n{\n    private readonly Kernel _kernel;\n    private readonly IStateStore<string> _stateStore;\n    private readonly KernelFunction _chatFunction;\n\n    public Guid Id { get; private set; }\n\n    internal SemanticKernelSession(Kernel kernel, IStateStore<string> stateStore, Guid sessionId)\n    {\n        _kernel = kernel;\n        _stateStore = stateStore;\n        _chatFunction = _kernel.CreateFunctionFromPrompt(prompt);\n        Id = sessionId;\n    }\n\n    const string prompt = @\"\n        ChatBot can have a conversation with you about any topic.\n        It can give explicit instructions or say 'I don't know' if it does not know the answer.\n\n        {{$history}}\n        User: {{$userInput}}\n        ChatBot:\";\n\n    public async Task<AIChatCompletion> ProcessRequest(AIChatRequest message)\n    {\n        var userInput = message.Messages.Last();\n        string history = await _stateStore.GetStateAsync(Id) ?? \"\";\n        /* TODO: Add support for text+image content */\n        var arguments = new KernelArguments()\n        {\n            [\"history\"] = history,\n            [\"userInput\"] = userInput.Content,\n        };\n        var botResponse = await _chatFunction.InvokeAsync(_kernel, arguments);\n        var updatedHistory = $\"{history}\\nUser: {userInput.Content}\\nChatBot: {botResponse}\";\n        await _stateStore.SetStateAsync(Id, updatedHistory);\n        return new AIChatCompletion(Message: new AIChatMessage\n        {\n            Role = AIChatRole.Assistant,\n            Content = $\"{botResponse}\",\n        })\n        {\n            SessionState = Id,\n        };\n    }\n\n    public async IAsyncEnumerable<AIChatCompletionDelta> ProcessStreamingRequest(AIChatRequest message)\n    {\n        var userInput = message.Messages.Last();\n        string history = await _stateStore.GetStateAsync(Id) ?? \"\";\n        var arguments = new KernelArguments()\n        {\n            [\"history\"] = history,\n            [\"userInput\"] = userInput.Content,\n        };\n        var streamedBotResponse = _chatFunction.InvokeStreamingAsync(_kernel, arguments);\n        StringBuilder response = new();\n        await foreach (var botResponse in streamedBotResponse)\n        {\n            response.Append(botResponse);\n            yield return new AIChatCompletionDelta(Delta: new AIChatMessageDelta\n            {\n                Role = AIChatRole.Assistant,\n                Content = $\"{botResponse}\",\n            })\n            {\n                SessionState = Id,\n            };\n        }\n        var updatedHistory = $\"{history}\\nUser: {userInput.Content}\\nChatBot: {response}\";\n        await _stateStore.SetStateAsync(Id, updatedHistory);\n    }\n\n}\n\npublic class SemanticKernelApp : ISemanticKernelApp\n{\n    private readonly ISecretStore _secretStore;\n    private readonly IStateStore<string> _stateStore;\n    private readonly Lazy<Task<Kernel>> _kernel;\n\n    private async Task<Kernel> InitKernel()\n    {\n        var config = await SemanticKernelConfig.CreateAsync(_secretStore, CancellationToken.None);\n        var builder = Kernel.CreateBuilder();\n        if (config.LLMConfig is AzureOpenAIConfig azureOpenAIConfig)\n        {\n            if (azureOpenAIConfig.Deployment is null || azureOpenAIConfig.Endpoint is null)\n            {\n                throw new InvalidOperationException(\"AzureOpenAI is enabled but AzureDeployment and AzureEndpoint are not set.\");\n            }\n            builder.AddAzureOpenAIChatCompletion(azureOpenAIConfig.Deployment, azureOpenAIConfig.Endpoint, new DefaultAzureCredential());\n        }\n        else if (config.LLMConfig is OpenAIConfig openAIConfig)\n        {\n            if (openAIConfig.Model is null || openAIConfig.Key is null)\n            {\n                throw new InvalidOperationException(\"AzureOpenAI is disabled but Model and APIKey are not set.\");\n            }\n            builder.AddOpenAIChatCompletion(openAIConfig.Model, openAIConfig.Key);\n        }\n        else\n        {\n            throw new InvalidOperationException(\"Unsupported LLMConfig type.\");\n        }\n        return builder.Build();\n    }\n\n    public SemanticKernelApp(ISecretStore secretStore, IStateStore<string> stateStore)\n    {\n        _secretStore = secretStore;\n        _stateStore = stateStore;\n        _kernel = new(() => Task.Run(InitKernel));\n    }\n\n    public async Task<ISemanticKernelSession> CreateSession(Guid sessionId)\n    {\n        var kernel = await _kernel.Value;\n        return new SemanticKernelSession(kernel, _stateStore, sessionId);\n    }\n\n    public async Task<ISemanticKernelSession> GetSession(Guid sessionId)\n    {\n        var kernel = await _kernel.Value;\n        var state = await _stateStore.GetStateAsync(sessionId);\n        if (state is null)\n        {\n            throw new KeyNotFoundException($\"Session {sessionId} not found.\");\n        }\n        return new SemanticKernelSession(kernel, _stateStore, sessionId);\n    }\n}\n"
  },
  {
    "path": "samples/backend/csharp/appsettings.Development.json",
    "content": "{\r\n  \"DetailedErrors\": true,\r\n  \"Logging\": {\r\n    \"LogLevel\": {\r\n      \"Default\": \"Information\",\r\n      \"Microsoft.AspNetCore\": \"Warning\"\r\n    }\r\n  }\r\n}\r\n"
  },
  {
    "path": "samples/backend/csharp/appsettings.json",
    "content": "{\r\n  \"Logging\": {\r\n    \"LogLevel\": {\r\n      \"Default\": \"Information\",\r\n      \"Microsoft.AspNetCore\": \"Warning\"\r\n    }\r\n  },\r\n  \"AllowedHosts\": \"*\"\r\n}\r\n"
  },
  {
    "path": "samples/backend/js/expressjs/.gitignore",
    "content": ".env\ndist"
  },
  {
    "path": "samples/backend/js/expressjs/package.json",
    "content": "{\n  \"name\": \"chat-endpoint\",\n  \"version\": \"1.0.0\",\n  \"description\": \"A ExpressJS-based reference implementation of a chat endpoint.\",\n  \"main\": \"dist/index.js\",\n  \"scripts\": {\n    \"build\": \"tsc\",\n    \"start\": \"node dist/index.js\",\n    \"dev\": \"nodemon src/index.ts\",\n    \"format\": \"prettier --write src\"\n  },\n  \"author\": \"Gerardo Lecaros <gerardo.lecaros.e@gmail.com>\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@azure/identity\": \"^4.2.1\",    \n    \"cors\": \"^2.8.5\",\n    \"dotenv\": \"^16.4.5\",\n    \"express\": \"^4.19.1\",\n    \"formidable\": \"^3.5.1\",\n    \"lodash\": \"^4.17.21\",\n    \"openai\": \"^4.52.0\",\n    \"uuid\": \"^9.0.1\"\n  },\n  \"devDependencies\": {\n    \"@microsoft/ai-chat-protocol\": \"^1.0.0-beta.20240610.1\",\n    \"@types/cors\": \"^2.8.17\",\n    \"@types/express\": \"^4.17.21\",\n    \"@types/formidable\": \"^3.4.5\",\n    \"@types/lodash\": \"^4.17.5\",\n    \"@types/node\": \"^20.11.30\",\n    \"@types/uuid\": \"^9.0.8\",\n    \"nodemon\": \"^3.1.0\",\n    \"prettier\": \"^3.2.5\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.4.3\"\n  }\n}\n"
  },
  {
    "path": "samples/backend/js/expressjs/src/config.ts",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport dotenv from \"dotenv\";\n\ndotenv.config();\n\nexport enum ConfigParameter {\n  port,\n  azureOpenAIEndpoint,\n  azureOpenAIDeployment,\n  redisUrl,\n  systemPrompt,\n  stateTTL,\n}\n\nexport function getConfig(parameter: ConfigParameter): string {\n  const getValue = () => {\n    switch (parameter) {\n      case ConfigParameter.azureOpenAIEndpoint: {\n        return process.env.AZURE_OPENAI_ENDPOINT;\n      }\n      case ConfigParameter.azureOpenAIDeployment: {\n        return process.env.AZURE_OPENAI_DEPLOYMENT;\n      }\n      case ConfigParameter.port: {\n        return process.env.PORT;\n      }\n      case ConfigParameter.systemPrompt: {\n        return process.env.SYSTEM_PROMPT;\n      }\n      case ConfigParameter.stateTTL: {\n        return process.env.STATE_TTL;\n      }\n      default: {\n        throw new Error(\"Unsupported config parameter.\");\n      }\n    }\n  };\n  const value = getValue();\n  if (!value) {\n    throw new Error(\"Not found.\");\n  }\n  return value;\n}\n"
  },
  {
    "path": "samples/backend/js/expressjs/src/index.ts",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport express, { Express } from \"express\";\nimport * as cors from \"cors\";\n\nimport chat from \"./routes/chat\";\nimport { ConfigParameter, getConfig } from \"./config\";\n\nconst app: Express = express();\n\nconst port = getConfig(ConfigParameter.port);\n\napp.use(cors.default());\n\napp.use(\"/api/chat\", chat);\n\napp.listen(port, () => {\n  console.log(`[server]: Server is running at http://localhost:${port}`);\n});\n"
  },
  {
    "path": "samples/backend/js/expressjs/src/routes/chat.ts",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport {\n  DefaultAzureCredential,\n  getBearerTokenProvider,\n} from \"@azure/identity\";\nimport express, { Router, Request } from \"express\";\nimport { v4 as uuid } from \"uuid\";\nimport formidable from \"formidable\";\nimport _ from \"lodash\";\nimport fs from \"fs\";\n\nimport { ConfigParameter, getConfig } from \"../config\";\nimport {\n  AIChatCompletion,\n  AIChatCompletionDelta,\n  AIChatCompletionRequest,\n  AIChatMessage,\n  AIChatRole,\n} from \"@microsoft/ai-chat-protocol\";\nimport { StateStore } from \"../state-store\";\nimport { AzureOpenAI } from \"openai\";\nimport {\n  ChatCompletionContentPartImage,\n  ChatCompletionMessageParam,\n} from \"openai/resources\";\n\ndeclare global {\n  namespace Express {\n    interface Request {\n      sessionState: string;\n    }\n  }\n}\n\nconst chat = Router();\n\nconst client = new AzureOpenAI({\n  apiVersion: \"2024-05-01-preview\",\n  endpoint: getConfig(ConfigParameter.azureOpenAIEndpoint),\n  azureADTokenProvider: getBearerTokenProvider(\n    new DefaultAzureCredential(),\n    \"https://cognitiveservices.azure.com/.default\",\n  ),\n});\n\nconst stateStore = new StateStore<AIChatMessage[]>();\n\ntype UnknownRequest = Request<{}, {}, unknown>;\ntype ChatRequest = Request<{}, {}, AIChatCompletionRequest>;\n\nasync function readFile(filepath: string): Promise<Buffer> {\n  const data = await fs.promises.readFile(filepath);\n  await fs.promises.unlink(filepath);\n  return data;\n}\n\nasync function readJson(filepath: string): Promise<ChatRequest> {\n  const buffer = await readFile(filepath);\n  const data = buffer.toString(\"utf-8\");\n  return JSON.parse(data);\n}\n\nconst jsonMiddleware = express.json();\n\nchat.use(async (req: UnknownRequest, res, next) => {\n  if (req.is(\"multipart/form-data\")) {\n    try {\n      const form = formidable();\n      const [, files] = await form.parse(req);\n\n      const [jsonFile] = files.json as formidable.File[];\n      const json = await readJson(jsonFile.filepath);\n\n      for (let key of Object.keys(files)) {\n        if (key === \"json\") {\n          continue;\n        }\n        const [file] = files[key] as formidable.File[];\n        const data = await readFile(file.filepath);\n        _.set(json, `${key}.data`, data);\n        _.set(json, `${key}.contentType`, file.mimetype);\n      }\n      req.body = json;\n      req.headers[\"content-type\"] = \"application/json\";\n\n      return next();\n    } catch (error) {\n      return next(error);\n    }\n  } else if (req.is(\"application/json\")) {\n    return jsonMiddleware(req, res, next);\n  }\n\n  return next();\n});\n\nchat.use((req: ChatRequest, res, next) => {\n  const request = req.body;\n  if (request.sessionState && typeof request.sessionState == \"string\") {\n    req.sessionState = request.sessionState as string;\n    try {\n      const history = stateStore.read(request.sessionState);\n      console.info(`Loaded history for session ${request.sessionState}`);\n      req.body.messages = [...history, ...request.messages];\n      return next();\n    } catch (error) {\n      console.error(\n        `Failed to load history for session ${request.sessionState}: ${error}`,\n      );\n    }\n  } else {\n    req.sessionState = uuid();\n  }\n\n  request.messages = [\n    {\n      role: \"system\",\n      content: getConfig(ConfigParameter.systemPrompt),\n    },\n    ...request.messages,\n  ];\n  return next();\n});\n\nfunction toOpenAIMessage(message: AIChatMessage): ChatCompletionMessageParam {\n  if (message.files && message.files.length > 0 && message.role === \"user\") {\n    const fileContent: ChatCompletionContentPartImage[] = message.files.map(\n      (file) => ({\n        type: \"image_url\",\n        image_url: {\n          url: `data:${file.contentType};base64,${file.data.toString(\"base64\")}`,\n        },\n      }),\n    );\n    return {\n      role: message.role,\n      content: [\n        ...fileContent,\n        {\n          type: \"text\",\n          text: message.content,\n        },\n      ],\n    };\n  }\n  return {\n    role: message.role,\n    content: message.content,\n  };\n}\n\nchat.post(\"/\", async (req: ChatRequest, res, next) => {\n  try {\n    const response = await client.chat.completions.create({\n      model: getConfig(ConfigParameter.azureOpenAIDeployment),\n      messages: req.body.messages.map(toOpenAIMessage),\n    });\n\n    const choice = response.choices[0];\n    const responseMessage: AIChatMessage = {\n      role: (choice?.message?.role ?? undefined) as AIChatRole,\n      content: choice?.message?.content ?? \"\",\n    };\n\n    stateStore.save(req.sessionState, [...req.body.messages, responseMessage]);\n\n    const completion: AIChatCompletion = {\n      message: responseMessage,\n      sessionState: req.sessionState,\n    };\n    res.json(completion);\n  } catch (error) {\n    return next(error);\n  }\n});\n\nchat.post(\n  \"/stream\",\n  async (req: Request<{}, {}, AIChatCompletionRequest>, res, next) => {\n    try {\n      const response = await client.chat.completions.create({\n        stream: true,\n        model: getConfig(ConfigParameter.azureOpenAIDeployment),\n        messages: req.body.messages.map(toOpenAIMessage),\n      });\n      res.contentType(\"application/jsonl\");\n      const responseMessage: AIChatMessage = {\n        role: \"assistant\",\n        content: \"\",\n      };\n      for await (const event of response) {\n        const choice = event.choices[0];\n        if (choice && choice.delta) {\n          const delta = choice.delta;\n          const completion: AIChatCompletionDelta = {\n            delta: {\n              content: delta.content ?? undefined,\n              role: (delta.role ?? undefined) as AIChatRole,\n            },\n            sessionState: req.sessionState,\n          };\n          responseMessage.role = completion.delta.role ?? responseMessage.role;\n          responseMessage.content += completion.delta.content ?? \"\";\n          res.write(JSON.stringify(completion) + \"\\r\\n\");\n        }\n      }\n      stateStore.save(req.sessionState, [\n        ...req.body.messages,\n        responseMessage,\n      ]);\n      res.end();\n    } catch (error) {\n      return next(error);\n    }\n  },\n);\n\nexport default chat;\n"
  },
  {
    "path": "samples/backend/js/expressjs/src/state-store.ts",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nexport class StateStore<T> {\n  private store: Record<string, T>;\n\n  constructor() {\n    this.store = {};\n  }\n\n  public read(key: string): T {\n    const state = this.store[key];\n    if (!state) {\n      throw new Error(\"Not found.\");\n    }\n    return this.store[key];\n  }\n\n  public async save(key: string, state: T) {\n    this.store[key] = state;\n  }\n}\n"
  },
  {
    "path": "samples/backend/js/expressjs/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    /* Visit https://aka.ms/tsconfig to read more about this file */\n\n    /* Projects */\n    // \"incremental\": true,                              /* Save .tsbuildinfo files to allow for incremental compilation of projects. */\n    // \"composite\": true,                                /* Enable constraints that allow a TypeScript project to be used with project references. */\n    // \"tsBuildInfoFile\": \"./.tsbuildinfo\",              /* Specify the path to .tsbuildinfo incremental compilation file. */\n    // \"disableSourceOfProjectReferenceRedirect\": true,  /* Disable preferring source files instead of declaration files when referencing composite projects. */\n    // \"disableSolutionSearching\": true,                 /* Opt a project out of multi-project reference checking when editing. */\n    // \"disableReferencedProjectLoad\": true,             /* Reduce the number of projects loaded automatically by TypeScript. */\n\n    /* Language and Environment */\n    \"target\": \"es2016\",                                  /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */\n    // \"lib\": [],                                        /* Specify a set of bundled library declaration files that describe the target runtime environment. */\n    // \"jsx\": \"preserve\",                                /* Specify what JSX code is generated. */\n    // \"experimentalDecorators\": true,                   /* Enable experimental support for legacy experimental decorators. */\n    // \"emitDecoratorMetadata\": true,                    /* Emit design-type metadata for decorated declarations in source files. */\n    // \"jsxFactory\": \"\",                                 /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */\n    // \"jsxFragmentFactory\": \"\",                         /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */\n    // \"jsxImportSource\": \"\",                            /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */\n    // \"reactNamespace\": \"\",                             /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */\n    // \"noLib\": true,                                    /* Disable including any library files, including the default lib.d.ts. */\n    // \"useDefineForClassFields\": true,                  /* Emit ECMAScript-standard-compliant class fields. */\n    // \"moduleDetection\": \"auto\",                        /* Control what method is used to detect module-format JS files. */\n\n    /* Modules */\n    \"module\": \"commonjs\",                                /* Specify what module code is generated. */\n    // \"rootDir\": \"./\",                                  /* Specify the root folder within your source files. */\n    // \"moduleResolution\": \"node10\",                     /* Specify how TypeScript looks up a file from a given module specifier. */\n    // \"baseUrl\": \"./\",                                  /* Specify the base directory to resolve non-relative module names. */\n    // \"paths\": {},                                      /* Specify a set of entries that re-map imports to additional lookup locations. */\n    // \"rootDirs\": [],                                   /* Allow multiple folders to be treated as one when resolving modules. */\n    // \"typeRoots\": [],                                  /* Specify multiple folders that act like './node_modules/@types'. */\n    // \"types\": [],                                      /* Specify type package names to be included without being referenced in a source file. */\n    // \"allowUmdGlobalAccess\": true,                     /* Allow accessing UMD globals from modules. */\n    // \"moduleSuffixes\": [],                             /* List of file name suffixes to search when resolving a module. */\n    // \"allowImportingTsExtensions\": true,               /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */\n    // \"resolvePackageJsonExports\": true,                /* Use the package.json 'exports' field when resolving package imports. */\n    // \"resolvePackageJsonImports\": true,                /* Use the package.json 'imports' field when resolving imports. */\n    // \"customConditions\": [],                           /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */\n    // \"resolveJsonModule\": true,                        /* Enable importing .json files. */\n    // \"allowArbitraryExtensions\": true,                 /* Enable importing files with any extension, provided a declaration file is present. */\n    // \"noResolve\": true,                                /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */\n\n    /* JavaScript Support */\n    // \"allowJs\": true,                                  /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */\n    // \"checkJs\": true,                                  /* Enable error reporting in type-checked JavaScript files. */\n    // \"maxNodeModuleJsDepth\": 1,                        /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */\n\n    /* Emit */\n    // \"declaration\": true,                              /* Generate .d.ts files from TypeScript and JavaScript files in your project. */\n    // \"declarationMap\": true,                           /* Create sourcemaps for d.ts files. */\n    // \"emitDeclarationOnly\": true,                      /* Only output d.ts files and not JavaScript files. */\n    // \"sourceMap\": true,                                /* Create source map files for emitted JavaScript files. */\n    // \"inlineSourceMap\": true,                          /* Include sourcemap files inside the emitted JavaScript. */\n    // \"outFile\": \"./\",                                  /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */\n    \"outDir\": \"./dist\",                                   /* Specify an output folder for all emitted files. */\n    // \"removeComments\": true,                           /* Disable emitting comments. */\n    // \"noEmit\": true,                                   /* Disable emitting files from a compilation. */\n    // \"importHelpers\": true,                            /* Allow importing helper functions from tslib once per project, instead of including them per-file. */\n    // \"importsNotUsedAsValues\": \"remove\",               /* Specify emit/checking behavior for imports that are only used for types. */\n    // \"downlevelIteration\": true,                       /* Emit more compliant, but verbose and less performant JavaScript for iteration. */\n    // \"sourceRoot\": \"\",                                 /* Specify the root path for debuggers to find the reference source code. */\n    // \"mapRoot\": \"\",                                    /* Specify the location where debugger should locate map files instead of generated locations. */\n    // \"inlineSources\": true,                            /* Include source code in the sourcemaps inside the emitted JavaScript. */\n    // \"emitBOM\": true,                                  /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */\n    // \"newLine\": \"crlf\",                                /* Set the newline character for emitting files. */\n    // \"stripInternal\": true,                            /* Disable emitting declarations that have '@internal' in their JSDoc comments. */\n    // \"noEmitHelpers\": true,                            /* Disable generating custom helper functions like '__extends' in compiled output. */\n    // \"noEmitOnError\": true,                            /* Disable emitting files if any type checking errors are reported. */\n    // \"preserveConstEnums\": true,                       /* Disable erasing 'const enum' declarations in generated code. */\n    // \"declarationDir\": \"./\",                           /* Specify the output directory for generated declaration files. */\n    // \"preserveValueImports\": true,                     /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */\n\n    /* Interop Constraints */\n    // \"isolatedModules\": true,                          /* Ensure that each file can be safely transpiled without relying on other imports. */\n    // \"verbatimModuleSyntax\": true,                     /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */\n    // \"allowSyntheticDefaultImports\": true,             /* Allow 'import x from y' when a module doesn't have a default export. */\n    \"esModuleInterop\": true,                             /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */\n    // \"preserveSymlinks\": true,                         /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */\n    \"forceConsistentCasingInFileNames\": true,            /* Ensure that casing is correct in imports. */\n\n    /* Type Checking */\n    \"strict\": true,                                      /* Enable all strict type-checking options. */\n    // \"noImplicitAny\": true,                            /* Enable error reporting for expressions and declarations with an implied 'any' type. */\n    // \"strictNullChecks\": true,                         /* When type checking, take into account 'null' and 'undefined'. */\n    // \"strictFunctionTypes\": true,                      /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */\n    // \"strictBindCallApply\": true,                      /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */\n    // \"strictPropertyInitialization\": true,             /* Check for class properties that are declared but not set in the constructor. */\n    // \"noImplicitThis\": true,                           /* Enable error reporting when 'this' is given the type 'any'. */\n    // \"useUnknownInCatchVariables\": true,               /* Default catch clause variables as 'unknown' instead of 'any'. */\n    // \"alwaysStrict\": true,                             /* Ensure 'use strict' is always emitted. */\n    // \"noUnusedLocals\": true,                           /* Enable error reporting when local variables aren't read. */\n    // \"noUnusedParameters\": true,                       /* Raise an error when a function parameter isn't read. */\n    // \"exactOptionalPropertyTypes\": true,               /* Interpret optional property types as written, rather than adding 'undefined'. */\n    // \"noImplicitReturns\": true,                        /* Enable error reporting for codepaths that do not explicitly return in a function. */\n    // \"noFallthroughCasesInSwitch\": true,               /* Enable error reporting for fallthrough cases in switch statements. */\n    // \"noUncheckedIndexedAccess\": true,                 /* Add 'undefined' to a type when accessed using an index. */\n    // \"noImplicitOverride\": true,                       /* Ensure overriding members in derived classes are marked with an override modifier. */\n    // \"noPropertyAccessFromIndexSignature\": true,       /* Enforces using indexed accessors for keys declared using an indexed type. */\n    // \"allowUnusedLabels\": true,                        /* Disable error reporting for unused labels. */\n    // \"allowUnreachableCode\": true,                     /* Disable error reporting for unreachable code. */\n\n    /* Completeness */\n    // \"skipDefaultLibCheck\": true,                      /* Skip type checking .d.ts files that are included with TypeScript. */\n    \"skipLibCheck\": true                                 /* Skip type checking all .d.ts files. */\n  }\n}\n"
  },
  {
    "path": "samples/backend/python/quart/.gitignore",
    "content": ".env"
  },
  {
    "path": "samples/backend/python/quart/__init__.py",
    "content": "import base64\nimport os\nimport re\nfrom typing import Optional\n\nfrom azure.identity import DefaultAzureCredential, get_bearer_token_provider\nfrom dotenv import load_dotenv\nfrom openai import AsyncAzureOpenAI\nfrom pydantic import BaseModel\n\nfrom model import (\n    AIChatCompletion,\n    AIChatCompletionDelta,\n    AIChatError,\n    AIChatFile,\n    AIChatMessage,\n    AIChatMessageDelta,\n    AIChatRequest,\n    AIChatRole,\n)\nfrom quart import Quart, jsonify, request, stream_with_context\n\nload_dotenv()\n\nPORT = os.getenv(\"PORT\", 3000)\nAZURE_OPENAI_ENDPOINT = os.getenv(\"AZURE_OPENAI_ENDPOINT\")\nAZURE_OPENAI_DEPLOYMENT = os.getenv(\"AZURE_OPENAI_DEPLOYMENT\")\nAZURE_OPENAI_API_VERSION = os.getenv(\"AZURE_OPENAI_API_VERSION\")\n\ntoken_provider = get_bearer_token_provider(DefaultAzureCredential(), \"https://cognitiveservices.azure.com/.default\")\nclient = AsyncAzureOpenAI(\n    api_version=AZURE_OPENAI_API_VERSION,\n    azure_endpoint=AZURE_OPENAI_ENDPOINT,\n    azure_ad_token_provider=token_provider,\n)\n\napp = Quart(__name__)\n\n\ndef run() -> None:\n    app.run(port=PORT)\n\n\ndef get_file_position(file_key: str) -> tuple[int, int, str]:\n    \"\"\"\n    Extracts the message and file indices from a given file key.\n\n    The function expects file keys in the format \"messages[<message_index>].files[<file_index>]\",\n    where <message_index> and <file_index> are integers. It parses these indices from the file key\n    and returns them along with the original file key as a tuple.\n\n    Args:\n        file_key (str): The key representing a file's position in the message structure,\n                        expected to follow the specific format mentioned above.\n\n    Returns:\n        tuple[int, int, str]: A tuple containing the message index, file index,\n                              and the original file key if the key matches the expected format.\n\n    Raises:\n        ValueError: If the file key does not match the expected format.\n    \"\"\"\n    match = re.match(r\"messages\\[(\\d+)\\]\\.files\\[(\\d+)\\]\", file_key)\n    if match:\n        message_index, file_index = map(int, match.groups())\n        return message_index, file_index, file_key\n    raise ValueError(f\"Invalid file name: {file_key}\")\n\n\ndef reconstruct_multipart_request(form: dict, files: dict):\n    \"\"\"\n    Reconstructs an AIChatRequest object from multipart form data.\n\n    This function takes form data and a dictionary of files, then reconstructs\n    the AIChatRequest object by parsing the JSON content from the form and attaching\n    the files to their corresponding messages based on their keys.\n\n    Args:\n        form (dict): A dictionary containing the form data, expected to have a \"json\" key\n                     with the JSON representation of the AIChatRequest.\n        files (dict): A dictionary where keys are file keys in the format\n                      \"messages[<message_index>].files[<file_index>]\" and values are the file objects.\n\n    Returns:\n        AIChatRequest: The reconstructed AIChatRequest object with files attached to the appropriate messages.\n\n    Raises:\n        ValueError: If any file key does not match the expected format, or if the indices in the file keys\n                    do not correspond to valid positions in the reconstructed AIChatRequest object.\n    \"\"\"\n    json_content = form[\"json\"]\n    chat_request = AIChatRequest.model_validate_json(json_content)\n\n    file_positions = sorted([get_file_position(file_key) for file_key in files])\n    for message_index, file_index, file_key in file_positions:\n        file = files[file_key]\n\n        if len(chat_request.messages) <= message_index:\n            raise ValueError(f\"Invalid message index: {file_key}\")\n\n        if chat_request.messages[message_index].files is None:\n            chat_request.messages[message_index].files = []\n\n        if len(chat_request.messages[message_index].files) != file_index:\n            raise ValueError(f\"Invalid file index: {file_key}\")\n\n        chat_request.messages[message_index].files.append(AIChatFile(content_type=file.content_type, data=file.read()))\n    return chat_request\n\n\ndef to_openai_message(chat_message: AIChatMessage):\n    if chat_message.files is None:\n        return {\n            \"role\": chat_message.role.value,\n            \"content\": chat_message.content,\n        }\n\n    def encode_file_to_data_url(file: AIChatFile):\n        base64_data = base64.b64encode(file.data).decode(\"utf-8\")\n        return f\"data:{file.content_type};base64,{base64_data}\"\n\n    images = [\n        {\"type\": \"image_url\", \"image_url\": {\"url\": encode_file_to_data_url(file)}}\n        for file in chat_message.files\n        if file.content_type.startswith(\"image/\")\n    ]\n    return {\n        \"role\": chat_message.role.value,\n        \"content\": [{\"type\": \"text\", \"text\": chat_message.content}] + images,\n    }\n\n\n@app.route(\"/api/chat/\", methods=[\"POST\"])\nasync def process_message():\n    try:\n        if request.content_type.startswith(\"multipart/form-data\"):\n            form = await request.form\n            files = await request.files\n            chat_request = reconstruct_multipart_request(form, files)\n        elif request.content_type.startswith(\"application/json\"):\n            chat_request_data = await request.data\n            chat_request = AIChatRequest.model_validate_json(chat_request_data)\n        else:\n            return jsonify({\"error\": \"Unsupported Media Type\"}), 415\n        completion = await client.chat.completions.create(\n            model=AZURE_OPENAI_DEPLOYMENT,\n            messages=[to_openai_message(message) for message in chat_request.messages],\n        )\n\n        message = completion.choices[0].message\n        response = AIChatCompletion(\n            message=AIChatMessage(\n                role=AIChatRole(message.role),\n                content=message.content,\n            ),\n        )\n        return jsonify(response.model_dump())\n    except Exception as e:\n        return (\n            jsonify(AIChatError(code=\"internal_error\", message=str(e)).model_dump()),\n            500,\n        )\n\n\ndef object_to_json_line(obj: BaseModel):\n    return f\"{obj.model_dump_json()}\\r\\n\"\n\n\n@app.route(\"/api/chat/stream\", methods=[\"POST\"])\nasync def process_message_stream():\n    @stream_with_context\n    async def async_generator():\n        try:\n            if request.content_type.startswith(\"multipart/form-data\"):\n                form = await request.form\n                files = await request.files\n                chat_request = reconstruct_multipart_request(form, files)\n            elif request.content_type.startswith(\"application/json\"):\n                chat_request_data = await request.data\n                chat_request = AIChatRequest.model_validate_json(chat_request_data)\n            else:\n                yield object_to_json_line(AIChatError(code=\"unsupported_media_type\", message=\"Unsupported Media Type\"))\n                return\n\n            stream = await client.chat.completions.create(\n                model=AZURE_OPENAI_DEPLOYMENT,\n                stream=True,\n                messages=[to_openai_message(message) for message in chat_request.messages],\n            )\n\n            async for chunk in stream:\n                if len(chunk.choices) == 0:\n                    continue\n                delta = chunk.choices[0].delta\n                response_chunk = AIChatCompletionDelta(\n                    delta=AIChatMessageDelta(\n                        content=delta.content,\n                        role=delta.role,\n                    ),\n                )\n                yield object_to_json_line(response_chunk)\n\n        except Exception as e:\n            error = AIChatError(code=\"internal_error\", message=str(e))\n            yield object_to_json_line(error)\n\n    return async_generator(), 200, {\"Content-Type\": \"application/jsonl\"}\n\n\nif __name__ == \"__main__\":\n    run()\n"
  },
  {
    "path": "samples/backend/python/quart/model/__init__.py",
    "content": "from .model import (\n    AIChatCompletion,\n    AIChatCompletionDelta,\n    AIChatCompletionOptions,\n    AIChatError,\n    AIChatErrorResponse,\n    AIChatFile,\n    AIChatMessage,\n    AIChatMessageDelta,\n    AIChatRequest,\n    AIChatRole,\n)\n\n__all__ = [\n    \"AIChatRequest\",\n    \"AIChatErrorResponse\",\n    \"AIChatCompletion\",\n    \"AIChatCompletionDelta\",\n    \"AIChatCompletionOptions\",\n    \"AIChatError\",\n    \"AIChatFile\",\n    \"AIChatMessage\",\n    \"AIChatMessageDelta\",\n    \"AIChatRole\",\n]\n"
  },
  {
    "path": "samples/backend/python/quart/model/model.py",
    "content": "from enum import Enum\nfrom typing import Any, Optional\n\nfrom pydantic import BaseModel, ConfigDict, Field\nfrom pydantic.alias_generators import to_camel\n\n\nclass ChatModel(BaseModel):\n    model_config = ConfigDict(\n        alias_generator=to_camel,\n        populate_by_name=True,\n        from_attributes=True,\n    )\n\n\nclass AIChatRole(str, Enum):\n    USER = \"user\"\n    ASSISTANT = \"assistant\"\n    SYSTEM = \"system\"\n\n\nclass AIChatFile(ChatModel):\n    content_type: str = Field(serialization_alias=\"contentType\")\n    data: bytes\n\n\nclass AIChatMessage(ChatModel):\n    role: AIChatRole\n    content: str\n    context: Optional[dict[str, Any]] = None\n    files: Optional[list[AIChatFile]] = None\n\n\nclass AIChatMessageDelta(ChatModel):\n    role: Optional[AIChatRole] = None\n    content: Optional[str] = None\n    context: Optional[dict[str, Any]] = None\n\n\nclass AIChatCompletion(ChatModel):\n    message: AIChatMessage\n    session_state: Optional[Any] = Field(serialization_alias=\"sessionState\", default=None)\n    context: Optional[dict[str, Any]] = None\n\n\nclass AIChatCompletionDelta(ChatModel):\n    delta: AIChatMessageDelta\n    session_state: Optional[Any] = Field(serialization_alias=\"sessionState\", default=None)\n    context: Optional[dict[str, Any]] = None\n\n\nclass AIChatCompletionOptions(ChatModel):\n    context: Optional[dict[str, Any]] = None\n    session_state: Optional[Any] = Field(serialization_alias=\"sessionState\", default=None)\n\n\nclass AIChatError(ChatModel):\n    code: str\n    message: str\n\n\nclass AIChatErrorResponse(ChatModel):\n    error: AIChatError\n\n\nclass AIChatRequest(ChatModel):\n    messages: list[AIChatMessage]\n    session_state: Optional[Any] = Field(serialization_alias=\"sessionState\", default=None)\n    context: Optional[bytes] = None\n"
  },
  {
    "path": "samples/backend/python/quart/pyproject.toml",
    "content": "[tool.ruff]\nline-length = 120\ntarget-version = \"py312\"\n\n[tool.ruff.lint]\nselect = [\"E\", \"F\", \"I\", \"UP\"]\nextend-ignore = [\"UP007\"]\n\n[tool.black]\nline-length = 120\ntarget-version = [\"py312\"]\n"
  },
  {
    "path": "samples/backend/python/quart/requirements-dev.txt",
    "content": "-r requirements.txt\nruff\nblack"
  },
  {
    "path": "samples/backend/python/quart/requirements.txt",
    "content": "openai\nquart[dotenv]\nazure-identity\npydantic"
  },
  {
    "path": "samples/frontend/js/react/.eslintrc.cjs",
    "content": "module.exports = {\n  root: true,\n  env: { browser: true, es2020: true },\n  extends: [\n    'eslint:recommended',\n    'plugin:@typescript-eslint/recommended',\n    'plugin:react-hooks/recommended',\n  ],\n  ignorePatterns: ['dist', '.eslintrc.cjs'],\n  parser: '@typescript-eslint/parser',\n  plugins: ['react-refresh'],\n  rules: {\n    'react-refresh/only-export-components': [\n      'warn',\n      { allowConstantExport: true },\n    ],\n  },\n}\n"
  },
  {
    "path": "samples/frontend/js/react/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\ndist\ndist-ssr\n*.local\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n.DS_Store\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n"
  },
  {
    "path": "samples/frontend/js/react/README.md",
    "content": "# Chat Protocol\n\nNow that you are here, you can start chatting with your endpoint, just type and press the **`Send`** button or just press **`Shift+Enter`** to send your message.\n\n## Installation\n\nTo integrate the chat protocol in your project, install the `@microsoft/ai-chat-protocol` package using npm:\n\n```bash\nnpm install -s @microsoft/ai-chat-protocol\n```\n\n## Usage\n\n### Streaming Requests\n\nFor streaming requests, use the `getStreamedCompletion` method. Here's an example:\n\n```typescript\ntry {\n  const result = await client.getStreamedCompletion([message], { sessionState: sessionState });\n\n  for await (const response of result) {\n    // Note: It is expected that you update your sessionState with the value you receive from your endpoint.\n    // Handle your streaming responses here.\n  }\n} catch (e) {\n  if (isChatError(e)) {\n    // Handle your chat error here.\n  }\n}\n```\n\n### Non-Streaming Requests\n\nFor non-streaming requests, you can use the `getCompletion` method. Here's an example:\n\n```typescript\ntry {\n  const result = await client.getCompletion([message], { sessionState: sessionState });\n  // Handle your result here.\n} catch (e) {\n  if (isChatError(e)) {\n    // Handle your chat error here.\n  }\n}\n```\n"
  },
  {
    "path": "samples/frontend/js/react/index.html",
    "content": "<!doctype html>\n<html lang=\"en\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" type=\"image/svg+xml\" href=\"/icon.svg\" />\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n    <title>Chat Protocol Sample</title>\n  </head>\n  <body>\n    <div id=\"root\"></div>\n    <script type=\"module\" src=\"/src/main.tsx\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "samples/frontend/js/react/package.json",
    "content": "{\n  \"name\": \"chat-sample-react\",\n  \"private\": true,\n  \"version\": \"0.0.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"dev\": \"vite --host\",\n    \"build\": \"tsc && vite build\",\n    \"lint\": \"eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0\",\n    \"preview\": \"vite preview\",\n    \"format\": \"prettier --write src\"\n  },\n  \"dependencies\": {\n    \"@fluentui/react-components\": \"9.46.8\",\n    \"@microsoft/ai-chat-protocol\": \"^1.0.0-beta.20240610.1\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"react-markdown\": \"^9.0.1\",\n    \"react-syntax-highlighter\": \"^15.5.0\",\n    \"react-textarea-autosize\": \"^8.5.3\",\n    \"remark-gfm\": \"^4.0.0\"\n  },\n  \"devDependencies\": {\n    \"@types/react\": \"^18.2.64\",\n    \"@types/react-dom\": \"^18.2.21\",\n    \"@typescript-eslint/eslint-plugin\": \"^7.1.1\",\n    \"@typescript-eslint/parser\": \"^7.1.1\",\n    \"@vitejs/plugin-react\": \"^4.2.1\",\n    \"eslint\": \"^8.57.0\",\n    \"eslint-plugin-react-hooks\": \"^4.6.2\",\n    \"eslint-plugin-react-refresh\": \"^0.4.7\",\n    \"prettier\": \"^3.2.5\",\n    \"typescript\": \"^5.2.2\",\n    \"vite\": \"^5.1.6\"\n  }\n}\n"
  },
  {
    "path": "samples/frontend/js/react/src/App.module.css",
    "content": "/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n */\n\nhtml,\nbody {\n  margin: 0;\n  padding: 0;\n  height: 100%;\n}\n\n.appContainer {\n  display: flex;\n  margin: 0;\n  padding: 0;\n  height: 100vh;\n  overflow: auto;\n}\n"
  },
  {
    "path": "samples/frontend/js/react/src/App.tsx",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { FluentProvider, webLightTheme } from \"@fluentui/react-components\";\nimport Chat from \"./Chat.tsx\";\nimport Readme from \"./Readme.tsx\";\nimport styles from \"./App.module.css\";\n\nfunction App() {\n  return (\n    <FluentProvider theme={webLightTheme}>\n      <div className={styles.appContainer}>\n        <Chat style={{ flex: 1 }} />\n        <Readme style={{ flex: 1 }} />\n      </div>\n    </FluentProvider>\n  );\n}\n\nexport default App;\n"
  },
  {
    "path": "samples/frontend/js/react/src/Chat.module.css",
    "content": "/**\n * Copyright (c) Microsoft Corporation.\n * Licensed under the MIT License.\n */\n\n.chatWindow {\n  display: flex;\n  flex-direction: column;\n  height: 100%;\n}\n\n.messages {\n  flex-grow: 1;\n  overflow-y: auto;\n  padding: 10px;\n}\n\n.userMessage {\n  display: flex;\n  justify-content: flex-end;\n}\n\n.assistantMessage {\n  display: flex;\n  justify-content: flex-start;\n}\n\n.messageBubble {\n  max-width: 60%;\n  margin: 5px;\n  padding: 10px;\n  border-radius: 10px;\n}\n\n.userMessage .messageBubble {\n  background-color: #dcf8c6;\n  /* light green */\n}\n\n.assistantMessage .messageBubble {\n  background-color: #ece5dd;\n  /* light gray */\n}\n\n.inputArea {\n  display: flex;\n  border-top: 1px solid #ece5dd;\n  padding: 10px;\n  flex-shrink: 0;\n  align-items: flex-end;\n}\n\n.inputArea textarea {\n  flex-grow: 1;\n  border: none;\n  border-radius: 20px;\n  padding: 10px;\n  margin-right: 10px;\n  resize: none;\n}\n\n.inputArea > div {\n  display: flex;\n  gap: 10px;\n}\n\n.inputArea button {\n  margin-right: 10px;\n}\n\n.caution {\n  padding: 10px 10px 10px 40px;\n  border: 1px solid #d1d5da;\n  background-color: #fdd;\n  background-image: url(\"./assets/caution.svg\");\n  background-repeat: no-repeat;\n  background-position: 10px center;\n  background-size: 20px;\n  color: #24292e;\n  font-weight: 600;\n  display: flex;\n  align-items: center;\n  border-radius: 5px;\n  width: 80%;\n  margin-left: auto;\n  margin-right: auto;\n}\n\n.buttons {\n  display: flex;\n  align-items: center;\n}\n"
  },
  {
    "path": "samples/frontend/js/react/src/Chat.tsx",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport { Button, ToggleButton } from \"@fluentui/react-components\";\nimport {\n  AIChatMessage,\n  AIChatProtocolClient,\n  AIChatError,\n} from \"@microsoft/ai-chat-protocol\";\nimport { useEffect, useId, useRef, useState } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport TextareaAutosize from \"react-textarea-autosize\";\nimport styles from \"./Chat.module.css\";\nimport gfm from \"remark-gfm\";\n\ntype ChatEntry = (AIChatMessage & { dataUrl?: string }) | AIChatError;\n\nfunction isChatError(entry: unknown): entry is AIChatError {\n  return (entry as AIChatError).code !== undefined;\n}\n\ninterface FileInput {\n  data: Uint8Array;\n  name: string;\n  type: string;\n}\n\nfunction toBase64DataUrl(\n  arr?: Uint8Array,\n  contentType?: string,\n): Promise<string | undefined> {\n  return new Promise<string | undefined>((resolve, reject) => {\n    if (!arr) {\n      resolve(undefined);\n      return;\n    }\n    const blob = new Blob([arr], { type: contentType });\n    const reader = new FileReader();\n\n    reader.onerror = reject;\n    reader.onload = (event) => {\n      resolve(event.target?.result as string);\n    };\n    reader.readAsDataURL(blob);\n  });\n}\n\nexport default function Chat({ style }: { style: React.CSSProperties }) {\n  const client = new AIChatProtocolClient(\"/api/chat/\");\n\n  const [messages, setMessages] = useState<ChatEntry[]>([]);\n  const [input, setInput] = useState<string>(\"\");\n  const [streaming, setStreaming] = useState<boolean>(false);\n  const inputId = useId();\n  const [sessionState, setSessionState] = useState<unknown>(undefined);\n  const messagesEndRef = useRef<HTMLDivElement>(null);\n  const [selectedFile, setSelectedFile] = useState<FileInput | undefined>(\n    undefined,\n  );\n  const fileInputRef = useRef<HTMLInputElement>(null);\n\n  function isArrayBuffer(value: unknown): value is ArrayBuffer {\n    return value instanceof ArrayBuffer;\n  }\n\n  const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {\n    if (e.target?.files && e.target.files.length > 0) {\n      const file = e.target.files[0];\n      const reader = new FileReader();\n\n      reader.onload = (loadEvent) => {\n        const arrayBuffer = loadEvent!.target!.result;\n        if (!isArrayBuffer(arrayBuffer)) {\n          setSelectedFile(undefined);\n          return;\n        }\n        const fileUint8Array = new Uint8Array(arrayBuffer);\n        setSelectedFile({\n          data: fileUint8Array,\n          name: file.name,\n          type: file.type,\n        });\n      };\n\n      reader.readAsArrayBuffer(file);\n    }\n  };\n\n  const clearSelectedFile = () => {\n    setSelectedFile(undefined);\n    if (fileInputRef.current) {\n      fileInputRef.current.value = \"\"; // Reset file input\n    }\n  };\n\n  const scrollToBottom = () => {\n    messagesEndRef.current?.scrollIntoView({ behavior: \"smooth\" });\n  };\n  useEffect(scrollToBottom, [messages]);\n\n  const sendMessage = async () => {\n    const message: AIChatMessage = {\n      role: \"user\",\n      content: input,\n      files: selectedFile\n        ? [\n            {\n              data: selectedFile.data,\n              contentType: selectedFile.type,\n            },\n          ]\n        : undefined,\n    };\n    const dataUrl = await toBase64DataUrl(\n      selectedFile?.data,\n      selectedFile?.type,\n    );\n    const updatedMessages: ChatEntry[] = [\n      ...messages,\n      {\n        ...message,\n        files: undefined,\n        dataUrl: dataUrl,\n      },\n    ];\n    setMessages(updatedMessages);\n    setInput(\"\");\n    setSelectedFile(undefined);\n    try {\n      if (streaming) {\n        const result = await client.getStreamedCompletion([message], {\n          sessionState: sessionState,\n        });\n        const latestMessage: AIChatMessage = { content: \"\", role: \"assistant\" };\n        for await (const response of result) {\n          if (response.sessionState) {\n            setSessionState(response.sessionState);\n          }\n          if (!response.delta) {\n            continue;\n          }\n          if (response.delta.role) {\n            latestMessage.role = response.delta.role;\n          }\n          if (response.delta.content) {\n            latestMessage.content += response.delta.content;\n            setMessages([...updatedMessages, latestMessage]);\n          }\n        }\n      } else {\n        const result = await client.getCompletion([message], {\n          sessionState: sessionState,\n        });\n        setSessionState(result.sessionState);\n        setMessages([...updatedMessages, result.message]);\n      }\n    } catch (e) {\n      if (isChatError(e)) {\n        setMessages([...updatedMessages, e]);\n      }\n    }\n  };\n\n  const getClassName = (message: ChatEntry) => {\n    if (isChatError(message)) {\n      return styles.caution;\n    }\n    return message.role === \"user\"\n      ? styles.userMessage\n      : styles.assistantMessage;\n  };\n\n  const getErrorMessage = (message: AIChatError) => {\n    return `${message.code}: ${message.message}`;\n  };\n\n  return (\n    <div className={styles.chatWindow} style={style}>\n      <div className={styles.messages}>\n        {messages.map((message) => (\n          <div key={crypto.randomUUID()} className={getClassName(message)}>\n            {isChatError(message) ? (\n              <>{getErrorMessage(message)}</>\n            ) : (\n              <>\n                <div className={styles.messageBubble}>\n                  <ReactMarkdown remarkPlugins={[gfm]}>\n                    {message.content}\n                  </ReactMarkdown>\n                  {message.dataUrl && (\n                    <img\n                      src={message.dataUrl}\n                      style={{\n                        maxHeight: \"300px\",\n                        maxWidth: \"100%\",\n                        height: \"auto\",\n                        width: \"auto\",\n                      }}\n                      alt=\"Message Attachment\"\n                    />\n                  )}\n                </div>\n              </>\n            )}\n          </div>\n        ))}\n        <div ref={messagesEndRef} />\n      </div>\n      <div className={styles.inputArea}>\n        <TextareaAutosize\n          id={inputId}\n          value={input}\n          onChange={(e) => setInput(e.target.value)}\n          onKeyDown={(e) => {\n            if (e.key === \"Enter\" && e.shiftKey) {\n              e.preventDefault();\n              sendMessage();\n            }\n          }}\n          minRows={1}\n          maxRows={4}\n        />\n        {selectedFile && (\n          <div>\n            <span>File: {selectedFile.name}</span>\n            <button onClick={clearSelectedFile}>Clear</button>\n          </div>\n        )}\n        <input\n          type=\"file\"\n          accept=\"image/*\"\n          style={{ display: \"none\" }}\n          ref={fileInputRef} // Create this ref using useRef in your component\n          onChange={handleFileChange} // Implement this function to handle file selection\n        />\n        <Button onClick={() => fileInputRef?.current?.click()}>Attach</Button>\n        <Button onClick={sendMessage}>Send</Button>\n        <ToggleButton\n          checked={streaming}\n          onClick={() => setStreaming(!streaming)}\n        >\n          Streaming\n        </ToggleButton>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "samples/frontend/js/react/src/Readme.tsx",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport React, { useEffect, useState } from \"react\";\nimport ReactMarkdown from \"react-markdown\";\nimport gfm from \"remark-gfm\";\nimport { Prism } from \"react-syntax-highlighter\";\nimport readmeContent from \"../README.md\";\n\nconst components = {\n  code(props: any) {\n    const { children, className, node, ...rest } = props;\n    const match = /language-(\\w+)/.exec(className || \"\");\n    return match ? (\n      <Prism\n        {...rest}\n        PreTag=\"div\"\n        children={String(children).replace(/\\n$/, \"\")}\n        language={match[1]}\n      />\n    ) : (\n      <code {...rest} className={className}>\n        {children}\n      </code>\n    );\n  },\n};\n\nfunction Readme({ style }: { style: React.CSSProperties }) {\n  const [markdown, setMarkdown] = useState(\"\");\n  useEffect(() => {\n    fetch(readmeContent)\n      .then((response) => response.text())\n      .then((text) => setMarkdown(text));\n  }, []);\n  return (\n    <div style={{ overflowY: \"auto\", ...style }}>\n      <ReactMarkdown components={components} remarkPlugins={[gfm]}>\n        {markdown}\n      </ReactMarkdown>\n    </div>\n  );\n}\n\nexport default Readme;\n"
  },
  {
    "path": "samples/frontend/js/react/src/globals.d.ts",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\ndeclare module \"*.md\";\n"
  },
  {
    "path": "samples/frontend/js/react/src/main.tsx",
    "content": "// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\nimport React from \"react\";\nimport ReactDOM from \"react-dom/client\";\nimport App from \"./App.tsx\";\n\nReactDOM.createRoot(document.getElementById(\"root\")!).render(\n  <React.StrictMode>\n    <App />\n  </React.StrictMode>,\n);\n"
  },
  {
    "path": "samples/frontend/js/react/src/vite-env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n"
  },
  {
    "path": "samples/frontend/js/react/tsconfig.json",
    "content": "{\n  \"compilerOptions\": {\n    \"target\": \"ES2020\",\n    \"useDefineForClassFields\": true,\n    \"lib\": [\"ES2020\", \"DOM\", \"DOM.Iterable\"],\n    \"module\": \"ESNext\",\n    \"skipLibCheck\": true,\n\n    /* Bundler mode */\n    \"moduleResolution\": \"bundler\",\n    \"allowImportingTsExtensions\": true,\n    \"resolveJsonModule\": true,\n    \"isolatedModules\": true,\n    \"noEmit\": true,\n    \"jsx\": \"react-jsx\",\n\n    /* Linting */\n    \"strict\": true,\n    \"noUnusedLocals\": true,\n    \"noUnusedParameters\": true,\n    \"noFallthroughCasesInSwitch\": true\n  },\n  \"include\": [\"src\"],\n  \"references\": [{ \"path\": \"./tsconfig.node.json\" }]\n}\n"
  },
  {
    "path": "samples/frontend/js/react/tsconfig.node.json",
    "content": "{\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"skipLibCheck\": true,\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"bundler\",\n    \"allowSyntheticDefaultImports\": true,\n    \"strict\": true\n  },\n  \"include\": [\"vite.config.ts\"]\n}\n"
  },
  {
    "path": "samples/frontend/js/react/vite.config.ts",
    "content": "import { defineConfig } from 'vite'\nimport react from '@vitejs/plugin-react'\n\n// https://vitejs.dev/config/\nexport default defineConfig({\n  plugins: [react()],\n  assetsInclude: ['**/*.md'],\n  server: {\n    proxy: {\n      '/api': {\n        target: 'http://localhost:3000',\n      },\n    },\n  },\n});\n"
  },
  {
    "path": "sdk/js/.gitignore",
    "content": "!packages/*\ndist\n.tshy\n.tshy-build\n*.tgz\n"
  },
  {
    "path": "spec/.gitignore",
    "content": "node_modules\ntsp-output"
  },
  {
    "path": "spec/README.md",
    "content": "# Microsoft AI Chat Protocol API Specification (Version 2024-05-29)\n\n## Rationale\n\nThe AI Chat Protocol API Specification is an effort to standarize API contracts across AI solutions and languages. By having a unified approach, AI application components become easily compatible and interoperable with one another. Additionally, this allows for a consistent API surface to perform AI evaluations on, reducing the complexity in consuming different AI service endpoints.\n\nIn this directory, the specification is defined via [TypeSpec](https://typespec.io) and available as a human-readable document via this README.\n\nThis protocol is inspired by the [OpenAI ChatCompletion API](https://platform.openai.com/docs/guides/text-generation/chat-completions-api) but contains additional fields required for a chat application. The original specification was created by [Pamela Fox](https://github.com/pamelafox) and [Natalia Venditto](https://github.com/anfibiacreativa).\n\nTable of contents:\n\n* [HTTP requests to AI chat app endpoints](#http-requests-to-ai-chat-app-endpoints)\n  * [Example request context](#example-request-context)\n* [HTTP responses from AI Chat App endpoints](#http-responses-from-ai-chat-app-endpoints)\n  * [Non-streaming response](#non-streaming-response)\n    * [Successful response](#successful-response)\n    * [Error response](#error-response)\n  * [Streaming response](#streaming-response)\n    * [Successful streamed response](#successful-streamed-response)\n    * [Error in streamed response](#error-in-streamed-response)\n  * [Answer formatting](#answer-formatting)\n  * [Example response context](#example-response-context)\n\n## HTTP requests to AI Chat App endpoints\n\nAn HTTP request should always be a POST request, with the following headers:\n\n* `Content-Type: application/json`\n* `Authorization: Bearer <ID token>`: _Optional._ For applications that require authentication.\n\nThe recommended path is `chat` for a non-streaming request and `chat/stream` for a streaming request.\n\nThe body of the request can contain these properties, in JSON format:\n\n* `\"messages\"`: A list of messages, each containing \"content\" and \"role\", where \"role\" may be \"assistant\" or \"user\". A single-turn chat app may only contain 1 message, while a multi-turn chat app may contain multiple messages.\n* `\"context\"`: _Optional_. An object containing any additional context about the request, such as the temperature to use for the LLM. Each application may define its own context properties. See [example request context properties](#example-request-context).\n* `\"sessionState\"`: _Optional._ An object containing the \"memory\" for the chat app, such as a user ID.\n\n### Usage example\n\nThe example belows represents a valid and compliant request body to the chat app endpoints:\n\n```json\n{\n    \"messages\": [\n        {\n            \"content\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n            \"role\": \"user\"\n        }\n    ],\n    \"context\": {},\n    \"sessionState\": null\n}\n```\n\n### Example request context\n\nThe request context object can contain any properties. Here are some common properties that may be of use depending on your AI application:\n\n* `\"overrides\"`: An object containing settings for the chat application.\n  * `\"temperature\"`: The temperature to use for the LLM.\n  * `\"top\"`: The number of results to return from the search engine.\n  * `\"retrieval_mode\"`: The mode to use for the search engine. Can be \"hybrid\", \"vectors\", or \"text\".\n  * `\"semantic_ranker\"`: _Specific to Azure AI Search_. Whether to use the semantic ranker for the search engine.\n  * `\"semantic_captions\"`: _Specific to Azure AI Search_. Whether to use semantic captions for the search engine.\n  * `\"suggest_followup_questions\"`: Whether to suggest follow-up questions for the chat app.\n  * `\"use_oid_security_filter\"`: Whether to use the OID security filter for the search engine.\n  * `\"use_groups_security_filter\"`: Whether to use the groups security filter for the search engine.\n  * `\"vector_fields\"`: A list of fields to search for the vector search engine.\n  * `\"use_gpt4v\"`: Whether to use a GPT-4V approach.\n  * `\"gpt4v_input\"`: The input type to use for a GPT-4V approach. Can be \"text\", \"textAndImages\", or \"images\".\n\nExample of the overrides object:\n\n```json\n\"overrides\": {\n    \"top\": 3,\n    \"retrieval_mode\": \"text\",\n    \"semantic_ranker\": false,\n    \"semantic_captions\": false,\n    \"suggest_followup_questions\": false,\n    \"use_oid_security_filter\": false,\n    \"use_groups_security_filter\": false,\n    \"vector_fields\": [\"embedding\"],\n    \"use_gpt4v\": false,\n    \"gpt4v_input\": \"textAndImages\"\n}\n```\n\n## HTTP responses from AI Chat App endpoints\n\nThe HTTP response should either be JSON for a non-streaming response, or [JSON Lines](https://github.com/wardi/jsonlines) (\"jsonl\") for a streaming response.\n\n### Non-streaming response\n\nThe response should contain this header:\n\n* `Content-Type: application/json`\n\n#### Successful response\n\nA successful response should have a status code of 200, and the body should contain a JSON object with the following properties:\n\n* `\"message\"`: An object containing the actual content of the response.  See [Answer formatting](#answer-formatting). _Comes from the [OpenAI chat completion object](https://platform.openai.com/docs/api-reference/chat/object)._\n* `\"context\"`: _Optional_. An object containing additional details needed for the chat app. Each application can define its own properties. See [example context properties for responses](#example-response-context).\n* `\"sessionState\"`: _Optional_. An object containing the \"memory\" for the chat app, such as a user ID.\n\nHere's an example JSON response:\n\n```json\n{\n    \"message\": {\n        \"content\": \"There is no specific information provided about what is included in the Northwind Health Plus plan that is not in the standard plan. It is recommended to read the plan details carefully and ask questions to understand the specific benefits of the Northwind Health Plus plan [Northwind_Standard_Benefits_Details.pdf#page=91].\",\n        \"function_call\": null,\n        \"role\": \"assistant\",\n        \"tool_calls\": null\n    },\n    \"context\": {\n        \"data_points\": {\n            \"text\": [\n                \"Northwind_Standard_Benefits_Details.pdf#page=91:    Tips for Avoiding Intentionally False Or Misleading Statements:   When it comes to understanding a health plan, it is important to be aware of any  intentiona lly false or misleading statements that the plan provider may make...(truncated)\",\n                \"Northwind_Standard_Benefits_Details.pdf#page=91:  It is important to  research the providers and services offered in the Northwind Standard plan in order to  determine if the providers and services offered are sufficient for the employee's needs...(truncated)\",\n                \"Northwind_Standard_Benefits_Details.pdf#page=17:  Employees should keep track of their claims and follow up with  Northwind Health if a claim is not processed in a timely manner...(truncated)\"\n            ]\n        },\n        \"thoughts\": [\n            {\n                \"description\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n                \"props\": null,\n                \"title\": \"Original user query\"\n            },\n            {\n                \"description\": \"Northwind Health Plus plan coverage details compared to standard plan\",\n                \"props\": {\n                    \"has_vector\": false,\n                    \"use_semantic_captions\": false\n                },\n                \"title\": \"Generated search query\"\n            },\n            {\n                \"description\": [\n                    {\n                        \"captions\": [],\n                        \"category\": null,\n                        \"content\": \"  \\nTips for Avoiding Intentionally False Or Misleading Statements:  \\nWhen it comes to understanding a health plan, it is important to be aware of any \\nintentiona lly false or misleading statements that the plan provider may make...(truncated)\",\n                        \"embedding\": null,\n                        \"groups\": [],\n                        \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-233\",\n                        \"imageEmbedding\": null,\n                        \"oids\": [],\n                        \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                        \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=91\"\n                    },\n                    {\n                        \"captions\": [],\n                        \"category\": null,\n                        \"content\": \" It is important to \\nresearch the providers and services offered in the Northwind Standard plan i n order to \\ndetermine if the providers and services offered are sufficient for the employee's needs...(truncated)\",\n                        \"embedding\": null,\n                        \"groups\": [],\n                        \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-232\",\n                        \"imageEmbedding\": null,\n                        \"oids\": [],\n                        \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                        \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=91\"\n                    },\n                    {\n                        \"captions\": [],\n                        \"category\": null,\n                        \"content\": \" Employees should keep track of their claims and follow up with \\nNorthwind Health if a claim is not processed in a timely manner...(truncated)\",\n                        \"embedding\": null,\n                        \"groups\": [],\n                        \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-41\",\n                        \"imageEmbedding\": null,\n                        \"oids\": [],\n                        \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                        \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=17\"\n                    }\n                ],\n                \"props\": null,\n                \"title\": \"Results\"\n            },\n            {\n                \"description\": [\n                    \"{'role': 'system', 'content': \\\"Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.\\n        Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question.\\n        For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.\\n        Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].\\n        \\n        \\n        \\\"}\",\n                    \"{'role': 'user', 'content': \\\"What is included in my Northwind Health Plus plan that is not in standard?\\n\\nSources:\\nNorthwind_Standard_Benefits_Details.pdf#page=91:    Tips for Avoiding Intentionally False Or Misleading Statements:   When it comes to understanding a health plan, it is important to be aware of any  intentiona lly false or misleading statements that the plan provider may make. To avoid  being misled, employees should follow the following tips:(truncated)\n                    \\nNorthwind_Standard_Benefits_Details.pdf#page=91:  It is important to  research the providers and services offered in the Northwind Standard plan in order to  determine if the providers and services offered are sufficient for the employee's needs.   In addition, Northwind Health may make claims that their plan offers low or no cost  prescription drugs..(truncated)\\\"}\"\n                ],\n                \"props\": null,\n                \"title\": \"Prompt\"\n            }\n        ]\n    },\n    \"sessionState\": null\n}\n```\n\n#### Error response\n\nAn error response should have a status code of 400 or 500, and the body should contain a JSON object with the following properties:\n\n* `\"error\"`: A string describing the error.\n\nHere's an example JSON response for a 400-level error:\n\n```json\n{\n    \"error\": \"Your message contains content that was flagged by the OpenAI content filter.\"\n}\n```\n\nHere's an example JSON response for a 500-level error:\n\n```json\n{\n    \"error\": \"The app encountered an error processing your request.\\nIf you are an administrator of the app, view the full error in the logs.\"\n}\n```\n\n### Streaming response\n\nThe response should contain this header:\n\n* `Content-Type: application/jsonl`\n\n#### Successful streamed response\n\nA successful response should have a status code of 200.\nThe body of the response should contain a sequence of JSON objects, each representing a chunk of the response.\nThe first chunk contains the `context` property, since that is available before the answer, and subsequent chunks contain parts of the answer to the question.\n\nEach JSON object should contain the following properties:\n\n* `\"delta\"`: An object containing the actual content of the response, a token at a time. See [Answer formatting](#answer-formatting). _Comes from the [OpenAI chat completion chunk object](https://platform.openai.com/docs/api-reference/chat/streaming)._\n* `\"context\"`: _Optional_. An object containing additional details needed for the chat app. Each application can define its own properties. See [example response context properties](#example-response-context).\n* `\"sessionState\"`: _Optional_. An object containing the \"memory\" for the chat app, such as a user ID.\n\nHere's an example of the first three JSON objects in a streaming response:\n\n```json\n{\n    \"delta\": {\n        \"role\": \"assistant\"\n    },\n    \"context\": {\n        \"data_points\": {\n            \"text\": [\n                \"Benefit_Options.pdf#page=3:  The plans also cover preventive care services such as mammograms, colonoscopies, and  other cancer screenings...(truncated)\",\n                \"Benefit_Options.pdf#page=3:   Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays,  doctor visits,...(truncated)\",\n                \"Benefit_Options.pdf#page=3:  With Northwind Health Plus, you can choose  from a variety of in -network providers, including primary care physicians,...(truncated)\"\n            ]\n        },\n        \"thoughts\": [\n            {\n                \"title\": \"Original user query\",\n                \"description\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n                \"props\": null\n            },\n            {\n                \"title\": \"Generated search query\",\n                \"description\": \"Northwind Health Plus plan standard\",\n                \"props\": {\n                    \"use_semantic_captions\": false,\n                    \"has_vector\": false\n                }\n            },\n            {\n                \"title\": \"Results\",\n                \"description\": [\n                    {\n                        \"id\": \"file-Benefit_Options_pdf-42656E656669745F4F7074696F6E732E706466-page-2\",\n                        \"content\": \" The plans also cover preventive care services such as mammograms, colonoscopies, and \\nother cancer screenings...(truncated)\",\n                        \"embedding\": null,\n                        \"imageEmbedding\": null,\n                        \"category\": null,\n                        \"sourcepage\": \"Benefit_Options.pdf#page=3\",\n                        \"sourcefile\": \"Benefit_Options.pdf\",\n                        \"oids\": [],\n                        \"groups\": [],\n                        \"captions\": []\n                    },\n                    {\n                        \"id\": \"file-Benefit_Options_pdf-42656E656669745F4F7074696F6E732E706466-page-3\",\n                        \"content\": \" \\nBoth plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, \\ndoctor visits,...(truncated)\",\n                        \"embedding\": null,\n                        \"imageEmbedding\": null,\n                        \"category\": null,\n                        \"sourcepage\": \"Benefit_Options.pdf#page=3\",\n                        \"sourcefile\": \"Benefit_Options.pdf\",\n                        \"oids\": [],\n                        \"groups\": [],\n                        \"captions\": []\n                    },\n                    {\n                        \"id\": \"file-Benefit_Options_pdf-42656E656669745F4F7074696F6E732E706466-page-1\",\n                        \"content\": \" With Northwind Health Plus, you can choose \\nfrom a variety of in -network providers, including primary care physicians,...(truncated)\",\n                        \"embedding\": null,\n                        \"imageEmbedding\": null,\n                        \"category\": null,\n                        \"sourcepage\": \"Benefit_Options.pdf#page=3\",\n                        \"sourcefile\": \"Benefit_Options.pdf\",\n                        \"oids\": [],\n                        \"groups\": [],\n                        \"captions\": []\n                    }\n                ],\n                \"props\": null\n            },\n            {\n                \"title\": \"Prompt\",\n                \"description\": [\n                    \"{'role': 'system', 'content': \\\"Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.\\\\n        Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question.\\\\n        For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.\\\\n        Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].\\\\n        \\\\n        \\\\n        \\\"}\",\n                    \"{'role': 'user', 'content': 'What is included in my Northwind Health Plus plan that is not in standard?'}\",\n                    \"{'role': 'assistant', 'content': 'There is no specific information provided about what is included in the Northwind Health Plus plan that is not in the standard plan. It is recommended to read the plan details carefully and ask questions to understand the specific benefits of the Northwind Health Plus plan [Northwind_Standard_Benefits_Details.pdf#page=91].'}\",\n                    \"{'role': 'user', 'content': \\\"What is included in my Northwind Health Plus plan that is not in standard?\\\\n\\\\nSources:\\\\nBenefit_Options.pdf#page=3:  The plans also cover preventive care services such as mammograms, colonoscopies, and  other cancer screenings...(truncated)\\\\nBenefit_Options.pdf#page=3:   Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays,  doctor visits,...(truncated)\\\\nBenefit_Options.pdf#page=3:  With Northwind Health Plus, you can choose  from a variety of in -network providers, including primary care physicians,...(truncated)\\\"}\"\n                ],\n                \"props\": null\n            }\n        ]\n    },\n    \"sessionState\": null,\n}{\n    \"delta\": {\n        \"content\": null,\n        \"function_call\": null,\n        \"role\": \"assistant\",\n        \"tool_calls\": null\n    }\n}{\n    \"delta\": {\n        \"content\": \"The\",\n        \"function_call\": null,\n        \"role\": null,\n        \"tool_calls\": null\n    }\n}\n```\n\n#### Error in streamed response\n\nIf an error is encountered before the stream begins, then the response may look like a non-streaming error response. However, if an error is encountered during the stream, then the server will have already sent a 200 response, and will send a chunk with an error object. Typically that would be the last chunk, but it may not be.\n\nHere's an example of an error chunk:\n\n```json\n{\n    \"error\": \"The app encountered an error processing your request.\\nIf you are an administrator of the app, view the full error in the logs.\"\n}\n```\n\n### Answer formatting\n\nTo support the display of citations, the answer from the LLM should contain source information in square brackets, such as `[info1.txt]`.\n\nHere's a full example of an answer with citation:\n\n```text\nThere is no specific information provided about what is included in the Northwind Health Plus plan that is not in the standard plan. It is recommended to read the plan details carefully and ask questions to understand the specific benefits of the Northwind Health Plus plan [Northwind_Standard_Benefits_Details.pdf#page=91].\n```\n\n### Example response context\n\nThe response context object can contain any properties. Here are some common properties that may be of use depending on your AI application along with some best practices:\n\n* `\"followup_questions\"`: A list of follow-up questions to ask the user.\n\n    Example:\n\n    ```json\n    \"followup_questions\": [\n        \"What types of prescription drugs are covered?\",\n        \"Which services have lower out-of-pocket costs?\"\n    ]\n    ```\n\n    If a client receives this property and the user has requested follow-up questions, the client should prompt the user with clickable versions of the questions. [See image](images/followup.png)\n\n* `\"data_points\"`: An object containing text and/or image data chunks, a list in the `\"text\"` or `\"images\"` properties.\n\n    Example:\n\n    ```json\n    \"data_points\": {\n        \"text\": [\n            \"Northwind_Standard_Benefits_Details.pdf#page=91:    Tips for Avoiding Intentionally False Or Misleading Statements:   When it comes to understanding a health plan, it is important to be aware of any intentionally false or misleading statements that the plan provider may make...(truncated)\",\n            \"Northwind_Standard_Benefits_Details.pdf#page=91:  It is important to research the providers and services offered in the Northwind Standard plan in order to  determine if the providers and services offered are sufficient for the employee's needs...(truncated)\",\n            \"Northwind_Standard_Benefits_Details.pdf#page=17:  Employees should keep track of their claims and follow up with  Northwind Health if a claim is not processed in a timely manner...(truncated)\"\n        ]\n    },\n    ```\n\n    Example with images:\n\n    ```json\n    \"data_points\": {\n        \"images\": [\n            {\n                \"detail\": \"auto\",\n                \"url\": \"data:image/png;base64,iVBOR1BORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR4nGMAAQAABQABDQ0tuhsAAAAASUVORK5CYII=\"\n            }\n        ],\n        \"text\": [\n            \"Financial Market Analysis Report 2023-6.png: 3</td><td>1</td></tr></table> Financial markets are interconnected, with movements in one segment often influencing other...(truncated)\"\n        ]\n    },\n    ```\n\n    If a client receives this property, the client should display the data points in a perusable format. [See image](images/data_points.png)\n\n* `\"thoughts\"`: A list describing each step of the backend. Each step should contain:\n  * `\"title\"`: A string describing the step.\n  * `\"description\"`: A string or list of strings describing the step.\n  * `\"props\"`: _Optional_. An object containing additional properties for the step.\n\n    Example:\n\n    ```json\n    \"thoughts\": [\n        {\n            \"title\": \"Original user query\",\n            \"description\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n            \"props\": null\n        },\n        {\n            \"title\": \"Generated search query\",\n            \"description\": \"Northwind Health Plus plan coverage details\",\n            \"props\": {\n                \"has_vector\": false,\n                \"use_semantic_captions\": false\n            }\n        },\n        {\n            \"title\": \"Results\",\n            \"description\": [\n                {\n                    \"captions\": [],\n                    \"category\": null,\n                    \"content\": \"  \\n\\u2022 Understand your coverage limits, and know what services are  covered and what services \\nare not covered...(truncated)\",\n                    \"embedding\": null,\n                    \"groups\": [],\n                    \"id\": \"file-Northwind_Health_Plus_Benefits_Details_pdf-4E6F72746877696E645F4865616C74685F506C75735F42656E65666974735F44657461696C732E706466-page-249\",\n                    \"imageEmbedding\": null,\n                    \"oids\": [],\n                    \"sourcefile\": \"Northwind_Health_Plus_Benefits_Details.pdf\",\n                    \"sourcepage\": \"Northwind_Health_Plus_Benefits_Details.pdf#page=100\"\n                },\n                {\n                    \"captions\": [],\n                    \"category\": null,\n                    \"content\": \" Employees should keep track of their claims and follow up with \\nNorthwind Health if a claim is not processed in a timely manner...(truncated)\",\n                    \"embedding\": null,\n                    \"groups\": [],\n                    \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-41\",\n                    \"imageEmbedding\": null,\n                    \"oids\": [],\n                    \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                    \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=17\"\n                },\n                {\n                    \"captions\": [],\n                    \"category\": null,\n                    \"content\": \" It is important to talk to your doctor or \\nhealth care provider to make su re that you understand the details of the clinical trial before \\nyou decide to participate...(truncated)\",\n                    \"embedding\": null,\n                    \"groups\": [],\n                    \"id\": \"file-Northwind_Health_Plus_Benefits_Details_pdf-4E6F72746877696E645F4865616C74685F506C75735F42656E65666974735F44657461696C732E706466-page-57\",\n                    \"imageEmbedding\": null,\n                    \"oids\": [],\n                    \"sourcefile\": \"Northwind_Health_Plus_Benefits_Details.pdf\",\n                    \"sourcepage\": \"Northwind_Health_Plus_Benefits_Details.pdf#page=24\"\n                }\n            ],\n            \"props\": null\n        },\n        {\n            \"title\": \"Prompt\",\n            \"description\": [\n                \"{'role': 'system', 'content': 'Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.\\\\n        Answer ONLY with the facts listed in the list of sources below. If there isn\\\\'t enough information below, say you don\\\\'t know. Do not generate answers that don\\\\'t use the sources below. If asking a clarifying question to the user would help, ask the question.\\\\n        For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.\\\\n        Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don\\\\'t combine sources, list each source separately, for example [info1.txt][info2.pdf].\\\\n        Generate 3 very brief follow-up questions that the user would likely ask next.\\\\n    Enclose the follow-up questions in double angle brackets. Example:\\\\n    <<Are there exclusions for prescriptions?>>\\\\n    <<Which pharmacies can be ordered from?>>\\\\n    <<What is the limit for over-the-counter medication?>>\\\\n    Do no repeat questions that have already been asked.\\\\n    Make sure the last question ends with \\\">>\\\".\\\\n    \\\\n        \\\\n        '}\",\n                \"{'role': 'user', 'content': 'What is included in my Northwind Health Plus plan that is not in standard?'}\",\n                \"{'role': 'assistant', 'content': 'The Northwind Health Plus plan includes coverage for prescription drugs, but it is important to read the plan details to determine which prescription drugs are covered and what the associated costs are [Northwind_Standard_Benefits_Details.pdf#page=91]. Additionally, employees should select in-network providers to maximize coverage and avoid unexpected costs, submit claims as soon as possible after a service is rendered, and track claims and follow up with Northwind Health if a claim is not processed in a timely manner [Northwind_Standard_Benefits_Details.pdf#page=17].\\\\n\\\\n'}\",\n                \"{'role': 'user', 'content': 'What is included in my Northwind Health Plus plan that is not in standard?\\\\n\\\\nSources:\\\\nNorthwind_Health_Plus_Benefits_Details.pdf#page=100:    \\u2022 Understand your coverage limits, and know what services are  covered and what services  are not covered...(truncated)\\\\nNorthwind_Standard_Benefits_Details.pdf#page=17:  Employees should keep track of their claims and follow up with  Northwind Health if a claim is not processed in a timely manner...(truncated)\\\\nNorthwind_Health_Plus_Benefits_Details.pdf#page=24:  It is important to talk to your doctor or  health care provider..(truncated)'}\"\n            ],\n            \"props\": null\n        }\n    ]\n    ```\n\n    If a client receives this property, the client should display the thoughts in a debug display or to the end-user as specified by the design system. [See image](images/thoughts.png)\n\n## Summary\n\nThe Microsoft AI Chat Protocol API Specification details a consistent pattern for requests and responses to an AI service endpoint, allowing for consistent service consumption and evaluations. Any comments or feedback can be left as an [issue](https://github.com/microsoft/ai-chat-protocol/issues/new).\n"
  },
  {
    "path": "spec/legacy/2024-01-28.md",
    "content": "# HTTP protocol for AI chat apps (Version 2024-01-28)\n\n> Note: This API spec is an older version. If you're adding the AI Chat Protocol to your application, follow the most recent spec located under `/spec`.\n\nWe are standardizing on a common HTTP protocol across AI chat app solutions and tools,\nto make them more compatible with each other. By agreeing on a common protocol, developers\ncan swap different components, like using the Python backend [azure-search-openai-demo](https://github.com/Azure-Samples/azure-search-openai-demo)\nwith the Web Components frontend from [azure-search-openai-javascript](https://github.com/Azure-Samples/azure-search-openai-javascript).\n\nThis protocol is inspired by the [OpenAI ChatCompletion API](https://platform.openai.com/docs/guides/text-generation/chat-completions-api),\nbut contains additional fields required for a chat application.\n\nTable of contents:\n\n* [HTTP requests to AI chat app endpoints](#http-requests-to-ai-chat-app-endpoints)\n  * [Recommended request context](#recommended-request-context)\n* [HTTP responses from RAG chat app endpoints](#http-responses-from-rag-chat-app-endpoints)\n  * [Non-streaming response](#non-streaming-response)\n    * [Successful response](#successful-response)\n    * [Error response](#error-response)\n  * [Streaming response](#streaming-response)\n    * [Successful response](#successful-streamed-response)\n    * [Error response](#error-in-streamed-response)\n  * [Answer formatting](#answer-formatting)\n  * [Recommended response context](#recommended-response-context)\n\n## HTTP requests to AI chat app endpoints\n\nAn HTTP request should always be a POST request, with the following headers:\n\n* `Content-Type: application/json`\n* `Authorization: Bearer <ID token>`: _Optional._ For applications that require authentication.\n\nThe body of the request can contain these properties, in JSON:\n\n* `\"messages\"`: A list of messages, each containing \"content\" and \"role\", where \"role\" may be \"assistant\" or \"user\". A single-turn chat app may only contain 1 message, while a multi-turn chat app may contain multiple messages.\n* `\"stream\"`: A boolean indicating whether the response should be streamed or not.\n* `\"context\"`: _Optional_. An object containing any additional context about the request, such as the temperature to use for the LLM. Each application may define its own context properties. See [recommended request context properties](#recommended-request-context).\n* `\"session_state\"`: _Optional._ An object containing the \"memory\" for the chat app, such as a user ID.\n\nHere's an example JSON request:\n\n```json\n{\n    \"messages\": [\n        {\n            \"content\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n            \"role\": \"user\"\n        }\n    ],\n    \"stream\": false,\n    \"context\": {},\n    \"session_state\": null\n}\n```\n\n### Recommended request context\n\nThe request context object can contain any properties. However, here are some recommended common properties:\n\n* `\"overrides\"`: An object containing settings for the chat application.\n  * `\"temperature\"`: The temperature to use for the LLM.\n  * `\"top\"`: The number of results to return from the search engine.\n  * `\"retrieval_mode\"`: The mode to use for the search engine. Can be \"hybrid\", \"vectors\", or \"text\".\n  * `\"semantic_ranker\"`: _Specific to Azure AI Search_. Whether to use the semantic ranker for the search engine.\n  * `\"semantic_captions\"`: _Specific to Azure AI Search_. Whether to use semantic captions for the search engine.\n  * `\"suggest_followup_questions\"`: Whether to suggest follow-up questions for the chat app.\n  * `\"use_oid_security_filter\"`: Whether to use the OID security filter for the search engine.\n  * `\"use_groups_security_filter\"`: Whether to use the groups security filter for the search engine.\n  * `\"vector_fields\"`: A list of fields to search for the vector search engine.\n  * `\"use_gpt4v\"`: Whether to use a GPT-4V approach.\n  * `\"gpt4v_input\"`: The input type to use for a GPT-4V approach. Can be \"text\", \"textAndImages\", or \"images\".\n\nExample:\n\n```json\n\"overrides\": {\n    \"top\": 3,\n    \"retrieval_mode\": \"text\",\n    \"semantic_ranker\": false,\n    \"semantic_captions\": false,\n    \"suggest_followup_questions\": false,\n    \"use_oid_security_filter\": false,\n    \"use_groups_security_filter\": false,\n    \"vector_fields\": [\"embedding\"],\n    \"use_gpt4v\": false,\n    \"gpt4v_input\": \"textAndImages\"\n}\n```\n\n## HTTP responses from RAG chat app endpoints\n\nThe HTTP response should either be JSON for a non-streaming response, or newline-delimited JSON (\"NDJSON\"/\"jsonlines\") for a streaming response.\n\n### Non-streaming response\n\nThis response is based off the\n[OpenAI chat completion object](https://platform.openai.com/docs/api-reference/chat/object),\nwith additional properties needed to display sources and citations properly.\n\nThe response should contain this header:\n\n* `Content-Type: application/json`\n\n#### Successful response\n\nA successful response should have a status code of 200, and the body should contain a JSON object with the following properties:\n\n* `\"choices\"`: A list of responses from the LLM, typically containing only 1 response as our app sets `n=1` when requesting a completion. Each response contains:\n  * `\"message\"`: An object containing the actual content of the response.  See [Answer formatting](#answer-formatting). _Comes from the OpenAI response._\n  * `\"finish_reason\"`: A string representing the finish state of the response. _Comes from the OpenAI response._\n  * `\"index\"`: A number indicating which response this is (0 in the case of 1 response given). _Comes from the OpenAI response._\n  * `\"content_filter_results\"`: An object from the Azure Content Safety filter. _Same as the OpenAI response, but will only be returned when using Azure OpenAI, not openai.com OpenAI._\n  * `\"context\"`: _Optional_. An object containing additional details needed for the chat app. Each application can define its own properties. See [recommended response context properties](#recommended-response-context).\n  * `\"session_state\"`: _Optional_. An object containing the \"memory\" for the chat app, such as a user ID.\n* `\"created\"`: The Unix timestamp (in seconds) of when the chat completion was created. _Comes from the OpenAI response._\n* `\"id\"`: A unique identifier for the chat completion. _Comes from the OpenAI response._\n* `\"model\"`: The model used for the completion, such as \"gpt-35-turbo\". _Comes from the OpenAI response._\n* `\"object\"`: The object type, always \"chat.completion\". _Comes from the OpenAI response._\n* `\"prompt_filter_results\"`: An object from the Azure Content Safety filter. _Same as the OpenAI response, but will only be returned when using Azure OpenAI, not openai.com OpenAI._\n* `\"system_fingerprint\"`: Represents the backend configuration that the model runs with. _Same as the OpenAI response, but will only be returned when using Azure OpenAI, not openai.com OpenAI._\n* `\"usage\"`: Usage statistics for the completion request. _Comes from the OpenAI response._\n\nHere's an example JSON response:\n\n```json\n{\n    \"choices\": [\n        {\n            \"content_filter_results\": {\n                \"hate\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"self_harm\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"sexual\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"violence\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                }\n            },\n            \"context\": {\n                \"data_points\": {\n                    \"text\": [\n                        \"Northwind_Standard_Benefits_Details.pdf#page=91:    Tips for Avoiding Intentionally False Or Misleading Statements:   When it comes to understanding a health plan, it is important to be aware of any  intentiona lly false or misleading statements that the plan provider may make...(truncated)\",\n                        \"Northwind_Standard_Benefits_Details.pdf#page=91:  It is important to  research the providers and services offered in the Northwind Standard plan in order to  determine if the providers and services offered are sufficient for the employee's needs...(truncated)\",\n                        \"Northwind_Standard_Benefits_Details.pdf#page=17:  Employees should keep track of their claims and follow up with  Northwind Health if a claim is not processed in a timely manner...(truncated)\"\n                    ]\n                },\n                \"thoughts\": [\n                    {\n                        \"description\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n                        \"props\": null,\n                        \"title\": \"Original user query\"\n                    },\n                    {\n                        \"description\": \"Northwind Health Plus plan coverage details compared to standard plan\",\n                        \"props\": {\n                            \"has_vector\": false,\n                            \"use_semantic_captions\": false\n                        },\n                        \"title\": \"Generated search query\"\n                    },\n                    {\n                        \"description\": [\n                            {\n                                \"captions\": [],\n                                \"category\": null,\n                                \"content\": \"  \\nTips for Avoiding Intentionally False Or Misleading Statements:  \\nWhen it comes to understanding a health plan, it is important to be aware of any \\nintentiona lly false or misleading statements that the plan provider may make...(truncated)\",\n                                \"embedding\": null,\n                                \"groups\": [],\n                                \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-233\",\n                                \"imageEmbedding\": null,\n                                \"oids\": [],\n                                \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                                \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=91\"\n                            },\n                            {\n                                \"captions\": [],\n                                \"category\": null,\n                                \"content\": \" It is important to \\nresearch the providers and services offered in the Northwind Standard plan i n order to \\ndetermine if the providers and services offered are sufficient for the employee's needs...(truncated)\",\n                                \"embedding\": null,\n                                \"groups\": [],\n                                \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-232\",\n                                \"imageEmbedding\": null,\n                                \"oids\": [],\n                                \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                                \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=91\"\n                            },\n                            {\n                                \"captions\": [],\n                                \"category\": null,\n                                \"content\": \" Employees should keep track of their claims and follow up with \\nNorthwind Health if a claim is not processed in a timely manner...(truncated)\",\n                                \"embedding\": null,\n                                \"groups\": [],\n                                \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-41\",\n                                \"imageEmbedding\": null,\n                                \"oids\": [],\n                                \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                                \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=17\"\n                            }\n                        ],\n                        \"props\": null,\n                        \"title\": \"Results\"\n                    },\n                    {\n                        \"description\": [\n                            \"{'role': 'system', 'content': \\\"Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.\\n        Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question.\\n        For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.\\n        Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].\\n        \\n        \\n        \\\"}\",\n                            \"{'role': 'user', 'content': \\\"What is included in my Northwind Health Plus plan that is not in standard?\\n\\nSources:\\nNorthwind_Standard_Benefits_Details.pdf#page=91:    Tips for Avoiding Intentionally False Or Misleading Statements:   When it comes to understanding a health plan, it is important to be aware of any  intentiona lly false or misleading statements that the plan provider may make. To avoid  being misled, employees should follow the following tips:(truncated)\n                            \\nNorthwind_Standard_Benefits_Details.pdf#page=91:  It is important to  research the providers and services offered in the Northwind Standard plan in order to  determine if the providers and services offered are sufficient for the employee's needs.   In addition, Northwind Health may make claims that their plan offers low or no cost  prescription drugs..(truncated)\\\"}\"\n                        ],\n                        \"props\": null,\n                        \"title\": \"Prompt\"\n                    }\n                ]\n            },\n            \"finish_reason\": \"stop\",\n            \"index\": 0,\n            \"message\": {\n                \"content\": \"There is no specific information provided about what is included in the Northwind Health Plus plan that is not in the standard plan. It is recommended to read the plan details carefully and ask questions to understand the specific benefits of the Northwind Health Plus plan [Northwind_Standard_Benefits_Details.pdf#page=91].\",\n                \"function_call\": null,\n                \"role\": \"assistant\",\n                \"tool_calls\": null\n            },\n            \"session_state\": null\n        }\n    ],\n    \"created\": 1706301586,\n    \"id\": \"chatcmpl-8lNHGormHX5fhozITxASIufDZno9D\",\n    \"model\": \"gpt-35-turbo\",\n    \"object\": \"chat.completion\",\n    \"prompt_filter_results\": [\n        {\n            \"content_filter_results\": {\n                \"hate\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"self_harm\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"sexual\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"violence\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                }\n            },\n            \"prompt_index\": 0\n        }\n    ],\n    \"system_fingerprint\": null,\n    \"usage\": {\n        \"completion_tokens\": 64,\n        \"prompt_tokens\": 967,\n        \"total_tokens\": 1031\n    }\n}\n```\n\n#### Error response\n\nAn error response should have a status code of 400 or 500, and the body should contain a JSON object with the following properties:\n\n* `\"error\"`: A string describing the error.\n\nHere's an example JSON response for a 500-level error:\n\n```json\n{\n    \"error\": \"The app encountered an error processing your request.\\nIf you are an administrator of the app, view the full error in the logs.\"\n}\n```\n\nHere's an example JSON response for a 400-level error:\n\n```json\n{\n    \"error\": \"Your message contains content that was flagged by the OpenAI content filter.\"\n}\n```\n\n### Streaming response\n\nThis response is based off the\n[OpenAI chat completion chunk object](https://platform.openai.com/docs/api-reference/chat/streaming),\nwith additional properties needed to display sources and citations properly.\n\nThe response should contain these headers:\n\n* `Content-Type: application/json-lines`\n* `Transfer-Encoding: chunked`\n\n#### Successful streamed response\n\nA successful response should have a status code of 200.\nThe body of the response should contain a sequence of JSON objects, each representing a chunk of the response.\nThe first chunk contains a choice with the `context` property, since that is available before the answer, and subsequent chunks contain parts of the answer to the question.\n\nEach JSON object should contain the following properties:\n\n* `\"choices\"`: A list of responses from the LLM, typically containing only 1 response as our app sets `n=1` when requesting a completion. Each response contains:\n  * `\"delta\"`: An object containing the actual content of the response, a token at a time. See [Answer formatting](#answer-formatting). _Comes from the OpenAI response._\n  * `\"finish_reason\"`: A string representing the finish state of the response. _Comes from the OpenAI response._\n  * `\"index\"`: A number indicating which response this is (0 in the case of 1 response given). _Comes from the OpenAI response._\n  * `\"content_filter_results\"`: An object from the Azure Content Safety filter. _Comes from the OpenAI response, but will only be returned when using Azure OpenAI, not openai.com OpenAI._\n  * `\"context\"`: _Optional_. An object containing additional details needed for the chat app. Each application can define its own properties. See [recommended response context properties](#recommended-response-context).\n  * `\"session_state\"`: _Optional_. An object containing the \"memory\" for the chat app, such as a user ID.\n* `\"created\"`: The Unix timestamp (in seconds) of when the chat completion was created. _Comes from the OpenAI response._\n* `\"id\"`: A unique identifier for the chat completion. _Comes from the OpenAI response._\n* `\"model\"`: The model used for the completion, such as \"gpt-35-turbo\". _Comes from the OpenAI response._\n* `\"object\"`: The object type, always \"chat.completion\". _Comes from the OpenAI response._\n* `\"prompt_filter_results\"`: An object from the Azure Content Safety filter. _Comes from the OpenAI response, but will only be returned when using Azure OpenAI, not openai.com OpenAI._\n* `\"system_fingerprint\"`: Represents the backend configuration that the model runs with. _Comes from the OpenAI response._\n* `\"usage\"`: Usage statistics for the completion request. _Comes from the OpenAI response._\n\nHere's an example of the first three JSON objects in a streaming response:\n\n```json\n{\n    \"choices\": [\n        {\n            \"delta\": {\n                \"role\": \"assistant\"\n            },\n            \"context\": {\n                \"data_points\": {\n                    \"text\": [\n                        \"Benefit_Options.pdf#page=3:  The plans also cover preventive care services such as mammograms, colonoscopies, and  other cancer screenings...(truncated)\",\n                        \"Benefit_Options.pdf#page=3:   Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays,  doctor visits,...(truncated)\",\n                        \"Benefit_Options.pdf#page=3:  With Northwind Health Plus, you can choose  from a variety of in -network providers, including primary care physicians,...(truncated)\"\n                    ]\n                },\n                \"thoughts\": [\n                    {\n                        \"title\": \"Original user query\",\n                        \"description\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n                        \"props\": null\n                    },\n                    {\n                        \"title\": \"Generated search query\",\n                        \"description\": \"Northwind Health Plus plan standard\",\n                        \"props\": {\n                            \"use_semantic_captions\": false,\n                            \"has_vector\": false\n                        }\n                    },\n                    {\n                        \"title\": \"Results\",\n                        \"description\": [\n                            {\n                                \"id\": \"file-Benefit_Options_pdf-42656E656669745F4F7074696F6E732E706466-page-2\",\n                                \"content\": \" The plans also cover preventive care services such as mammograms, colonoscopies, and \\nother cancer screenings...(truncated)\",\n                                \"embedding\": null,\n                                \"imageEmbedding\": null,\n                                \"category\": null,\n                                \"sourcepage\": \"Benefit_Options.pdf#page=3\",\n                                \"sourcefile\": \"Benefit_Options.pdf\",\n                                \"oids\": [],\n                                \"groups\": [],\n                                \"captions\": []\n                            },\n                            {\n                                \"id\": \"file-Benefit_Options_pdf-42656E656669745F4F7074696F6E732E706466-page-3\",\n                                \"content\": \" \\nBoth plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays, \\ndoctor visits,...(truncated)\",\n                                \"embedding\": null,\n                                \"imageEmbedding\": null,\n                                \"category\": null,\n                                \"sourcepage\": \"Benefit_Options.pdf#page=3\",\n                                \"sourcefile\": \"Benefit_Options.pdf\",\n                                \"oids\": [],\n                                \"groups\": [],\n                                \"captions\": []\n                            },\n                            {\n                                \"id\": \"file-Benefit_Options_pdf-42656E656669745F4F7074696F6E732E706466-page-1\",\n                                \"content\": \" With Northwind Health Plus, you can choose \\nfrom a variety of in -network providers, including primary care physicians,...(truncated)\",\n                                \"embedding\": null,\n                                \"imageEmbedding\": null,\n                                \"category\": null,\n                                \"sourcepage\": \"Benefit_Options.pdf#page=3\",\n                                \"sourcefile\": \"Benefit_Options.pdf\",\n                                \"oids\": [],\n                                \"groups\": [],\n                                \"captions\": []\n                            }\n                        ],\n                        \"props\": null\n                    },\n                    {\n                        \"title\": \"Prompt\",\n                        \"description\": [\n                            \"{'role': 'system', 'content': \\\"Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.\\\\n        Answer ONLY with the facts listed in the list of sources below. If there isn't enough information below, say you don't know. Do not generate answers that don't use the sources below. If asking a clarifying question to the user would help, ask the question.\\\\n        For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.\\\\n        Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don't combine sources, list each source separately, for example [info1.txt][info2.pdf].\\\\n        \\\\n        \\\\n        \\\"}\",\n                            \"{'role': 'user', 'content': 'What is included in my Northwind Health Plus plan that is not in standard?'}\",\n                            \"{'role': 'assistant', 'content': 'There is no specific information provided about what is included in the Northwind Health Plus plan that is not in the standard plan. It is recommended to read the plan details carefully and ask questions to understand the specific benefits of the Northwind Health Plus plan [Northwind_Standard_Benefits_Details.pdf#page=91].'}\",\n                            \"{'role': 'user', 'content': \\\"What is included in my Northwind Health Plus plan that is not in standard?\\\\n\\\\nSources:\\\\nBenefit_Options.pdf#page=3:  The plans also cover preventive care services such as mammograms, colonoscopies, and  other cancer screenings...(truncated)\\\\nBenefit_Options.pdf#page=3:   Both plans offer coverage for medical services. Northwind Health Plus offers coverage for hospital stays,  doctor visits,...(truncated)\\\\nBenefit_Options.pdf#page=3:  With Northwind Health Plus, you can choose  from a variety of in -network providers, including primary care physicians,...(truncated)\\\"}\"\n                        ],\n                        \"props\": null\n                    }\n                ]\n            },\n            \"session_state\": null,\n            \"finish_reason\": null,\n            \"index\": 0\n        }\n    ],\n    \"object\": \"chat.completion.chunk\"\n}{\n    \"id\": \"chatcmpl-8lNX48CGv9kW7vXTCa9Jb0J4RfnlQ\",\n    \"choices\": [\n        {\n            \"delta\": {\n                \"content\": null,\n                \"function_call\": null,\n                \"role\": \"assistant\",\n                \"tool_calls\": null\n            },\n            \"finish_reason\": null,\n            \"index\": 0,\n            \"content_filter_results\": {}\n        }\n    ],\n    \"created\": 1706302566,\n    \"model\": \"gpt-35-turbo\",\n    \"object\": \"chat.completion.chunk\",\n    \"system_fingerprint\": null\n}{\n    \"id\": \"chatcmpl-8lNX48CGv9kW7vXTCa9Jb0J4RfnlQ\",\n    \"choices\": [\n        {\n            \"delta\": {\n                \"content\": \"The\",\n                \"function_call\": null,\n                \"role\": null,\n                \"tool_calls\": null\n            },\n            \"finish_reason\": null,\n            \"index\": 0,\n            \"content_filter_results\": {\n                \"hate\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"self_harm\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"sexual\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                },\n                \"violence\": {\n                    \"filtered\": false,\n                    \"severity\": \"safe\"\n                }\n            }\n        }\n    ],\n    \"created\": 1706302566,\n    \"model\": \"gpt-35-turbo\",\n    \"object\": \"chat.completion.chunk\",\n    \"system_fingerprint\": null\n}\n```\n\n#### Error in streamed response\n\nIf an error is encountered before the stream begins, then the response may look like a non-streaming error response. However, if an error is encountered during the stream, then the server will have already sent a 200 response, and will send a chunk with an error object. Typically that would be the last chunk, but it may not be.\n\nHere's an example of an error chunk:\n\n```json\n{\n    \"error\": \"The app encountered an error processing your request.\\nIf you are an administrator of the app, view the full error in the logs.\"\n}\n```\n\n### Answer formatting\n\nTo support the display of citations, the answer from the LLM should contain source information in square brackets, such as `[info1.txt]`.\n\nHere's a full example of an answer with citation:\n\n```text\nThere is no specific information provided about what is included in the Northwind Health Plus plan that is not in the standard plan. It is recommended to read the plan details carefully and ask questions to understand the specific benefits of the Northwind Health Plus plan [Northwind_Standard_Benefits_Details.pdf#page=91].\n```\n\n### Recommended response context\n\nThe response context object can contain any properties. However, here are some recommended properties:\n\n* `\"followup_questions\"`: A list of follow-up questions to ask the user.\n\n    Example:\n\n    ```json\n    \"followup_questions\": [\n        \"What types of prescription drugs are covered?\",\n        \"Which services have lower out-of-pocket costs?\"\n    ]\n    ```\n\n    If a client receives this property and the user has requested follow-up questions, the client should prompt the user with clickable versions of the questions. [See image](images/followup.png)\n\n* `\"data_points\"`: An object containing text and/or image data chunks, a list in the `\"text\"` or `\"images\"` properties.\n\n    Example:\n\n    ```json\n    \"data_points\": {\n        \"text\": [\n            \"Northwind_Standard_Benefits_Details.pdf#page=91:    Tips for Avoiding Intentionally False Or Misleading Statements:   When it comes to understanding a health plan, it is important to be aware of any intentionally false or misleading statements that the plan provider may make...(truncated)\",\n            \"Northwind_Standard_Benefits_Details.pdf#page=91:  It is important to research the providers and services offered in the Northwind Standard plan in order to  determine if the providers and services offered are sufficient for the employee's needs...(truncated)\",\n            \"Northwind_Standard_Benefits_Details.pdf#page=17:  Employees should keep track of their claims and follow up with  Northwind Health if a claim is not processed in a timely manner...(truncated)\"\n        ]\n    },\n    ```\n\n    Example with images:\n\n    ```json\n    \"data_points\": {\n        \"images\": [\n            {\n                \"detail\": \"auto\",\n                \"url\": \"data:image/png;base64,iVBOR1BORw0KGgoAAAANSUhEUgAAAAEAAAABAQAAAAA3bvkkAAAACklEQVR4nGMAAQAABQABDQ0tuhsAAAAASUVORK5CYII=\"\n            }\n        ],\n        \"text\": [\n            \"Financial Market Analysis Report 2023-6.png: 3</td><td>1</td></tr></table> Financial markets are interconnected, with movements in one segment often influencing other...(truncated)\"\n        ]\n    },\n    ```\n\n    If a client receives this property, the client should display the data points in a perusable format. [See image](images/datapoints.png)\n\n* `\"thoughts\"`: A list describing each step of the backend. Each step should contain:\n  * `\"title\"`: A string describing the step.\n  * `\"description\"`: A string or list of strings describing the step.\n  * `\"props\"`: _Optional_. An object containing additional properties for the step.\n\n    Example:\n\n    ```json\n    \"thoughts\": [\n        {\n            \"title\": \"Original user query\",\n            \"description\": \"What is included in my Northwind Health Plus plan that is not in standard?\",\n            \"props\": null\n        },\n        {\n            \"title\": \"Generated search query\",\n            \"description\": \"Northwind Health Plus plan coverage details\",\n            \"props\": {\n                \"has_vector\": false,\n                \"use_semantic_captions\": false\n            }\n        },\n        {\n            \"title\": \"Results\",\n            \"description\": [\n                {\n                    \"captions\": [],\n                    \"category\": null,\n                    \"content\": \"  \\n\\u2022 Understand your coverage limits, and know what services are  covered and what services \\nare not covered...(truncated)\",\n                    \"embedding\": null,\n                    \"groups\": [],\n                    \"id\": \"file-Northwind_Health_Plus_Benefits_Details_pdf-4E6F72746877696E645F4865616C74685F506C75735F42656E65666974735F44657461696C732E706466-page-249\",\n                    \"imageEmbedding\": null,\n                    \"oids\": [],\n                    \"sourcefile\": \"Northwind_Health_Plus_Benefits_Details.pdf\",\n                    \"sourcepage\": \"Northwind_Health_Plus_Benefits_Details.pdf#page=100\"\n                },\n                {\n                    \"captions\": [],\n                    \"category\": null,\n                    \"content\": \" Employees should keep track of their claims and follow up with \\nNorthwind Health if a claim is not processed in a timely manner...(truncated)\",\n                    \"embedding\": null,\n                    \"groups\": [],\n                    \"id\": \"file-Northwind_Standard_Benefits_Details_pdf-4E6F72746877696E645F5374616E646172645F42656E65666974735F44657461696C732E706466-page-41\",\n                    \"imageEmbedding\": null,\n                    \"oids\": [],\n                    \"sourcefile\": \"Northwind_Standard_Benefits_Details.pdf\",\n                    \"sourcepage\": \"Northwind_Standard_Benefits_Details.pdf#page=17\"\n                },\n                {\n                    \"captions\": [],\n                    \"category\": null,\n                    \"content\": \" It is important to talk to your doctor or \\nhealth care provider to make su re that you understand the details of the clinical trial before \\nyou decide to participate...(truncated)\",\n                    \"embedding\": null,\n                    \"groups\": [],\n                    \"id\": \"file-Northwind_Health_Plus_Benefits_Details_pdf-4E6F72746877696E645F4865616C74685F506C75735F42656E65666974735F44657461696C732E706466-page-57\",\n                    \"imageEmbedding\": null,\n                    \"oids\": [],\n                    \"sourcefile\": \"Northwind_Health_Plus_Benefits_Details.pdf\",\n                    \"sourcepage\": \"Northwind_Health_Plus_Benefits_Details.pdf#page=24\"\n                }\n            ],\n            \"props\": null\n        },\n        {\n            \"title\": \"Prompt\",\n            \"description\": [\n                \"{'role': 'system', 'content': 'Assistant helps the company employees with their healthcare plan questions, and questions about the employee handbook. Be brief in your answers.\\\\n        Answer ONLY with the facts listed in the list of sources below. If there isn\\\\'t enough information below, say you don\\\\'t know. Do not generate answers that don\\\\'t use the sources below. If asking a clarifying question to the user would help, ask the question.\\\\n        For tabular information return it as an html table. Do not return markdown format. If the question is not in English, answer in the language used in the question.\\\\n        Each source has a name followed by colon and the actual information, always include the source name for each fact you use in the response. Use square brackets to reference the source, for example [info1.txt]. Don\\\\'t combine sources, list each source separately, for example [info1.txt][info2.pdf].\\\\n        Generate 3 very brief follow-up questions that the user would likely ask next.\\\\n    Enclose the follow-up questions in double angle brackets. Example:\\\\n    <<Are there exclusions for prescriptions?>>\\\\n    <<Which pharmacies can be ordered from?>>\\\\n    <<What is the limit for over-the-counter medication?>>\\\\n    Do no repeat questions that have already been asked.\\\\n    Make sure the last question ends with \\\">>\\\".\\\\n    \\\\n        \\\\n        '}\",\n                \"{'role': 'user', 'content': 'What is included in my Northwind Health Plus plan that is not in standard?'}\",\n                \"{'role': 'assistant', 'content': 'The Northwind Health Plus plan includes coverage for prescription drugs, but it is important to read the plan details to determine which prescription drugs are covered and what the associated costs are [Northwind_Standard_Benefits_Details.pdf#page=91]. Additionally, employees should select in-network providers to maximize coverage and avoid unexpected costs, submit claims as soon as possible after a service is rendered, and track claims and follow up with Northwind Health if a claim is not processed in a timely manner [Northwind_Standard_Benefits_Details.pdf#page=17].\\\\n\\\\n'}\",\n                \"{'role': 'user', 'content': 'What is included in my Northwind Health Plus plan that is not in standard?\\\\n\\\\nSources:\\\\nNorthwind_Health_Plus_Benefits_Details.pdf#page=100:    \\u2022 Understand your coverage limits, and know what services are  covered and what services  are not covered...(truncated)\\\\nNorthwind_Standard_Benefits_Details.pdf#page=17:  Employees should keep track of their claims and follow up with  Northwind Health if a claim is not processed in a timely manner...(truncated)\\\\nNorthwind_Health_Plus_Benefits_Details.pdf#page=24:  It is important to talk to your doctor or  health care provider..(truncated)'}\"\n            ],\n            \"props\": null\n        }\n    ]\n    ```\n\n    If a client receives this property, the client should display the thoughts in a debug display. [See image](images/thoughts.png)\n\n## Sample applications\n\nThe following applications support at least a subset of the schema described above:\n\nMarkdown table:\n\n| Application | Supports schema | Description |\n| ----------- | --------------- | ----------- |\n| [azure-search-openai-demo](https://www.github.com/Azure-samples/azure-search-openai-demo) | Supports the full schema, including all recommended context properties. | A RAG chat app that uses Azure AI Search and OpenAI. |\n| [azure-search-openai-javascript](https://www.github.com/Azure-samples/azure-search-openai-javascript) | Supports most of the schema, but not all recommended context properties (like for GPT-4V feature). | A RAG chat app that uses Azure AI Search and OpenAI. |\n| [chatgpt-backend-fastapi](https://github.com/pamelafox/chatgpt-backend-fastapi/) | Supports messages and stream, but no context properties. | A simple chat app using OpenAI. |\n| [chatgpt-quickstart](https://github.com/Azure-Samples/chatgpt-quickstart) | Supports messages, but no context properties, and only supports stream = True. | A simple chat app using OpenAI. |\n"
  },
  {
    "path": "spec/main.tsp",
    "content": "import \"@typespec/http\";\nimport \"@typespec/rest\";\nimport \"@typespec/openapi3\";\n\nimport \"./operations.tsp\";\n\nusing TypeSpec.Http;\n\n@service({\n  title: \"Chat Protocol\",\n})\n@server(\n  \"{endpoint}\",\n  \"Chat Protocol enabled endpoint\",\n  {\n    endpoint: string,\n  }\n)\nnamespace AI.Chat;\n"
  },
  {
    "path": "spec/models.tsp",
    "content": "import \"@typespec/http\";\n\nusing TypeSpec.Http;\n\nmodel ContextProp {\n  context?: Record<unknown>;\n}\n\nmodel SessionStateProp {\n  sessionState?: unknown;\n}\n\nenum AIChatRole {\n  assistant,\n  user,\n  system,\n}\n\nmodel AIChatMessage {\n  role: AIChatRole;\n  content: string;\n  ...ContextProp;\n}\n\nmodel AIChatMessageDelta {\n  role?: AIChatRole;\n  content?: string;\n  ...ContextProp;\n}\n\nmodel AIChatCompletion {\n  message: AIChatMessage;\n  ...ContextProp;\n  ...SessionStateProp;\n}\n\nmodel AIChatCompletionDelta {\n  @header\n  contentType: \"application/jsonl\";\n\n  delta: AIChatMessageDelta;\n  ...ContextProp;\n  ...SessionStateProp;\n}\n\nmodel AIChatCompletionRequest {\n  messages: AIChatMessage[];\n  ...ContextProp;\n  ...SessionStateProp;\n}\n\nmodel AIChatCompletionRequestMultipart {\n  json: HttpPart<AIChatCompletionRequest>,\n  files: HttpPart<bytes>[],\n}\n\nmodel AIChatErrorResponse {\n  error: AIChatError\n}\n\nmodel AIChatError {\n  code: string;\n  message: string;\n}\n"
  },
  {
    "path": "spec/operations.tsp",
    "content": "import \"@typespec/http\";\n\nimport \"./models.tsp\";\n\nusing TypeSpec.Http;\n\nnamespace AI.Chat;\n\nalias Response<T> = T | AIChatErrorResponse;\n\nop getCompletion(@header contentType: \"application/json\" | \"multipart/form-data\", body: AIChatCompletionRequest | AIChatCompletionRequestMultipart): Response<AIChatCompletion>;\n\n@overload(getCompletion)\n@post\nop getCompletionJSON(@header contentType: \"application/json\", @body body: AIChatCompletionRequest): Response<AIChatCompletion>;\n\n@overload(getCompletion)\n@post\nop getCompletionMultipart(@header contentType: \"multipart/form-data\", @multipartBody body: AIChatCompletionRequestMultipart): Response<AIChatCompletion>;\n\n@post\n@route(\"/stream\")\nop getStreamedCompletion(@header contentType: \"application/json\" | \"multipart/form-data\", body: AIChatCompletionRequest | AIChatCompletionRequestMultipart): Response<AIChatCompletionDelta>;\n\n@overload(getStreamedCompletion)\n@post\n@route(\"/stream\")\nop getStreamedCompletionJSON(@header contentType: \"application/json\", @body body: AIChatCompletionRequest): Response<AIChatCompletionDelta>;\n\n@overload(getStreamedCompletion)\n@post\n@route(\"/stream\")\nop getStreamedCompletionMultipart(@header contentType: \"multipart/form-data\", @multipartBody body: AIChatCompletionRequestMultipart): Response<AIChatCompletionDelta>;\n"
  },
  {
    "path": "spec/package.json",
    "content": "{\n  \"name\": \"chat-protocol\",\n  \"version\": \"0.1.0\",\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"tsp compile .\",\n    \"format\": \"tsp format **/*.tsp\"\n  },\n  \"dependencies\": {\n    \"@typespec/compiler\": \"^0.57.0\",\n    \"@typespec/http\": \"^0.57.0\",\n    \"@typespec/rest\": \"^0.57.0\",\n    \"@typespec/openapi3\": \"^0.57.0\"\n  },\n  \"private\": true\n}"
  },
  {
    "path": "spec/tspconfig.yaml",
    "content": "emit:\n  - \"@typespec/openapi3\"\n"
  }
]