├── .github
└── workflows
│ └── auto_add_to_project.yml
├── CODE_OF_CONDUCT.md
├── LICENSE
├── README.md
├── SECURITY.md
├── SUPPORT.md
├── session-delivery-resources
├── README.md
├── custom-engine-agent
│ ├── data
│ │ ├── Ajlal Nueimat.pdf
│ │ ├── Alexis Bourgeois.pdf
│ │ ├── Anna Ostrowska.pdf
│ │ ├── Anthony Ivanov.pdf
│ │ ├── Archie Boyle.pdf
│ │ ├── Ariane Meyer.pdf
│ │ ├── Arnel Cruz.pdf
│ │ ├── Bao Guo.pdf
│ │ ├── Brita Tamm.pdf
│ │ ├── Carme Fontbernat.pdf
│ │ ├── Dabir Al.pdf
│ │ ├── Dai Nakamura.pdf
│ │ ├── Enric Argilaguet.pdf
│ │ ├── Fatma Yilmaz.pdf
│ │ ├── Gökçe Aslan.pdf
│ │ ├── Hacer Çetin.pdf
│ │ ├── Hamed Bettar.pdf
│ │ ├── Hanno Simon.pdf
│ │ ├── Irena Jaworska.pdf
│ │ ├── Isaac Talbot.pdf
│ │ ├── Marina Rodríguez.pdf
│ │ ├── Marites Reyes.pdf
│ │ ├── Marta Guanyavents.pdf
│ │ ├── Mia Zimmermann.pdf
│ │ ├── Ni Kang.pdf
│ │ ├── Nihad Samaha.pdf
│ │ ├── Pedro Armijo.pdf
│ │ ├── Qi Shi.pdf
│ │ ├── Qing Cao.pdf
│ │ ├── Renata Hall.pdf
│ │ ├── Saara Teinemaa.pdf
│ │ ├── Sara Folgueroles.pdf
│ │ ├── Shiori Sakamoto.pdf
│ │ ├── Yueying Ren.pdf
│ │ └── Yuu Shibata.pdf
│ └── readme.md
└── declarative-agent
│ ├── README.md
│ └── assets
│ ├── copilot-chat-screen.png
│ ├── explain-comparison.png
│ ├── recommend-product.png
│ ├── tell-me-about-contoso-quad.png
│ └── working-trey.png
└── src
├── README.md
├── custom-engine-agent
├── .gitignore
├── .vscode
│ ├── extensions.json
│ ├── launch.json
│ ├── settings.json
│ └── tasks.json
├── .webappignore
├── README.md
├── aad.manifest.json
├── adapter.ts
├── app
│ ├── actions.ts
│ ├── app.ts
│ └── card.ts
├── appPackage
│ ├── color.png
│ ├── manifest.json
│ └── outline.png
├── build
│ └── aad.manifest.local.json
├── config.ts
├── env
│ ├── .env.dev
│ ├── .env.local.sample
│ ├── .env.local.user.sample
│ └── .env.testtool
├── index.ts
├── infra
│ ├── azure.bicep
│ ├── azure.parameters.json
│ └── botRegistration
│ │ ├── azurebot.bicep
│ │ └── readme.md
├── package-lock.json
├── package.json
├── prompts
│ ├── chat
│ │ ├── config.json
│ │ └── skprompt.txt
│ └── monologue
│ │ ├── actions.json
│ │ ├── config.json
│ │ └── skprompt.txt
├── public
│ ├── auth-end.html
│ └── auth-start.html
├── src
│ ├── adapter.ts
│ ├── app
│ │ ├── actions.ts
│ │ ├── app.ts
│ │ └── card.ts
│ ├── config.ts
│ ├── index.ts
│ ├── prompts
│ │ ├── chat
│ │ │ ├── config.json
│ │ │ └── skprompt.txt
│ │ └── monologue
│ │ │ ├── actions.json
│ │ │ ├── config.json
│ │ │ └── skprompt.txt
│ └── public
│ │ ├── auth-end.html
│ │ └── auth-start.html
├── teamsapp.local.yml
├── teamsapp.testtool.yml
├── teamsapp.yml
├── tsconfig.json
└── web.config
└── declarative-agent
├── .funcignore
├── .gitignore
├── .vscode
├── extensions.json
├── launch.json
├── settings.json
└── tasks.json
├── README.md
├── TreyResearch API.postman_collection.json
├── appPackage
├── TreyResearch-blue-192.png
├── TreyResearch-blue-32.png
├── extra-icons
│ ├── TreyResearch-gold-192.png
│ ├── TreyResearch-gold-32.png
│ ├── TreyResearch-green-192.png
│ ├── TreyResearch-green-32.png
│ ├── TreyResearch-red-192.png
│ ├── TreyResearch-red-32.png
│ ├── color.png
│ └── outline.png
├── manifest.json
├── trey-declarative-agent.json
├── trey-definition.json
└── trey-plugin.json
├── env
├── .env.dev
├── .env.local.sample
└── .env.local.user.sample
├── host.json
├── http
└── treyResearchAPI.http
├── infra
├── azure.bicep
└── azure.parameters.json
├── local.settings.json
├── package-lock.json
├── package.json
├── sampleDocs
├── Bellows College MSA.docx
├── Bellows College NDA.docx
├── Bellows College SOW - Network security review.docx
├── My Hours.xlsx
├── Woodgrove MSA.docx
├── Woodgrove NDA.docx
└── Woodgrove SOW - Financial data plugin for Microsoft Copilot.docx
├── scripts
├── db-setup.js
└── db
│ ├── Assignment.json
│ ├── Consultant.json
│ └── Project.json
├── src
├── functions
│ ├── consultants.ts
│ ├── me.ts
│ └── projects.ts
├── model
│ ├── apiModel.ts
│ ├── baseModel.ts
│ └── dbModel.ts
└── services
│ ├── AssignmentDbService.ts
│ ├── ConsultantApiService.ts
│ ├── ConsultantDbService.ts
│ ├── DbService.ts
│ ├── IdentityService.ts
│ ├── ProjectApiService.ts
│ ├── ProjectDbService.ts
│ └── Utilities.ts
├── teamsapp.local.yml
├── teamsapp.yml
└── tsconfig.json
/.github/workflows/auto_add_to_project.yml:
--------------------------------------------------------------------------------
1 | name: Add new issues to AI Tour GH Project
2 |
3 | on:
4 | issues:
5 | types:
6 | - opened
7 |
8 | jobs:
9 | add-to-project:
10 | name: Add issue to project
11 | runs-on: ubuntu-latest
12 | steps:
13 | - uses: actions/add-to-project@v1.0.2
14 | with:
15 | project-url: ${{ secrets.GH_PROJECT_URL }}
16 | github-token: ${{ secrets.ADD_TO_PROJECT }}
17 | label_issues:
18 | runs-on: ubuntu-latest
19 | permissions:
20 | issues: write
21 | steps:
22 | - run: gh issue edit "$NUMBER" --add-label "$LABELS"
23 | env:
24 | GH_TOKEN: ${{ secrets.ADD_TO_PROJECT }}
25 | GH_REPO: ${{ github.repository }}
26 | NUMBER: ${{ github.event.issue.number }}
27 | LABELS: Extensibility
28 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Microsoft Open Source Code of Conduct
2 |
3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
4 |
5 | Resources:
6 |
7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
10 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
9 | # Developer's Guide to Customizing Microsoft Copilot
10 |
11 | 
12 |
13 | This repo is a companion to this session at Microsoft AI Tour, a worldwide tour of events.
14 |
15 | ## Session Description
16 |
17 | This session will introduce the components of the Microsoft Copilot stack and explain the different options of extensibility and integration that you can enable with Microsoft Copilot. The session contains architecture and code deepdives that help showcase the different extensibility pathways.
18 |
19 | ## Learning Outcomes
20 | You will learn how to integrate your systems, workflows, and applications directly with Microsoft Copilot, maximizing Copilot's potential through different types of copilot extensions. You will learn which type of extensibility and development pathway is the right one for you and see these extensions be built live during the breakout.
21 |
22 | ## Technology Used
23 |
24 | - Declarative agents
25 | - Plugins
26 | - Custom engine agents
27 | - Teams AI library
28 | - Teams Toolkit
29 | - Visual Studio Code
30 | - Microsoft 365 Copilot
31 | - Azure OpenAI
32 | - Azure AI Search
33 |
34 | ## Additional Resources and Continued Learning
35 |
36 | If you are presenting these slides, you can find additional resources, including the slides for the presentation to deliver this session [here](/session-delivery-resources/README.md).
37 |
38 | | Resources | Links | Description |
39 | |:-------------------|:----------------------------------|:-------------------|
40 | | Copilot Developer Camp | [Link](https://aka.ms/copilotdevcamp) | Learn more about Copilot Extensibility with hands on with Labs and Samples|
41 | | Microsoft 365 Copilot Extensibility Learn Paths | [Link](https://aka.ms/extend-microsoft365-copilot)| Explore Microsoft 365 Copilot Extensibility with Learn paths|
42 |
43 |
44 | ## Content Owners
45 |
46 |
47 |
48 |
75 |
76 |
77 |
78 | ## Responsible AI
79 |
80 | Microsoft is committed to helping our customers use our AI products responsibly, sharing our learnings, and building trust-based partnerships through tools like Transparency Notes and Impact Assessments. Many of these resources can be found at [https://aka.ms/RAI](https://aka.ms/RAI).
81 | Microsoft’s approach to responsible AI is grounded in our AI principles of fairness, reliability and safety, privacy and security, inclusiveness, transparency, and accountability.
82 |
83 | Large-scale natural language, image, and speech models - like the ones used in this sample - can potentially behave in ways that are unfair, unreliable, or offensive, in turn causing harms. Please consult the [Azure OpenAI service Transparency note](https://learn.microsoft.com/legal/cognitive-services/openai/transparency-note?tabs=text) to be informed about risks and limitations.
84 |
85 | The recommended approach to mitigating these risks is to include a safety system in your architecture that can detect and prevent harmful behavior. [Azure AI Content Safety](https://learn.microsoft.com/azure/ai-services/content-safety/overview) provides an independent layer of protection, able to detect harmful user-generated and AI-generated content in applications and services. Azure AI Content Safety includes text and image APIs that allow you to detect material that is harmful. We also have an interactive Content Safety Studio that allows you to view, explore and try out sample code for detecting harmful content across different modalities. The following [quickstart documentation](https://learn.microsoft.com/azure/ai-services/content-safety/quickstart-text?tabs=visual-studio%2Clinux&pivots=programming-language-rest) guides you through making requests to the service.
86 |
87 | Another aspect to take into account is the overall application performance. With multi-modal and multi-models applications, we consider performance to mean that the system performs as you and your users expect, including not generating harmful outputs. It's important to assess the performance of your overall application using [generation quality and risk and safety metrics](https://learn.microsoft.com/azure/ai-studio/concepts/evaluation-metrics-built-in).
88 |
89 | You can evaluate your AI application in your development environment using the [prompt flow SDK](https://microsoft.github.io/promptflow/index.html). Given either a test dataset or a target, your generative AI application generations are quantitatively measured with built-in evaluators or custom evaluators of your choice. To get started with the prompt flow sdk to evaluate your system, you can follow the [quickstart guide](https://learn.microsoft.com/azure/ai-studio/how-to/develop/flow-evaluate-sdk). Once you execute an evaluation run, you can [visualize the results in Azure AI Studio](https://learn.microsoft.com/azure/ai-studio/how-to/evaluate-flow-results).
90 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ## Security
4 |
5 | Microsoft 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).
6 |
7 | If 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.
8 |
9 | ## Reporting Security Issues
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues.**
12 |
13 | Instead, 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).
14 |
15 | If 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).
16 |
17 | You 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).
18 |
19 | Please 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:
20 |
21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
22 | * Full paths of source file(s) related to the manifestation of the issue
23 | * The location of the affected source code (tag/branch/commit or direct URL)
24 | * Any special configuration required to reproduce the issue
25 | * Step-by-step instructions to reproduce the issue
26 | * Proof-of-concept or exploit code (if possible)
27 | * Impact of the issue, including how an attacker might exploit the issue
28 |
29 | This information will help us triage your report more quickly.
30 |
31 | If 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.
32 |
33 | ## Preferred Languages
34 |
35 | We prefer all communications to be in English.
36 |
37 | ## Policy
38 |
39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd).
40 |
41 |
42 |
--------------------------------------------------------------------------------
/SUPPORT.md:
--------------------------------------------------------------------------------
1 | # TODO: The maintainer of this repo has not yet edited this file
2 |
3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project?
4 |
5 | - **No CSS support:** Fill out this template with information about how to file issues and get help.
6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps.
7 | - **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide.
8 |
9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.*
10 |
11 | # Support
12 |
13 | ## How to file issues and get help
14 |
15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing
16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or
17 | feature request as a new Issue.
18 |
19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE
20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER
21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**.
22 |
23 | ## Microsoft Support Policy
24 |
25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
26 |
--------------------------------------------------------------------------------
/session-delivery-resources/README.md:
--------------------------------------------------------------------------------
1 | ## How To Use
2 |
3 | The following resources are intended for a presenter to learn and deliver the session.
4 |
5 | Welcome,
6 |
7 | We're glad you are here and look forward to your delivery of this amazing content. As an experienced presenter, we know you know HOW to present so this guide will focus on WHAT you need to present. It will provide you a full run-through of the presentation created by the presentation design team.
8 |
9 | Along with the video of the presentation, this document will link to all the assets you need to successfully present including PowerPoint slides and demo instructions &
10 | code.
11 |
12 | 1. Read document in its entirety.
13 | 2. Watch the video presentation
14 | 3. Ask questions of the Lead Presenter
15 |
16 | ## File Summary
17 |
18 | | Resources | Links | Description |
19 | |-------------------|----------------------------------|-------------------|
20 | | PowerPoint | [Presentation](https://aka.ms/AAtl1af) | Slides |
21 | | PPT Recording | [Session Recording](https://aka.ms/AAtkk28) | Video Recording of the PowerPoint slides with no audio |
22 | | Demo - Custom Engine Agent 1 & 2| [Instructions](custom-engine-agent) & [Video 1](https://aka.ms/AAtl1ak) and [Video 2](https://aka.ms/AAtl1ai) | Demo instructions and videos |
23 | | Demo - Declarative Agent | [Instructions](declarative-agent) & [Video](https://aka.ms/AAtkk29) | Demo instructions and videos |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Ajlal Nueimat.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Ajlal Nueimat.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Alexis Bourgeois.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Alexis Bourgeois.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Anna Ostrowska.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Anna Ostrowska.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Anthony Ivanov.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Anthony Ivanov.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Archie Boyle.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Archie Boyle.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Ariane Meyer.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Ariane Meyer.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Arnel Cruz.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Arnel Cruz.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Bao Guo.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Bao Guo.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Brita Tamm.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Brita Tamm.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Carme Fontbernat.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Carme Fontbernat.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Dabir Al.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Dabir Al.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Dai Nakamura.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Dai Nakamura.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Enric Argilaguet.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Enric Argilaguet.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Fatma Yilmaz.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Fatma Yilmaz.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Gökçe Aslan.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Gökçe Aslan.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Hacer Çetin.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Hacer Çetin.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Hamed Bettar.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Hamed Bettar.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Hanno Simon.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Hanno Simon.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Irena Jaworska.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Irena Jaworska.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Isaac Talbot.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Isaac Talbot.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Marina Rodríguez.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Marina Rodríguez.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Marites Reyes.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Marites Reyes.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Marta Guanyavents.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Marta Guanyavents.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Mia Zimmermann.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Mia Zimmermann.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Ni Kang.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Ni Kang.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Nihad Samaha.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Nihad Samaha.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Pedro Armijo.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Pedro Armijo.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Qi Shi.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Qi Shi.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Qing Cao.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Qing Cao.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Renata Hall.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Renata Hall.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Saara Teinemaa.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Saara Teinemaa.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Sara Folgueroles.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Sara Folgueroles.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Shiori Sakamoto.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Shiori Sakamoto.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Yueying Ren.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Yueying Ren.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/data/Yuu Shibata.pdf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/custom-engine-agent/data/Yuu Shibata.pdf
--------------------------------------------------------------------------------
/session-delivery-resources/custom-engine-agent/readme.md:
--------------------------------------------------------------------------------
1 | # Instructions for Custom engine copilots with Teams AI Library and VSCode
2 |
3 | This demo showcases how to build a custom engine copilot with Retrieval-Augmented Generation for sophisticated question answering using Teams AI library and Teams Toolkit.
4 |
5 | Video instructions are available [here](https://aka.ms/AArxxcf).
6 |
7 | ## Pre-requisites
8 |
9 | - [Node.js](https://nodejs.org/), supported versions: 16, 18
10 | - [Teams Toolkit for Visual Studio Code](https://aka.ms/teams-toolkit) version 5.10.1
11 | - Prepare your own [Azure OpenAI](https://aka.ms/oai/access), [Azure AI Search](https://azure.microsoft.com/en-us/products/ai-services/ai-search) and [storage account](https://learn.microsoft.com/en-us/azure/storage/common/storage-account-create) resources.
12 | - A Microsoft 365 Account with a permission to **Upload custom apps** to Teams.
13 |
14 | ## Get started with Azure OpenAI
15 |
16 | 1. Start your demo in Azure OpenAI resource and navigate to Azure AI Foundry.
17 | 2. Create a deployment model, From the **Deployments** tab, select **Create a new deployment**. Fill out the following details and select **Create**:
18 | - **Deployment name:** Recommended to use the same name with the selected deployment model, such as `gpt-4`.
19 | - **Select a model:** Select `gpt-4` or higher model.
20 | - **Model version:** Auto update to default.
21 | - **Deployment type:** Standard.
22 | - **Content Filter:** Default.
23 | 3. Create an embeddings deployment model, select **Create a new deployment**. Fill out the following details and select Create:
24 | - **Select a model:** `text-embedding-ada-002`.
25 | - **Model version:** Default.
26 | - **Deployment type:** Standard.
27 | - **Deployment name:** Choose a memorable name, such as text-embeddings
28 | - **Content Filter:** Default.
29 | 4. Once your models are successfully created, you can navigate to **Chat**. In the **Setup** section, select **Add your data** tab and then **Add a data source**.
30 | 5. Select **Upload files (preview)**, then fill the details as the following and select **Next**:
31 | - **Subscription:** Select the subscription you created your Azure resources.
32 | - **Select Azure Blob storage resource:** Select your storage resource. (You'll see a message *Azure OpenAI needs your permission to access this resource*, select **Turn on CORS**.)
33 | - **Select Azure AI Search resource:** Select your Azure AI Search resournce.
34 | - **Enter the index name:** Index name, such as `resumes`; make note of this.
35 | - Check the box for *Add vector search to this search resource* and select your embedding model. Then select **Next**.
36 | - Select **Browse for a file** and select the pdf documents from the [data](./data) folder. Then, select **Upload files** and **Next**.
37 | - Select Search type as `vector` and chunk size as `1024(Default)`, then **Next**.
38 | - Select `API Key` as Azure resource authentication type, then **Next**.
39 | 5. Once your data ingestion is completed, use Chat playground to ask questions about your data such as:
40 | - "Suggest me .NET developers who can speak Spanish"
41 | - "Suggest me python developers in London Area"
42 |
43 | ## Bring your data to Teams
44 |
45 | 1. Clone this github repo, navigate to [custom-engine-agent](../../src/custom-engine-agent/) folder and open it in Visual Studio Code.
46 | 2. In the project folder, select `env` folder and rename:
47 | - **.env.local.sample** file to **.env.local**
48 | - **.env.local.user.sample** file to **env.local.user**
49 | 3. Open `env.local.user` file and fill the values as shown below and save the file:
50 |
51 | ```json
52 | SECRET_BOT_PASSWORD=
53 | SECRET_AZURE_OPENAI_API_KEY=''
54 | AZURE_OPENAI_ENDPOINT=''
55 | AZURE_OPENAI_DEPLOYMENT_NAME=''
56 | AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=''
57 | SECRET_AZURE_SEARCH_KEY=''
58 | AZURE_SEARCH_ENDPOINT=''
59 | INDEX_NAME=''
60 | TEAMS_APP_UPDATE_TIME=
61 | SECRET_AAD_APP_CLIENT_SECRET=
62 | HR_EMAIL=
63 | ```
64 |
65 | Select Teams Toolkit on Visual Studio Code navigation bar, login with Microsoft 365 Account.
66 |
67 | >Make sure that sideloading is enabled for your account. If Sideloading is not enabled yet:
68 | >
69 | >1️⃣ Navigate to https://admin.microsoft.com/, which is the Microsoft 365 Admin Center.
70 | >
71 | >2️⃣ In the left panel of the admin center, select Show all to open up the entire navigation. When the panel opens, select Teams to >open the Microsoft Teams admin center.
72 | >
73 | >3️⃣ In the left of the Microsoft Teams admin center, open the Teams apps accordion. Select Setup Policies, you will see a list of >App setup policies. Then, select the Global (Org-wide default) policy.
74 | >
75 | >4️⃣ Ensure the first switch, Upload custom apps is turned On.
76 |
77 | Let's test the demo on Teams. Start debugging your app by selecting **Run and Debug** tab on Visual Studio Code and Debug in Teams (Edge) or Debug in Teams (Chrome). Microsoft Teams will pop up on your browser. Once your app details show up on Teams, select Add and start chatting with your app.
78 |
79 | >Tip: Testing this exercise locally
80 | >
81 | >Make sure to test and debug this exercise on Teams locally, as some of the Teams AI library capabilities you've implemented in >your app so far won't smoothly work in the Teams App Test Tool.
82 |
83 | Ensure your questions are related to your dataset. Go through pdf documents in the resumes folder to understand more about your data. Challenge your custom engine agent by combining requirements and asking complex questions! Some suggestions would be:
84 |
85 | Can you suggest a candidate who is suitable for spanish speaking role that requires at least 2 years of .NET experience?
86 | Who are the other good candidates?
87 | Who would be suitable for a position that requires 5+ python development experience?
88 | Can you suggest any candidates for a senior developer position with 7+ year experience that requires Japanese speaking?
--------------------------------------------------------------------------------
/session-delivery-resources/declarative-agent/README.md:
--------------------------------------------------------------------------------
1 | # Instructions for building declarative agents with Teams Toolkit for Visual Studio Code
2 |
3 | This document showcases how to run the declarative agent, Trey Genie which demonstrates an agent which is grounded in the API plugin as well as in specific SharePoint files.
4 |
5 | The declarative agent contains instructions, a plugin (skills) to call real time API and uses knowledge from documents stored in SharePoint Online as grounding data.
6 |
7 | ## Prerequisites
8 |
9 | - [Node.js 18](https://nodejs.org/)
10 | - [Teams Toolkit Visual Studio Code Extension](https://aka.ms/teams-toolkit), v5.10.0 and higher
11 | - Microsoft 365 tenant that is [prepared for testing](https://learn.microsoft.com/%20%20microsoftteams/platform/m365-apps/prerequisites#prepare-a-developer-tenant-for-testing) and has Microsoft 365 Copilot enabled
12 | - User account that has a [Microsoft 365 Copilot license](https://learn.microsoft.com/microsoft-365-copilot/extensibility/prerequisites#prerequisites)
13 |
14 |
15 | ## Set up SharePoint Site
16 |
17 | - Create SharePoint Online site Teams Site with the name `Trey Research legal`
18 | - In it's `Documents` folder upload all the files from [sampleDocs](../../src/declarative-agent/sampleDocs/) folder in this repo.
19 |
20 | ## Set up Trey Genie locally
21 | - Clone this github repo, navigate to folder **src/declarative-agent/** and open it in Visual Studio Code.
22 | - In the folder, select **env** folder and rename:
23 | **.env.local.sample** file to **.env.local**
24 | **.env.local.user.sample** file to **env.local.user**
25 | - Open **env.local** file and update the value of `SHAREPOINT_DOCS_URL` with you SharePoint Online site's absolute URL, where you uploaded the sample documents in the previous step
26 |
27 | ## Run Trey Genie locally in Teams
28 |
29 | Select Teams Toolkit on Visual Studio Code navigation bar, login with Microsoft 365 Account.
30 |
31 | >Make sure that sideloading is enabled for your account. If Sideloading is not enabled yet:
32 | >
33 | >1️⃣ Navigate to https://admin.microsoft.com/, which is the Microsoft 365 Admin Center.
34 | >
35 | >2️⃣ In the left panel of the admin center, select Show all to open up the entire navigation. When the panel opens, select Teams to >open the Microsoft Teams admin center.
36 | >
37 | >3️⃣ In the left of the Microsoft Teams admin center, open the Teams apps accordion. Select Setup Policies, you will see a list of >App setup policies. Then, select the Global (Org-wide default) policy.
38 | >
39 | >4️⃣ Ensure the first switch, Upload custom apps is turned On.
40 |
41 | Start debugging your app by selecting **Run and Debug** tab on Visual Studio Code and Debug in Teams (Edge) or Debug in Teams (Chrome). Or select F5.
42 |
43 | Microsoft Teams will pop up on your browser.
44 | Open the Copilot chat 1️⃣ and the right flyout 2️⃣ to show your previous chats and declarative agents and select the Trey Genie Local copilot 3️⃣ .
45 |
46 | 
47 |
48 | Try one of the conversation starters like `Find consultants with TypeScript skills`.
49 |
50 | 
51 |
52 | ### Prompts that work in the completed solution
53 |
54 | * what trey projects am i assigned to?
55 | (NOTE: The logged in user is assumed to be consultant "Avery Howard". As this sample does not have Auth)
56 | * what trey projects is domi working on?
57 | * do we have any trey consultants with azure certifications?
58 | * what trey projects are we doing for relecloud?
59 | * which trey consultants are working with woodgrove bank?
60 | * in trey research, how many hours has avery delivered this month?
61 | * please find a trey consultant with python skills who is available immediately
62 | * are any trey research consultants available who are AWS certified? (multi-parameter!)
63 | * does trey research have any architects with javascript skills? (multi-parameter!)
64 | * what trey research designers are working at woodgrove bank? (multi-parameter!)
65 | * please charge 10 hours to woodgrove bank in trey research (POST request)
66 | * please add sanjay to the contoso project for trey research (POST request with easy to forget entities, hoping to prompt the user; for now they are defaulted)
67 |
68 |
--------------------------------------------------------------------------------
/session-delivery-resources/declarative-agent/assets/copilot-chat-screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/declarative-agent/assets/copilot-chat-screen.png
--------------------------------------------------------------------------------
/session-delivery-resources/declarative-agent/assets/explain-comparison.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/declarative-agent/assets/explain-comparison.png
--------------------------------------------------------------------------------
/session-delivery-resources/declarative-agent/assets/recommend-product.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/declarative-agent/assets/recommend-product.png
--------------------------------------------------------------------------------
/session-delivery-resources/declarative-agent/assets/tell-me-about-contoso-quad.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/declarative-agent/assets/tell-me-about-contoso-quad.png
--------------------------------------------------------------------------------
/session-delivery-resources/declarative-agent/assets/working-trey.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/session-delivery-resources/declarative-agent/assets/working-trey.png
--------------------------------------------------------------------------------
/src/README.md:
--------------------------------------------------------------------------------
1 | # Source code
2 |
3 | This folder contains the source code for this session demos.
4 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/.gitignore:
--------------------------------------------------------------------------------
1 | # TeamsFx files
2 | env/.env.*.user
3 | env/.env.local
4 | .localConfigs
5 | .localConfigs.testTool
6 | .localConfigs
7 | .notification.localstore.json
8 | .notification.testtoolstore.json
9 | appPackage/build
10 |
11 | # dependencies
12 | node_modules/
13 |
14 | # misc
15 | .env
16 | .deployment
17 | .DS_Store
18 |
19 | # build
20 | lib/
21 |
22 | # Dev tool directories
23 | /devTools/
--------------------------------------------------------------------------------
/src/custom-engine-agent/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "TeamsDevApp.ms-teams-vscode-extension"
4 | ]
5 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch Remote (Edge)",
6 | "type": "msedge",
7 | "request": "launch",
8 | "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
9 | "presentation": {
10 | "group": "3-remote",
11 | "order": 1
12 | },
13 | "internalConsoleOptions": "neverOpen"
14 | },
15 | {
16 | "name": "Launch Remote (Chrome)",
17 | "type": "chrome",
18 | "request": "launch",
19 | "url": "https://teams.microsoft.com/l/app/${{TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
20 | "presentation": {
21 | "group": "3-remote",
22 | "order": 2
23 | },
24 | "internalConsoleOptions": "neverOpen"
25 | },
26 | {
27 | "name": "Launch App (Edge)",
28 | "type": "msedge",
29 | "request": "launch",
30 | "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
31 | "cascadeTerminateToConfigurations": [
32 | "Attach to Local Service"
33 | ],
34 | "presentation": {
35 | "group": "all",
36 | "hidden": true
37 | },
38 | "internalConsoleOptions": "neverOpen"
39 | },
40 | {
41 | "name": "Launch App (Chrome)",
42 | "type": "chrome",
43 | "request": "launch",
44 | "url": "https://teams.microsoft.com/l/app/${{local:TEAMS_APP_ID}}?installAppPackage=true&webjoin=true&${account-hint}",
45 | "cascadeTerminateToConfigurations": [
46 | "Attach to Local Service"
47 | ],
48 | "presentation": {
49 | "group": "all",
50 | "hidden": true
51 | },
52 | "internalConsoleOptions": "neverOpen"
53 | },
54 | {
55 | "name": "Attach to Local Service",
56 | "type": "node",
57 | "request": "attach",
58 | "port": 9239,
59 | "restart": true,
60 | "presentation": {
61 | "group": "all",
62 | "hidden": true
63 | },
64 | "internalConsoleOptions": "neverOpen"
65 | }
66 | ],
67 | "compounds": [
68 | {
69 | "name": "Debug in Teams (Edge)",
70 | "configurations": [
71 | "Launch App (Edge)",
72 | "Attach to Local Service"
73 | ],
74 | "preLaunchTask": "Start Teams App Locally",
75 | "presentation": {
76 | "group": "2-local",
77 | "order": 1
78 | },
79 | "stopAll": true
80 | },
81 | {
82 | "name": "Debug in Teams (Chrome)",
83 | "configurations": [
84 | "Launch App (Chrome)",
85 | "Attach to Local Service"
86 | ],
87 | "preLaunchTask": "Start Teams App Locally",
88 | "presentation": {
89 | "group": "2-local",
90 | "order": 2
91 | },
92 | "stopAll": true
93 | },
94 | {
95 | "name": "Debug in Test Tool",
96 | "configurations": [
97 | "Attach to Local Service"
98 | ],
99 | "preLaunchTask": "Start Teams App (Test Tool)",
100 | "presentation": {
101 | "group": "1-local",
102 | "order": 1
103 | },
104 | "stopAll": true
105 | }
106 | ]
107 | }
108 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug.onTaskErrors": "abort",
3 | "json.schemas": [
4 | {
5 | "fileMatch": [
6 | "/aad.*.json"
7 | ],
8 | "schema": {}
9 | }
10 | ]
11 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/.webappignore:
--------------------------------------------------------------------------------
1 | .webappignore
2 | .fx
3 | .deployment
4 | .localConfigs.testTool
5 | .localConfigs
6 | .notification.localstore.json
7 | .notification.testtoolstore.json
8 | .vscode
9 | *.js.map
10 | *.ts.map
11 | *.ts
12 | .git*
13 | .tsbuildinfo
14 | CHANGELOG.md
15 | readme.md
16 | local.settings.json
17 | test
18 | tsconfig.json
19 | .DS_Store
20 | teamsapp.yml
21 | teamsapp.*.yml
22 | /env/
23 | /node_modules/.bin
24 | /node_modules/ts-node
25 | /node_modules/typescript
26 | /appPackage/
27 | /infra/
28 | /devTools/
--------------------------------------------------------------------------------
/src/custom-engine-agent/README.md:
--------------------------------------------------------------------------------
1 | # Get started with the sample
2 |
3 | Visit [session-delivery-resources](../../session-delivery-resources/custom-engine-agent/) to learn more about running this sample locally.
4 |
5 | ## Final application
6 |
7 | This folder contains a solution from the [Copilot Developer Camp labs](https://aka.ms/copilotdevcamp). When completed, the solution is a custom engine agent called "Career Genie" which provides assistant to Human Resources team in Trey Research. Trey Research is a fictitious consulting company that supplies talent in the software and pharmaceuticals industries. The vision for this demo is to show a sophisticated custom engine agent that utilizes custom AI models and orchestrator to meet the unique needs of the Human Resources department such as managing resumes, create new job posts and more.
8 |
9 | ### Prompts that work in the completed solution
10 |
11 | - Hello
12 | - Can you suggest candidates who have experience in .NET?
13 | - Great, add Isaac Talbot in the .NET Developer Candidates list
14 | - Add Anthony Ivanov in the same list with Isaac
15 | - Can you summarize my lists
16 | - Suggest candidates who have experience in Python and are able to speak Spanish
17 | - Nice! Add Sara Folgueroles in the Python Developer Candidates (Spanish speaking) list
18 | - Can you suggest candidates who have 10+ years of experience
19 | - Ok, remove Anthony from the .NET Developer Candidates list
20 | - Add Anthony Ivanov in the Talent list
21 | - Summarize my lists
22 | - Add Pedro Armijo in the same list with Sara
23 | - Summarize my lists
24 | - Send my lists to HR
--------------------------------------------------------------------------------
/src/custom-engine-agent/aad.manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "${{AAD_APP_OBJECT_ID}}",
3 | "appId": "${{AAD_APP_CLIENT_ID}}",
4 | "name": "CareerGenieBot-aad",
5 | "accessTokenAcceptedVersion": 2,
6 | "signInAudience": "AzureADMultipleOrgs",
7 | "optionalClaims": {
8 | "idToken": [],
9 | "accessToken": [
10 | {
11 | "name": "idtyp",
12 | "source": null,
13 | "essential": false,
14 | "additionalProperties": []
15 | }
16 | ],
17 | "saml2Token": []
18 | },
19 | "requiredResourceAccess": [
20 | {
21 | "resourceAppId": "Microsoft Graph",
22 | "resourceAccess": [
23 | {
24 | "id": "User.Read",
25 | "type": "Scope"
26 | },
27 | {
28 | "id": "Mail.Read",
29 | "type": "Scope"
30 | }
31 | ]
32 | }
33 | ],
34 | "oauth2Permissions": [
35 | {
36 | "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.",
37 | "adminConsentDisplayName": "Teams can access app's web APIs",
38 | "id": "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}",
39 | "isEnabled": true,
40 | "type": "User",
41 | "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have",
42 | "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf",
43 | "value": "access_as_user"
44 | }
45 | ],
46 | "preAuthorizedApplications": [
47 | {
48 | "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
49 | "permissionIds": [
50 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
51 | ]
52 | },
53 | {
54 | "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346",
55 | "permissionIds": [
56 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
57 | ]
58 | },
59 | {
60 | "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c",
61 | "permissionIds": [
62 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
63 | ]
64 | },
65 | {
66 | "appId": "00000002-0000-0ff1-ce00-000000000000",
67 | "permissionIds": [
68 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
69 | ]
70 | },
71 | {
72 | "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3",
73 | "permissionIds": [
74 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
75 | ]
76 | },
77 | {
78 | "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c",
79 | "permissionIds": [
80 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
81 | ]
82 | },
83 | {
84 | "appId": "4765445b-32c6-49b0-83e6-1d93765276ca",
85 | "permissionIds": [
86 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
87 | ]
88 | },
89 | {
90 | "appId": "4345a7b9-9a63-4910-a426-35363201d503",
91 | "permissionIds": [
92 | "${{AAD_APP_ACCESS_AS_USER_PERMISSION_ID}}"
93 | ]
94 | }
95 | ],
96 | "identifierUris":[
97 | "api://botid-${{BOT_ID}}"
98 | ],
99 | "replyUrlsWithType":[
100 | {
101 | "url": "https://${{BOT_DOMAIN}}/auth-end.html",
102 | "type": "Web"
103 | }
104 | ]
105 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/adapter.ts:
--------------------------------------------------------------------------------
1 | // Import required bot services.
2 | // See https://aka.ms/bot-services to learn more about the different parts of a bot.
3 | import {
4 | CloudAdapter,
5 | ConfigurationBotFrameworkAuthentication,
6 | ConfigurationServiceClientCredentialFactory,
7 | } from "botbuilder";
8 |
9 | // This bot's main dialog.
10 | import config from "./config";
11 | import { TeamsAdapter } from '@microsoft/teams-ai';
12 | // const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(
13 | // {},
14 | // new ConfigurationServiceClientCredentialFactory({
15 | // MicrosoftAppId: config.botId,
16 | // MicrosoftAppPassword: process.env.BOT_PASSWORD,
17 | // MicrosoftAppType: "MultiTenant",
18 | // })
19 | // );
20 |
21 | // Create adapter.
22 | // See https://aka.ms/about-bot-adapter to learn more about how bots work.
23 | const adapter = new TeamsAdapter(
24 | {},
25 | new ConfigurationServiceClientCredentialFactory({
26 | MicrosoftAppId: config.botId,
27 | MicrosoftAppPassword: process.env.BOT_PASSWORD,
28 | MicrosoftAppType: 'MultiTenant',
29 | })
30 | );
31 |
32 | // Catch-all for errors.
33 | const onTurnErrorHandler = async (context, error) => {
34 | // This check writes out errors to console log .vs. app insights.
35 | // NOTE: In production environment, you should consider logging this to Azure
36 | // application insights.
37 | console.error(`\n [onTurnError] unhandled error: ${error}`);
38 |
39 | // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat.
40 | if (context.activity.type === "message") {
41 | // Send a trace activity, which will be displayed in Bot Framework Emulator
42 | await context.sendTraceActivity(
43 | "OnTurnError Trace",
44 | `${error}`,
45 | "https://www.botframework.com/schemas/error",
46 | "TurnError"
47 | );
48 |
49 | // Send a message to the user
50 | await context.sendActivity("The bot encountered an error or bug.");
51 | await context.sendActivity("To continue to run this bot, please fix the bot source code.");
52 | }
53 | };
54 |
55 | // Set the onTurnError for the singleton CloudAdapter.
56 | adapter.onTurnError = onTurnErrorHandler;
57 |
58 | export default adapter;
59 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/app/actions.ts:
--------------------------------------------------------------------------------
1 | import { getUserDisplayName, ApplicationTurnState } from './app';
2 | import { Client } from "@microsoft/microsoft-graph-client";
3 | import config from '../config';
4 |
5 | function getCandidates(state: ApplicationTurnState, list: string): string[] {
6 | ensureListExists(state, list);
7 | return state.conversation.lists[list];
8 | }
9 |
10 | function setCandidates(state: ApplicationTurnState, list: string, Candidates: string[]): void {
11 | ensureListExists(state, list);
12 | state.conversation.lists[list] = Candidates ?? [];
13 | }
14 |
15 | function ensureListExists(state: ApplicationTurnState, listName: string): void {
16 | if (typeof state.conversation.lists != 'object') {
17 | state.conversation.lists = {};
18 | }
19 |
20 | if (!Object.prototype.hasOwnProperty.call(state.conversation.lists, listName)) {
21 | state.conversation.lists[listName] = [];
22 | }
23 | }
24 |
25 | function deleteList(state: ApplicationTurnState, listName: string): void {
26 | if (
27 | typeof state.conversation.lists == 'object' &&
28 | Object.prototype.hasOwnProperty.call(state.conversation.lists, listName)
29 | ) {
30 | delete state.conversation.lists[listName];
31 | }
32 | }
33 |
34 | async function sendLists(state: ApplicationTurnState, token): Promise {
35 | const email = await createEmailContent(state.conversation.lists, token);
36 | try {
37 | const client = Client.init({
38 | authProvider: (done) => {
39 | done(null, token);
40 | }
41 | });
42 | const sendEmail = await client.api('/me/sendMail').post(JSON.stringify(email));
43 | if (sendEmail.ok) {
44 | return email.message.body.content;
45 | }
46 | else {
47 | console.log(`Error ${sendEmail.status} calling Graph in sendToHR: ${sendEmail.statusText}`);
48 | return 'Error sending email';
49 | }
50 | } catch (error) {
51 | console.error('Error in sendLists:', error);
52 | throw error;
53 | }
54 | }
55 |
56 | async function createEmailContent(lists, token) {
57 | let emailContent = '';
58 | for (const listName in lists) {
59 | if (lists.hasOwnProperty(listName)) {
60 | emailContent += `${listName}:\n`;
61 | lists[listName].forEach(candidate => {
62 | emailContent += ` • ${candidate}\n`;
63 | });
64 | emailContent += '\n'; // Add an extra line between different lists
65 | }
66 | }
67 |
68 | const profileName = await getUserDisplayName(token);
69 |
70 | const email = {
71 | "message": {
72 | "subject": "Request to Schedule Interviews with Shortlisted Candidates",
73 | "body": {
74 | "contentType": "Text",
75 | "content": `Hello HR Team, \nI hope this email finds you well. \n\nCould you please assist in scheduling 1:1 interviews with the following shortlisted candidates? \n\n${emailContent} Please arrange suitable times and send out the calendar invites accordingly. \n\n Best Regards, \n ${profileName}`
76 | },
77 | "toRecipients": [
78 | {
79 | "emailAddress": {
80 | "address": `${config.HR_EMAIL}`
81 | }
82 | }
83 | ]
84 | },
85 | "saveToSentCandidates": "true"
86 | };
87 | return await email;
88 | }
89 |
90 | export { getCandidates, setCandidates, ensureListExists, deleteList, sendLists };
--------------------------------------------------------------------------------
/src/custom-engine-agent/app/card.ts:
--------------------------------------------------------------------------------
1 | import { AdaptiveCard, Message, Utilities } from '@microsoft/teams-ai';
2 | /**
3 | * Create an adaptive card from a prompt response.
4 | * @param {Message} response The prompt response to create the card from.
5 | * @returns {AdaptiveCard} The response card.
6 | */
7 |
8 | //Adaptive card to display the response and citations
9 | export function createResponseCard(response: Message): AdaptiveCard {
10 | const citationCards = response.context?.citations.map((citation, i) => ({
11 | type: 'Action.ShowCard',
12 | title: `${i+1}`,
13 | card: {
14 | type: 'AdaptiveCard',
15 | body: [
16 | {
17 | type: 'TextBlock',
18 | text: citation.title,
19 | fontType: 'Default',
20 | weight: 'Bolder'
21 | },
22 | {
23 | type: 'TextBlock',
24 | text: citation.content,
25 | wrap: true
26 | }
27 | ]
28 | }
29 | }));
30 |
31 | const text = Utilities.formatCitationsResponse(response.content!);
32 | return {
33 | type: 'AdaptiveCard',
34 | body: [
35 | {
36 | type: 'TextBlock',
37 | text: text,
38 | wrap: true
39 | },
40 | {
41 | type: 'TextBlock',
42 | text: 'Citations',
43 | wrap: true,
44 | fontType: 'Default',
45 | weight: 'Bolder'
46 | },
47 | {
48 | type: 'ActionSet',
49 | actions: citationCards
50 | }
51 | ],
52 | $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
53 | version: '1.5'
54 | };
55 | }
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/appPackage/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/custom-engine-agent/appPackage/color.png
--------------------------------------------------------------------------------
/src/custom-engine-agent/appPackage/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/en-us/json-schemas/teams/v1.16/MicrosoftTeams.schema.json",
3 | "manifestVersion": "1.16",
4 | "version": "1.0.0",
5 | "id": "${{TEAMS_APP_ID}}",
6 | "packageName": "com.microsoft.teams.extension",
7 | "developer": {
8 | "name": "Teams App, Inc.",
9 | "websiteUrl": "https://www.example.com",
10 | "privacyUrl": "https://www.example.com/privacy",
11 | "termsOfUseUrl": "https://www.example.com/termofuse"
12 | },
13 | "icons": {
14 | "color": "color.png",
15 | "outline": "outline.png"
16 | },
17 | "name": {
18 | "short": "CareerGenie${{APP_NAME_SUFFIX}}",
19 | "full": "full name for CareerGenie"
20 | },
21 | "description": {
22 | "short": "short description for CareerGenie",
23 | "full": "full description for CareerGenie"
24 | },
25 | "accentColor": "#FFFFFF",
26 | "bots": [
27 | {
28 | "botId": "${{BOT_ID}}",
29 | "scopes": ["personal", "team", "groupChat"],
30 | "isNotificationOnly": false,
31 | "supportsCalling": false,
32 | "supportsVideo": false,
33 | "supportsFiles": false
34 | }
35 | ],
36 | "composeExtensions": [],
37 | "configurableTabs": [],
38 | "staticTabs": [],
39 | "permissions": [
40 | "identity",
41 | "messageTeamMembers"
42 | ],
43 | "validDomains": [
44 | "${{BOT_DOMAIN}}",
45 | "*.botframework.com"
46 | ],
47 | "webApplicationInfo": {
48 | "id": "${{BOT_ID}}",
49 | "resource": "api://botid-${{BOT_ID}}"
50 | }
51 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/appPackage/outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/custom-engine-agent/appPackage/outline.png
--------------------------------------------------------------------------------
/src/custom-engine-agent/build/aad.manifest.local.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "ba8e6ff2-657f-46ac-bed6-8665bf726691",
3 | "appId": "250a006b-c87d-40c0-be4d-be72841a0a2d",
4 | "name": "CareerGenieBot-aad",
5 | "accessTokenAcceptedVersion": 2,
6 | "signInAudience": "AzureADMultipleOrgs",
7 | "optionalClaims": {
8 | "idToken": [],
9 | "accessToken": [
10 | {
11 | "name": "idtyp",
12 | "source": null,
13 | "essential": false,
14 | "additionalProperties": []
15 | }
16 | ],
17 | "saml2Token": []
18 | },
19 | "requiredResourceAccess": [
20 | {
21 | "resourceAppId": "00000003-0000-0000-c000-000000000000",
22 | "resourceAccess": [
23 | {
24 | "id": "e1fe6dd8-ba31-4d61-89e7-88639da4683d",
25 | "type": "Scope"
26 | },
27 | {
28 | "id": "570282fd-fa5c-430d-a7fd-fc8dc98a9dca",
29 | "type": "Scope"
30 | }
31 | ]
32 | }
33 | ],
34 | "oauth2Permissions": [
35 | {
36 | "adminConsentDescription": "Allows Teams to call the app's web APIs as the current user.",
37 | "adminConsentDisplayName": "Teams can access app's web APIs",
38 | "id": "d65b865b-d634-43a9-a872-692bebc2b7e9",
39 | "isEnabled": true,
40 | "type": "User",
41 | "userConsentDescription": "Enable Teams to call this app's web APIs with the same rights that you have",
42 | "userConsentDisplayName": "Teams can access app's web APIs and make requests on your behalf",
43 | "value": "access_as_user"
44 | }
45 | ],
46 | "preAuthorizedApplications": [
47 | {
48 | "appId": "1fec8e78-bce4-4aaf-ab1b-5451cc387264",
49 | "permissionIds": [
50 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
51 | ]
52 | },
53 | {
54 | "appId": "5e3ce6c0-2b1f-4285-8d4b-75ee78787346",
55 | "permissionIds": [
56 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
57 | ]
58 | },
59 | {
60 | "appId": "d3590ed6-52b3-4102-aeff-aad2292ab01c",
61 | "permissionIds": [
62 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
63 | ]
64 | },
65 | {
66 | "appId": "00000002-0000-0ff1-ce00-000000000000",
67 | "permissionIds": [
68 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
69 | ]
70 | },
71 | {
72 | "appId": "bc59ab01-8403-45c6-8796-ac3ef710b3e3",
73 | "permissionIds": [
74 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
75 | ]
76 | },
77 | {
78 | "appId": "0ec893e0-5785-4de6-99da-4ed124e5296c",
79 | "permissionIds": [
80 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
81 | ]
82 | },
83 | {
84 | "appId": "4765445b-32c6-49b0-83e6-1d93765276ca",
85 | "permissionIds": [
86 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
87 | ]
88 | },
89 | {
90 | "appId": "4345a7b9-9a63-4910-a426-35363201d503",
91 | "permissionIds": [
92 | "d65b865b-d634-43a9-a872-692bebc2b7e9"
93 | ]
94 | }
95 | ],
96 | "identifierUris": [
97 | "api://botid-e0a45687-5765-4e61-9120-24879c12a27e"
98 | ],
99 | "replyUrlsWithType": [
100 | {
101 | "url": "https://1p3tpxsq-3978.use.devtunnels.ms/auth-end.html",
102 | "type": "Web"
103 | }
104 | ]
105 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/config.ts:
--------------------------------------------------------------------------------
1 | const config = {
2 | botId: process.env.BOT_ID,
3 | botPassword: process.env.BOT_PASSWORD,
4 | azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY,
5 | azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
6 | azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
7 | azureOpenAIEmbeddingDeploymentName: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME,
8 | azureSearchKey: process.env.AZURE_SEARCH_KEY,
9 | azureSearchEndpoint: process.env.AZURE_SEARCH_ENDPOINT,
10 | indexName: process.env.INDEX_NAME,
11 | aadAppClientId: process.env.AAD_APP_CLIENT_ID,
12 | aadAppClientSecret: process.env.AAD_APP_CLIENT_SECRET,
13 | aadAppOauthAuthorityHost: process.env.AAD_APP_OAUTH_AUTHORITY_HOST,
14 | aadAppTenantId: process.env.AAD_APP_TENANT_ID,
15 | botDomain: process.env.BOT_DOMAIN,
16 | aadAppOauthAuthority: process.env.AAD_APP_OAUTH_AUTHORITY,
17 | HR_EMAIL: process.env.HR_EMAIL,
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/env/.env.dev:
--------------------------------------------------------------------------------
1 | # This file includes environment variables that will be committed to git by default.
2 |
3 | # Built-in environment variables
4 | TEAMSFX_ENV=dev
5 | APP_NAME_SUFFIX=dev
6 |
7 | # Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups.
8 | AZURE_SUBSCRIPTION_ID=
9 | AZURE_RESOURCE_GROUP_NAME=
10 | RESOURCE_SUFFIX=
11 |
12 | # Generated during provision, you can also add your own variables.
13 | BOT_ID=
14 | TEAMS_APP_ID=
15 | BOT_AZURE_APP_SERVICE_RESOURCE_ID=
16 | BOT_DOMAIN=
--------------------------------------------------------------------------------
/src/custom-engine-agent/env/.env.local.sample:
--------------------------------------------------------------------------------
1 | # This file includes environment variables that will be committed to git by default.
2 |
3 | # Built-in environment variables
4 | TEAMSFX_ENV=local
5 | APP_NAME_SUFFIX=local
6 |
7 | # Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups.
8 | AZURE_SUBSCRIPTION_ID=
9 | AZURE_RESOURCE_GROUP_NAME=
10 | RESOURCE_SUFFIX=
11 |
12 | # Generated during provision, you can also add your own variables.
13 | BOT_ID=
14 | TEAMS_APP_ID=
15 | BOT_AZURE_APP_SERVICE_RESOURCE_ID=
16 | BOT_DOMAIN=
17 | BOT_ENDPOINT=
18 | AAD_APP_CLIENT_ID=
19 | AAD_APP_OBJECT_ID=
20 | AAD_APP_TENANT_ID=
21 | AAD_APP_OAUTH_AUTHORITY=
22 | AAD_APP_OAUTH_AUTHORITY_HOST=
23 | TEAMS_APP_TENANT_ID=
24 | AAD_APP_ACCESS_AS_USER_PERMISSION_ID=
--------------------------------------------------------------------------------
/src/custom-engine-agent/env/.env.local.user.sample:
--------------------------------------------------------------------------------
1 | SECRET_BOT_PASSWORD=
2 | SECRET_AZURE_OPENAI_API_KEY=''
3 | AZURE_OPENAI_ENDPOINT=''
4 | AZURE_OPENAI_DEPLOYMENT_NAME=''
5 | AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME=''
6 | SECRET_AZURE_SEARCH_KEY=''
7 | AZURE_SEARCH_ENDPOINT=''
8 | INDEX_NAME=''
9 | TEAMS_APP_UPDATE_TIME=
10 | SECRET_AAD_APP_CLIENT_SECRET=
11 | HR_EMAIL=
--------------------------------------------------------------------------------
/src/custom-engine-agent/env/.env.testtool:
--------------------------------------------------------------------------------
1 | # This file includes environment variables that can be committed to git. It's gitignored by default because it represents your local development environment.
2 |
3 | # Built-in environment variables
4 | TEAMSFX_ENV=testtool
5 |
6 | # Environment variables used by test tool
7 | TEAMSAPPTESTER_PORT=56150
8 | TEAMSFX_NOTIFICATION_STORE_FILENAME=.notification.testtoolstore.json
--------------------------------------------------------------------------------
/src/custom-engine-agent/index.ts:
--------------------------------------------------------------------------------
1 | // Import required packages
2 | import * as restify from "restify";
3 | import * as path from 'path';
4 | // This bot's adapter
5 | import adapter from "./adapter";
6 |
7 | // This bot's main dialog.
8 | import app from "./app/app";
9 |
10 | // Create HTTP server.
11 | const server = restify.createServer();
12 | server.use(restify.plugins.bodyParser());
13 |
14 | server.listen(process.env.port || process.env.PORT || 3978, () => {
15 | console.log(`\nBot Started, ${server.name} listening to ${server.url}`);
16 | });
17 | server.get(
18 | '/auth-:name(start|end).html',
19 | restify.plugins.serveStatic({
20 | directory: path.join(__dirname, 'public')
21 | })
22 | );
23 |
24 | // Listen for incoming server requests.
25 | server.post("/api/messages", async (req, res) => {
26 | // Route received a request to adapter for processing
27 | await adapter.process(req, res as any, async (context) => {
28 | // Dispatch to application for routing
29 | await app.run(context);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/infra/azure.bicep:
--------------------------------------------------------------------------------
1 | @maxLength(20)
2 | @minLength(4)
3 | @description('Used to generate names for all resources in this file')
4 | param resourceBaseName string
5 |
6 | @description('Required when create Azure Bot service')
7 | param botAadAppClientId string
8 |
9 | @secure()
10 | @description('Required by Bot Framework package in your bot project')
11 | param botAadAppClientSecret string
12 |
13 | @secure()
14 | param azureOpenAIKey string
15 |
16 | @secure()
17 | param azureOpenAIEndpoint string
18 |
19 | @secure()
20 | param azureOpenAIDeploymentName string
21 |
22 | param webAppSKU string
23 |
24 | @maxLength(42)
25 | param botDisplayName string
26 |
27 | param serverfarmsName string = resourceBaseName
28 | param webAppName string = resourceBaseName
29 | param location string = resourceGroup().location
30 |
31 | // Compute resources for your Web App
32 | resource serverfarm 'Microsoft.Web/serverfarms@2021-02-01' = {
33 | kind: 'app'
34 | location: location
35 | name: serverfarmsName
36 | sku: {
37 | name: webAppSKU
38 | }
39 | }
40 |
41 | // Web App that hosts your bot
42 | resource webApp 'Microsoft.Web/sites@2021-02-01' = {
43 | kind: 'app'
44 | location: location
45 | name: webAppName
46 | properties: {
47 | serverFarmId: serverfarm.id
48 | httpsOnly: true
49 | siteConfig: {
50 | alwaysOn: true
51 | appSettings: [
52 | {
53 | name: 'WEBSITE_RUN_FROM_PACKAGE'
54 | value: '1' // Run Azure App Service from a package file
55 | }
56 | {
57 | name: 'WEBSITE_NODE_DEFAULT_VERSION'
58 | value: '~18' // Set NodeJS version to 18.x for your site
59 | }
60 | {
61 | name: 'RUNNING_ON_AZURE'
62 | value: '1'
63 | }
64 | {
65 | name: 'BOT_ID'
66 | value: botAadAppClientId
67 | }
68 | {
69 | name: 'BOT_PASSWORD'
70 | value: botAadAppClientSecret
71 | }
72 | {
73 | name: 'AZURE_OPENAI_API_KEY'
74 | value: azureOpenAIKey
75 | }
76 | {
77 | name: 'AZURE_OPENAI_ENDPOINT'
78 | value: azureOpenAIEndpoint
79 | }
80 | {
81 | name: 'AZURE_OPENAI_DEPLOYMENT_NAME'
82 | value: azureOpenAIDeploymentName
83 | }
84 | ]
85 | ftpsState: 'FtpsOnly'
86 | }
87 | }
88 | }
89 |
90 | // Register your web service as a bot with the Bot Framework
91 | module azureBotRegistration './botRegistration/azurebot.bicep' = {
92 | name: 'Azure-Bot-registration'
93 | params: {
94 | resourceBaseName: resourceBaseName
95 | botAadAppClientId: botAadAppClientId
96 | botAppDomain: webApp.properties.defaultHostName
97 | botDisplayName: botDisplayName
98 | }
99 | }
100 |
101 | // The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
102 | output BOT_AZURE_APP_SERVICE_RESOURCE_ID string = webApp.id
103 | output BOT_DOMAIN string = webApp.properties.defaultHostName
104 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/infra/azure.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "resourceBaseName": {
6 | "value": "bot${{RESOURCE_SUFFIX}}"
7 | },
8 | "botAadAppClientId": {
9 | "value": "${{BOT_ID}}"
10 | },
11 | "botAadAppClientSecret": {
12 | "value": "${{SECRET_BOT_PASSWORD}}"
13 | },
14 | "azureOpenAIKey": {
15 | "value": "${{SECRET_AZURE_OPENAI_API_KEY}}"
16 | },
17 | "azureOpenAIEndpoint": {
18 | "value": "${{AZURE_OPENAI_ENDPOINT}}"
19 | },
20 | "azureOpenAIDeploymentName": {
21 | "value": "${{AZURE_OPENAI_DEPLOYMENT_NAME}}"
22 | },
23 | "webAppSKU": {
24 | "value": "B1"
25 | },
26 | "botDisplayName": {
27 | "value": "CareerGenie"
28 | }
29 | }
30 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/infra/botRegistration/azurebot.bicep:
--------------------------------------------------------------------------------
1 | @maxLength(20)
2 | @minLength(4)
3 | @description('Used to generate names for all resources in this file')
4 | param resourceBaseName string
5 |
6 | @maxLength(42)
7 | param botDisplayName string
8 |
9 | param botServiceName string = resourceBaseName
10 | param botServiceSku string = 'F0'
11 | param botAadAppClientId string
12 | param botAppDomain string
13 |
14 | // Register your web service as a bot with the Bot Framework
15 | resource botService 'Microsoft.BotService/botServices@2021-03-01' = {
16 | kind: 'azurebot'
17 | location: 'global'
18 | name: botServiceName
19 | properties: {
20 | displayName: botDisplayName
21 | endpoint: 'https://${botAppDomain}/api/messages'
22 | msaAppId: botAadAppClientId
23 | }
24 | sku: {
25 | name: botServiceSku
26 | }
27 | }
28 |
29 | // Connect the bot service to Microsoft Teams
30 | resource botServiceMsTeamsChannel 'Microsoft.BotService/botServices/channels@2021-03-01' = {
31 | parent: botService
32 | location: 'global'
33 | name: 'MsTeamsChannel'
34 | properties: {
35 | channelName: 'MsTeamsChannel'
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/infra/botRegistration/readme.md:
--------------------------------------------------------------------------------
1 | The `azurebot.bicep` module is provided to help you create Azure Bot service when you don't use Azure to host your app. If you use Azure as infrastrcture for your app, `azure.bicep` under infra folder already leverages this module to create Azure Bot service for you. You don't need to deploy `azurebot.bicep` again.
--------------------------------------------------------------------------------
/src/custom-engine-agent/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "careergenie",
3 | "version": "1.0.0",
4 | "msteams": {
5 | "teamsAppId": null
6 | },
7 | "description": "Microsoft Teams Toolkit AI Chat Bot Sample with Teams AI Library",
8 | "engines": {
9 | "node": "16 || 18"
10 | },
11 | "author": "Microsoft",
12 | "license": "MIT",
13 | "main": "./lib/src/index.js",
14 | "scripts": {
15 | "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev",
16 | "dev:teamsfx:testtool": "env-cmd --silent -f .localConfigs.testTool npm run dev",
17 | "dev:teamsfx:launch-testtool": "env-cmd --silent -f env/.env.testtool teamsapptester start",
18 | "dev": "nodemon --exec node --inspect=9239 --signal SIGINT -r ts-node/register ./src/index.ts",
19 | "build": "tsc --build && shx cp -r ./src/prompts ./lib/src",
20 | "start": "node ./lib/src/index.js",
21 | "test": "echo \"Error: no test specified\" && exit 1",
22 | "watch": "nodemon --exec \"npm run start\""
23 | },
24 | "repository": {
25 | "type": "git",
26 | "url": "https://github.com"
27 | },
28 | "dependencies": {
29 | "@microsoft/microsoft-graph-client": "^3.0.7",
30 | "@microsoft/microsoft-graph-types": "^2.40.0",
31 | "@microsoft/teams-ai": "^1.5.3",
32 | "botbuilder": "^4.23.1",
33 | "openai": "^4.68.2",
34 | "restify": "^10.0.0"
35 | },
36 | "devDependencies": {
37 | "@types/node": "^18.0.0",
38 | "@types/restify": "^8.5.5",
39 | "env-cmd": "^10.1.0",
40 | "nodemon": "^2.0.22",
41 | "shx": "^0.3.3",
42 | "ts-node": "^10.4.0",
43 | "typescript": "^4.4.4"
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/prompts/chat/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema": 1.1,
3 | "description": "A bot that can chat with users",
4 | "type": "completion",
5 | "completion": {
6 | "completion_type": "chat",
7 | "include_history": true,
8 | "include_input": true,
9 | "max_input_tokens": 2800,
10 | "max_tokens": 1000,
11 | "temperature": 0.9,
12 | "top_p": 0.0,
13 | "presence_penalty": 0.6,
14 | "frequency_penalty": 0.0,
15 | "data_sources": [
16 | {
17 | "type": "azure_search",
18 | "parameters": {
19 | "endpoint": "$searchEndpoint",
20 | "index_name": "$indexName",
21 | "authentication": {
22 | "type": "api_key",
23 | "key": "$searchApiKey"
24 | },
25 | "query_type":"vector",
26 | "in_scope": true,
27 | "strictness": 3,
28 | "top_n_documents": 3,
29 | "embedding_dependency": {
30 | "type": "deployment_name",
31 | "deployment_name": "$azureOpenAIEmbeddingDeploymentName"
32 | }
33 | }
34 | }
35 | ]
36 | }
37 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/prompts/chat/skprompt.txt:
--------------------------------------------------------------------------------
1 | You are a career specialist named "Career Genie" that helps Human Resources team for finding the right candidate for the jobs.
2 | You are friendly and professional.
3 | You always greet users with excitement and introduce yourself first.
4 | You like using emojis where appropriate.
5 | Always mention all citations in your content.
--------------------------------------------------------------------------------
/src/custom-engine-agent/prompts/monologue/actions.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "createList",
4 | "description": "Creates a new list with an optional set of initial Candidates",
5 | "parameters": {
6 | "type": "object",
7 | "properties": {
8 | "list": {
9 | "type": "string",
10 | "description": "The name of the list to create"
11 | },
12 | "Candidates": {
13 | "type": "array",
14 | "description": "The Candidates to add to the list",
15 | "Candidates": {
16 | "type": "string"
17 | }
18 | }
19 | },
20 | "required": [
21 | "list"
22 | ]
23 | }
24 | },
25 | {
26 | "name": "deleteList",
27 | "description": "Deletes a list",
28 | "parameters": {
29 | "type": "object",
30 | "properties": {
31 | "list": {
32 | "type": "string",
33 | "description": "The name of the list to delete"
34 | }
35 | },
36 | "required": [
37 | "list"
38 | ]
39 | }
40 | },
41 | {
42 | "name": "addCandidates",
43 | "description": "Adds one or more Candidates to a list",
44 | "parameters": {
45 | "type": "object",
46 | "properties": {
47 | "list": {
48 | "type": "string",
49 | "description": "The name of the list to add the item to"
50 | },
51 | "Candidates": {
52 | "type": "array",
53 | "description": "The Candidates to add to the list",
54 | "Candidates": {
55 | "type": "string"
56 | }
57 | }
58 | },
59 | "required": [
60 | "list",
61 | "Candidates"
62 | ]
63 | }
64 | },
65 | {
66 | "name": "removeCandidates",
67 | "description": "Removes one or more Candidates from a list",
68 | "parameters": {
69 | "type": "object",
70 | "properties": {
71 | "list": {
72 | "type": "string",
73 | "description": "The name of the list to remove the item from"
74 | },
75 | "Candidates": {
76 | "type": "array",
77 | "description": "The Candidates to remove from the list",
78 | "Candidates": {
79 | "type": "string"
80 | }
81 | }
82 | },
83 | "required": [
84 | "list",
85 | "Candidates"
86 | ]
87 | }
88 | },
89 | {
90 | "name": "sendLists",
91 | "description": "Send list of Candidates to Human Resources, aka HR for scheduling interviews",
92 | "parameters": {
93 | "type": "object",
94 | "properties": {
95 | "list": {
96 | "type": "string",
97 | "description": "The name of the list to send Human Resources, aka HR for scheduling interviews"
98 | },
99 | "Candidates": {
100 | "type": "array",
101 | "description": "The Candidates in the list to send Human Resources, aka HR for scheduling interviews",
102 | "Candidates": {
103 | "type": "string"
104 | }
105 | }
106 | },
107 | "required": [
108 | "list",
109 | "Candidates"
110 | ]
111 | }
112 | }
113 | ]
--------------------------------------------------------------------------------
/src/custom-engine-agent/prompts/monologue/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema": 1.1,
3 | "description": "A bot that can chat with users",
4 | "type": "completion",
5 | "completion": {
6 | "completion_type": "chat",
7 | "include_history": true,
8 | "include_input": true,
9 | "max_input_tokens": 2800,
10 | "max_tokens": 1000,
11 | "temperature": 0.9,
12 | "top_p": 0.0,
13 | "presence_penalty": 0.6,
14 | "frequency_penalty": 0.0
15 | },
16 | "augmentation": {
17 | "augmentation_type": "monologue"
18 | }
19 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/prompts/monologue/skprompt.txt:
--------------------------------------------------------------------------------
1 | You are a career specialist named "Career Genie" that helps Human Resources team who can manage lists of Candidates.
2 | You are friendly and professional. You like using emojis where appropriate.
3 | Always share the lists in bullet points.
4 |
5 | rules:
6 | - only create lists the user has explicitly asked to create.
7 | - only add Candidates to a list that the user has asked to have added.
8 | - if multiple lists are being manipulated, call a separate action for each list.
9 | - if Candidates are being added and removed from a list, call a separate action for each operation.
10 | - if user asks for a summary, share all the lists and candidates.
11 | - only send an email to HR if user has explicitly asked to send.
12 |
13 | Current lists:
14 | {{$conversation.lists}}
--------------------------------------------------------------------------------
/src/custom-engine-agent/public/auth-end.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Login End Page
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/adapter.ts:
--------------------------------------------------------------------------------
1 | // Import required bot services.
2 | // See https://aka.ms/bot-services to learn more about the different parts of a bot.
3 | import {
4 | CloudAdapter,
5 | ConfigurationBotFrameworkAuthentication,
6 | ConfigurationServiceClientCredentialFactory,
7 | } from "botbuilder";
8 |
9 | // This bot's main dialog.
10 | import config from "./config";
11 | import { TeamsAdapter } from '@microsoft/teams-ai';
12 | // const botFrameworkAuthentication = new ConfigurationBotFrameworkAuthentication(
13 | // {},
14 | // new ConfigurationServiceClientCredentialFactory({
15 | // MicrosoftAppId: config.botId,
16 | // MicrosoftAppPassword: process.env.BOT_PASSWORD,
17 | // MicrosoftAppType: "MultiTenant",
18 | // })
19 | // );
20 |
21 | // Create adapter.
22 | // See https://aka.ms/about-bot-adapter to learn more about how bots work.
23 | const adapter = new TeamsAdapter(
24 | {},
25 | new ConfigurationServiceClientCredentialFactory({
26 | MicrosoftAppId: config.botId,
27 | MicrosoftAppPassword: process.env.BOT_PASSWORD,
28 | MicrosoftAppType: 'MultiTenant',
29 | })
30 | );
31 |
32 | // Catch-all for errors.
33 | const onTurnErrorHandler = async (context, error) => {
34 | // This check writes out errors to console log .vs. app insights.
35 | // NOTE: In production environment, you should consider logging this to Azure
36 | // application insights.
37 | console.error(`\n [onTurnError] unhandled error: ${error}`);
38 |
39 | // Only send error message for user messages, not for other message types so the bot doesn't spam a channel or chat.
40 | if (context.activity.type === "message") {
41 | // Send a trace activity, which will be displayed in Bot Framework Emulator
42 | await context.sendTraceActivity(
43 | "OnTurnError Trace",
44 | `${error}`,
45 | "https://www.botframework.com/schemas/error",
46 | "TurnError"
47 | );
48 |
49 | // Send a message to the user
50 | await context.sendActivity("The bot encountered an error or bug.");
51 | await context.sendActivity("To continue to run this bot, please fix the bot source code.");
52 | }
53 | };
54 |
55 | // Set the onTurnError for the singleton CloudAdapter.
56 | adapter.onTurnError = onTurnErrorHandler;
57 |
58 | export default adapter;
59 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/app/actions.ts:
--------------------------------------------------------------------------------
1 | import { getUserDisplayName, ApplicationTurnState } from './app';
2 | import { Client } from "@microsoft/microsoft-graph-client";
3 | import config from '../config';
4 |
5 | function getCandidates(state: ApplicationTurnState, list: string): string[] {
6 | ensureListExists(state, list);
7 | return state.conversation.lists[list];
8 | }
9 |
10 | function setCandidates(state: ApplicationTurnState, list: string, Candidates: string[]): void {
11 | ensureListExists(state, list);
12 | state.conversation.lists[list] = Candidates ?? [];
13 | }
14 |
15 | function ensureListExists(state: ApplicationTurnState, listName: string): void {
16 | if (typeof state.conversation.lists != 'object') {
17 | state.conversation.lists = {};
18 | }
19 |
20 | if (!Object.prototype.hasOwnProperty.call(state.conversation.lists, listName)) {
21 | state.conversation.lists[listName] = [];
22 | }
23 | }
24 |
25 | function deleteList(state: ApplicationTurnState, listName: string): void {
26 | if (
27 | typeof state.conversation.lists == 'object' &&
28 | Object.prototype.hasOwnProperty.call(state.conversation.lists, listName)
29 | ) {
30 | delete state.conversation.lists[listName];
31 | }
32 | }
33 |
34 | async function sendLists(state: ApplicationTurnState, token): Promise {
35 | const email = await createEmailContent(state.conversation.lists, token);
36 | try {
37 | const client = Client.init({
38 | authProvider: (done) => {
39 | done(null, token);
40 | }
41 | });
42 | const sendEmail = await client.api('/me/sendMail').post(JSON.stringify(email));
43 | if (sendEmail.ok) {
44 | return email.message.body.content;
45 | }
46 | else {
47 | console.log(`Error ${sendEmail.status} calling Graph in sendToHR: ${sendEmail.statusText}`);
48 | return 'Error sending email';
49 | }
50 | } catch (error) {
51 | console.error('Error in sendLists:', error);
52 | throw error;
53 | }
54 | }
55 |
56 | async function createEmailContent(lists, token) {
57 | let emailContent = '';
58 | for (const listName in lists) {
59 | if (lists.hasOwnProperty(listName)) {
60 | emailContent += `${listName}:\n`;
61 | lists[listName].forEach(candidate => {
62 | emailContent += ` • ${candidate}\n`;
63 | });
64 | emailContent += '\n'; // Add an extra line between different lists
65 | }
66 | }
67 |
68 | const profileName = await getUserDisplayName(token);
69 |
70 | const email = {
71 | "message": {
72 | "subject": "Request to Schedule Interviews with Shortlisted Candidates",
73 | "body": {
74 | "contentType": "Text",
75 | "content": `Hello HR Team, \nI hope this email finds you well. \n\nCould you please assist in scheduling 1:1 interviews with the following shortlisted candidates? \n\n${emailContent} Please arrange suitable times and send out the calendar invites accordingly. \n\n Best Regards, \n ${profileName}`
76 | },
77 | "toRecipients": [
78 | {
79 | "emailAddress": {
80 | "address": `${config.HR_EMAIL}`
81 | }
82 | }
83 | ]
84 | },
85 | "saveToSentCandidates": "true"
86 | };
87 | return await email;
88 | }
89 |
90 | export { getCandidates, setCandidates, ensureListExists, deleteList, sendLists };
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/app/card.ts:
--------------------------------------------------------------------------------
1 | import { AdaptiveCard, Message, Utilities } from '@microsoft/teams-ai';
2 | /**
3 | * Create an adaptive card from a prompt response.
4 | * @param {Message} response The prompt response to create the card from.
5 | * @returns {AdaptiveCard} The response card.
6 | */
7 |
8 | //Adaptive card to display the response and citations
9 | export function createResponseCard(response: Message): AdaptiveCard {
10 | const citationCards = response.context?.citations.map((citation, i) => ({
11 | type: 'Action.ShowCard',
12 | title: `${i+1}`,
13 | card: {
14 | type: 'AdaptiveCard',
15 | body: [
16 | {
17 | type: 'TextBlock',
18 | text: citation.title,
19 | fontType: 'Default',
20 | weight: 'Bolder'
21 | },
22 | {
23 | type: 'TextBlock',
24 | text: citation.content,
25 | wrap: true
26 | }
27 | ]
28 | }
29 | }));
30 |
31 | const text = Utilities.formatCitationsResponse(response.content!);
32 | return {
33 | type: 'AdaptiveCard',
34 | body: [
35 | {
36 | type: 'TextBlock',
37 | text: text,
38 | wrap: true
39 | },
40 | {
41 | type: 'TextBlock',
42 | text: 'Citations',
43 | wrap: true,
44 | fontType: 'Default',
45 | weight: 'Bolder'
46 | },
47 | {
48 | type: 'ActionSet',
49 | actions: citationCards
50 | }
51 | ],
52 | $schema: 'http://adaptivecards.io/schemas/adaptive-card.json',
53 | version: '1.5'
54 | };
55 | }
56 |
57 |
58 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/config.ts:
--------------------------------------------------------------------------------
1 | const config = {
2 | botId: process.env.BOT_ID,
3 | botPassword: process.env.BOT_PASSWORD,
4 | azureOpenAIKey: process.env.AZURE_OPENAI_API_KEY,
5 | azureOpenAIEndpoint: process.env.AZURE_OPENAI_ENDPOINT,
6 | azureOpenAIDeploymentName: process.env.AZURE_OPENAI_DEPLOYMENT_NAME,
7 | azureOpenAIEmbeddingDeploymentName: process.env.AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME,
8 | azureSearchKey: process.env.AZURE_SEARCH_KEY,
9 | azureSearchEndpoint: process.env.AZURE_SEARCH_ENDPOINT,
10 | indexName: process.env.INDEX_NAME,
11 | aadAppClientId: process.env.AAD_APP_CLIENT_ID,
12 | aadAppClientSecret: process.env.AAD_APP_CLIENT_SECRET,
13 | aadAppOauthAuthorityHost: process.env.AAD_APP_OAUTH_AUTHORITY_HOST,
14 | aadAppTenantId: process.env.AAD_APP_TENANT_ID,
15 | botDomain: process.env.BOT_DOMAIN,
16 | aadAppOauthAuthority: process.env.AAD_APP_OAUTH_AUTHORITY,
17 | HR_EMAIL: process.env.HR_EMAIL,
18 | };
19 |
20 | export default config;
21 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/index.ts:
--------------------------------------------------------------------------------
1 | // Import required packages
2 | import * as restify from "restify";
3 | import * as path from 'path';
4 | // This bot's adapter
5 | import adapter from "./adapter";
6 |
7 | // This bot's main dialog.
8 | import app from "./app/app";
9 |
10 | // Create HTTP server.
11 | const server = restify.createServer();
12 | server.use(restify.plugins.bodyParser());
13 |
14 | server.listen(process.env.port || process.env.PORT || 3978, () => {
15 | console.log(`\nBot Started, ${server.name} listening to ${server.url}`);
16 | });
17 | server.get(
18 | '/auth-:name(start|end).html',
19 | restify.plugins.serveStatic({
20 | directory: path.join(__dirname, 'public')
21 | })
22 | );
23 |
24 | // Listen for incoming server requests.
25 | server.post("/api/messages", async (req, res) => {
26 | // Route received a request to adapter for processing
27 | await adapter.process(req, res as any, async (context) => {
28 | // Dispatch to application for routing
29 | await app.run(context);
30 | });
31 | });
32 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/prompts/chat/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema": 1.1,
3 | "description": "A bot that can chat with users",
4 | "type": "completion",
5 | "completion": {
6 | "completion_type": "chat",
7 | "include_history": true,
8 | "include_input": true,
9 | "max_input_tokens": 2800,
10 | "max_tokens": 1000,
11 | "temperature": 0.9,
12 | "top_p": 0.0,
13 | "presence_penalty": 0.6,
14 | "frequency_penalty": 0.0,
15 | "data_sources": [
16 | {
17 | "type": "azure_search",
18 | "parameters": {
19 | "endpoint": "$searchEndpoint",
20 | "index_name": "$indexName",
21 | "authentication": {
22 | "type": "api_key",
23 | "key": "$searchApiKey"
24 | },
25 | "query_type":"vector",
26 | "in_scope": true,
27 | "strictness": 3,
28 | "top_n_documents": 3,
29 | "embedding_dependency": {
30 | "type": "deployment_name",
31 | "deployment_name": "$azureOpenAIEmbeddingDeploymentName"
32 | }
33 | }
34 | }
35 | ]
36 | }
37 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/prompts/chat/skprompt.txt:
--------------------------------------------------------------------------------
1 | You are a career specialist named "Career Genie" that helps Human Resources team for finding the right candidate for the jobs.
2 | You are friendly and professional.
3 | You always greet users with excitement and introduce yourself first.
4 | You like using emojis where appropriate.
5 | Always mention all citations in your content.
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/prompts/monologue/actions.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name": "createList",
4 | "description": "Creates a new list with an optional set of initial Candidates",
5 | "parameters": {
6 | "type": "object",
7 | "properties": {
8 | "list": {
9 | "type": "string",
10 | "description": "The name of the list to create"
11 | },
12 | "Candidates": {
13 | "type": "array",
14 | "description": "The Candidates to add to the list",
15 | "Candidates": {
16 | "type": "string"
17 | }
18 | }
19 | },
20 | "required": [
21 | "list"
22 | ]
23 | }
24 | },
25 | {
26 | "name": "deleteList",
27 | "description": "Deletes a list",
28 | "parameters": {
29 | "type": "object",
30 | "properties": {
31 | "list": {
32 | "type": "string",
33 | "description": "The name of the list to delete"
34 | }
35 | },
36 | "required": [
37 | "list"
38 | ]
39 | }
40 | },
41 | {
42 | "name": "addCandidates",
43 | "description": "Adds one or more Candidates to a list",
44 | "parameters": {
45 | "type": "object",
46 | "properties": {
47 | "list": {
48 | "type": "string",
49 | "description": "The name of the list to add the item to"
50 | },
51 | "Candidates": {
52 | "type": "array",
53 | "description": "The Candidates to add to the list",
54 | "Candidates": {
55 | "type": "string"
56 | }
57 | }
58 | },
59 | "required": [
60 | "list",
61 | "Candidates"
62 | ]
63 | }
64 | },
65 | {
66 | "name": "removeCandidates",
67 | "description": "Removes one or more Candidates from a list",
68 | "parameters": {
69 | "type": "object",
70 | "properties": {
71 | "list": {
72 | "type": "string",
73 | "description": "The name of the list to remove the item from"
74 | },
75 | "Candidates": {
76 | "type": "array",
77 | "description": "The Candidates to remove from the list",
78 | "Candidates": {
79 | "type": "string"
80 | }
81 | }
82 | },
83 | "required": [
84 | "list",
85 | "Candidates"
86 | ]
87 | }
88 | },
89 | {
90 | "name": "sendLists",
91 | "description": "Send list of Candidates to Human Resources, aka HR for scheduling interviews",
92 | "parameters": {
93 | "type": "object",
94 | "properties": {
95 | "list": {
96 | "type": "string",
97 | "description": "The name of the list to send Human Resources, aka HR for scheduling interviews"
98 | },
99 | "Candidates": {
100 | "type": "array",
101 | "description": "The Candidates in the list to send Human Resources, aka HR for scheduling interviews",
102 | "Candidates": {
103 | "type": "string"
104 | }
105 | }
106 | },
107 | "required": [
108 | "list",
109 | "Candidates"
110 | ]
111 | }
112 | }
113 | ]
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/prompts/monologue/config.json:
--------------------------------------------------------------------------------
1 | {
2 | "schema": 1.1,
3 | "description": "A bot that can chat with users",
4 | "type": "completion",
5 | "completion": {
6 | "completion_type": "chat",
7 | "include_history": true,
8 | "include_input": true,
9 | "max_input_tokens": 2800,
10 | "max_tokens": 1000,
11 | "temperature": 0.9,
12 | "top_p": 0.0,
13 | "presence_penalty": 0.6,
14 | "frequency_penalty": 0.0
15 | },
16 | "augmentation": {
17 | "augmentation_type": "monologue"
18 | }
19 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/prompts/monologue/skprompt.txt:
--------------------------------------------------------------------------------
1 | You are a career specialist named "Career Genie" that helps Human Resources team who can manage lists of Candidates.
2 | You are friendly and professional. You like using emojis where appropriate.
3 | Always share the lists in bullet points.
4 |
5 | rules:
6 | - only create lists the user has explicitly asked to create.
7 | - only add Candidates to a list that the user has asked to have added.
8 | - if multiple lists are being manipulated, call a separate action for each list.
9 | - if Candidates are being added and removed from a list, call a separate action for each operation.
10 | - if user asks for a summary, share all the lists and candidates.
11 | - only send an email to HR if user has explicitly asked to send.
12 |
13 | Current lists:
14 | {{$conversation.lists}}
--------------------------------------------------------------------------------
/src/custom-engine-agent/src/public/auth-end.html:
--------------------------------------------------------------------------------
1 |
2 |
3 | Login End Page
4 |
5 |
6 |
7 |
8 |
9 |
14 |
15 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/teamsapp.local.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.5/yaml.schema.json
2 | # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
3 | # Visit https://aka.ms/teamsfx-actions for details on actions
4 | version: v1.5
5 |
6 | provision:
7 | - uses: aadApp/create # Creates a new Entra ID (AAD) app to authenticate users if the environment variable that stores clientId is empty
8 | with:
9 | name: CareerGenieBot-aad # Note: when you run aadApp/update, the AAD app name will be updated based on the definition in manifest. If you don't want to change the name, make sure the name in AAD manifest is the same with the name defined here.
10 | generateClientSecret: true # If the value is false, the action will not generate client secret for you
11 | signInAudience: "AzureADMultipleOrgs" # Authenticate users with a Microsoft work or school account in your organization's Azure AD tenant (for example, single tenant).
12 | writeToEnvironmentFile: # Write the information of created resources into environment file for the specified environment variable(s).
13 | clientId: AAD_APP_CLIENT_ID
14 | clientSecret: SECRET_AAD_APP_CLIENT_SECRET # Environment variable that starts with `SECRET_` will be stored to the .env.{envName}.user environment file
15 | objectId: AAD_APP_OBJECT_ID
16 | tenantId: AAD_APP_TENANT_ID
17 | authority: AAD_APP_OAUTH_AUTHORITY
18 | authorityHost: AAD_APP_OAUTH_AUTHORITY_HOST
19 |
20 | # Creates a Teams app
21 | - uses: teamsApp/create
22 | with:
23 | # Teams app name
24 | name: CareerGenie${{APP_NAME_SUFFIX}}
25 | # Write the information of created resources into environment file for
26 | # the specified environment variable(s).
27 | writeToEnvironmentFile:
28 | teamsAppId: TEAMS_APP_ID
29 |
30 | # Automates the creation an Azure AD app registration which is required for a bot.
31 | # The Bot ID (AAD app client ID) and Bot Password (AAD app client secret) are saved to an environment file.
32 | - uses: botAadApp/create
33 | with:
34 | name: CareerGenieAAD
35 | writeToEnvironmentFile:
36 | botId: BOT_ID
37 | botPassword: SECRET_BOT_PASSWORD
38 |
39 | # Automates the creation and configuration of a Bot Framework registration which is required for a bot.
40 | # This configures the bot to use the Azure AD app registration created in the previous step.
41 | # Teams Toolkit automatically creates a local Dev Tunnel URL and updates BOT_ENDPOINT when debugging (F5).
42 | - uses: botFramework/create
43 | with:
44 | botId: ${{BOT_ID}}
45 | name: CareerGenieBot
46 | messagingEndpoint: ${{BOT_ENDPOINT}}/api/messages
47 | description: ""
48 | channels:
49 | - name: msteams
50 |
51 |
52 | - uses: aadApp/update # Apply the AAD manifest to an existing AAD app. Will use the object id in manifest file to determine which AAD app to update.
53 | with:
54 | manifestPath: ./aad.manifest.json # Relative path to teamsfx folder. Environment variables in manifest will be replaced before apply to AAD app
55 | outputFilePath: ./build/aad.manifest.${{TEAMSFX_ENV}}.json
56 |
57 | # Validate using manifest schema
58 | - uses: teamsApp/validateManifest
59 | with:
60 | # Path to manifest template
61 | manifestPath: ./appPackage/manifest.json
62 |
63 | # Build Teams app package with latest env value
64 | - uses: teamsApp/zipAppPackage
65 | with:
66 | # Path to manifest template
67 | manifestPath: ./appPackage/manifest.json
68 | outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
69 | outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
70 | # Validate app package using validation rules
71 | - uses: teamsApp/validateAppPackage
72 | with:
73 | # Relative path to this file. This is the path for built zip file.
74 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
75 |
76 | # Apply the Teams app manifest to an existing Teams app in
77 | # Teams Developer Portal.
78 | # Will use the app id in manifest file to determine which Teams app to update.
79 | - uses: teamsApp/update
80 | with:
81 | # Relative path to this file. This is the path for built zip file.
82 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
83 |
84 | deploy:
85 | # Run npm command
86 | - uses: cli/runNpmCommand
87 | name: install dependencies
88 | with:
89 | args: install --no-audit
90 |
91 | # Generate runtime environment variables
92 | - uses: file/createOrUpdateEnvironmentFile
93 | with:
94 | target: ./.localConfigs
95 | envs:
96 | BOT_ID: ${{BOT_ID}}
97 | BOT_PASSWORD: ${{SECRET_BOT_PASSWORD}}
98 | BOT_DOMAIN: ${{BOT_DOMAIN}}
99 | AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}}
100 | AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}}
101 | AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}}
102 | AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME: ${{AZURE_OPENAI_EMBEDDING_DEPLOYMENT_NAME}}
103 | AZURE_SEARCH_KEY: ${{SECRET_AZURE_SEARCH_KEY}}
104 | AZURE_SEARCH_ENDPOINT: ${{AZURE_SEARCH_ENDPOINT}}
105 | INDEX_NAME: ${{INDEX_NAME}}
106 | AAD_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}}
107 | AAD_APP_CLIENT_SECRET: ${{SECRET_AAD_APP_CLIENT_SECRET}}
108 | AAD_APP_TENANT_ID: ${{AAD_APP_TENANT_ID}}
109 | AAD_APP_OAUTH_AUTHORITY_HOST: ${{AAD_APP_OAUTH_AUTHORITY_HOST}}
110 | AAD_APP_OAUTH_AUTHORITY: ${{AAD_APP_OAUTH_AUTHORITY}}
111 | HR_EMAIL: ${{HR_EMAIL}}
112 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/teamsapp.testtool.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.5/yaml.schema.json
2 | # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
3 | # Visit https://aka.ms/teamsfx-actions for details on actions
4 | version: v1.5
5 |
6 | deploy:
7 | # Install development tool(s)
8 | - uses: devTool/install
9 | with:
10 | testTool:
11 | version: ~0.2.1
12 | symlinkDir: ./devTools/teamsapptester
13 |
14 | # Run npm command
15 | - uses: cli/runNpmCommand
16 | with:
17 | args: install --no-audit
18 |
19 | # Generate runtime environment variables
20 | - uses: file/createOrUpdateEnvironmentFile
21 | with:
22 | target: ./.localConfigs.testTool
23 | envs:
24 | AZURE_OPENAI_API_KEY: ${{SECRET_AZURE_OPENAI_API_KEY}}
25 | AZURE_OPENAI_ENDPOINT: ${{AZURE_OPENAI_ENDPOINT}}
26 | AZURE_OPENAI_DEPLOYMENT_NAME: ${{AZURE_OPENAI_DEPLOYMENT_NAME}}
27 | TEAMSFX_NOTIFICATION_STORE_FILENAME: ${{TEAMSFX_NOTIFICATION_STORE_FILENAME}}
--------------------------------------------------------------------------------
/src/custom-engine-agent/teamsapp.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.5/yaml.schema.json
2 | # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
3 | # Visit https://aka.ms/teamsfx-actions for details on actions
4 | version: v1.5
5 |
6 | environmentFolderPath: ./env
7 |
8 | # Triggered when 'teamsapp provision' is executed
9 | provision:
10 | # Creates a Teams app
11 | - uses: teamsApp/create
12 | with:
13 | # Teams app name
14 | name: CareerGenie${{APP_NAME_SUFFIX}}
15 | # Write the information of created resources into environment file for
16 | # the specified environment variable(s).
17 | writeToEnvironmentFile:
18 | teamsAppId: TEAMS_APP_ID
19 |
20 | # Create or reuse an existing Microsoft Entra application for bot.
21 | - uses: aadApp/create
22 | with:
23 | # The Microsoft Entra application's display name
24 | name: CareerGenie${{APP_NAME_SUFFIX}}
25 | generateClientSecret: true
26 | signInAudience: AzureADMultipleOrgs
27 | writeToEnvironmentFile:
28 | # The Microsoft Entra application's client id created for bot.
29 | clientId: BOT_ID
30 | # The Microsoft Entra application's client secret created for bot.
31 | clientSecret: SECRET_BOT_PASSWORD
32 | # The Microsoft Entra application's object id created for bot.
33 | objectId: BOT_OBJECT_ID
34 |
35 | - uses: arm/deploy # Deploy given ARM templates parallelly.
36 | with:
37 | # AZURE_SUBSCRIPTION_ID is a built-in environment variable,
38 | # if its value is empty, TeamsFx will prompt you to select a subscription.
39 | # Referencing other environment variables with empty values
40 | # will skip the subscription selection prompt.
41 | subscriptionId: ${{AZURE_SUBSCRIPTION_ID}}
42 | # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable,
43 | # if its value is empty, TeamsFx will prompt you to select or create one
44 | # resource group.
45 | # Referencing other environment variables with empty values
46 | # will skip the resource group selection prompt.
47 | resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}}
48 | templates:
49 | - path: ./infra/azure.bicep # Relative path to this file
50 | # Relative path to this yaml file.
51 | # Placeholders will be replaced with corresponding environment
52 | # variable before ARM deployment.
53 | parameters: ./infra/azure.parameters.json
54 | # Required when deploying ARM template
55 | deploymentName: Create-resources-for-bot
56 | # Teams Toolkit will download this bicep CLI version from github for you,
57 | # will use bicep CLI in PATH if you remove this config.
58 | bicepCliVersion: v0.9.1
59 |
60 | # Validate using manifest schema
61 | - uses: teamsApp/validateManifest
62 | with:
63 | # Path to manifest template
64 | manifestPath: ./appPackage/manifest.json
65 | # Build Teams app package with latest env value
66 | - uses: teamsApp/zipAppPackage
67 | with:
68 | # Path to manifest template
69 | manifestPath: ./appPackage/manifest.json
70 | outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
71 | outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
72 | # Validate app package using validation rules
73 | - uses: teamsApp/validateAppPackage
74 | with:
75 | # Relative path to this file. This is the path for built zip file.
76 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
77 | # Apply the Teams app manifest to an existing Teams app in
78 | # Teams Developer Portal.
79 | # Will use the app id in manifest file to determine which Teams app to update.
80 | - uses: teamsApp/update
81 | with:
82 | # Relative path to this file. This is the path for built zip file.
83 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
84 |
85 | # Triggered when 'teamsapp deploy' is executed
86 | deploy:
87 | # Run npm command
88 | - uses: cli/runNpmCommand
89 | name: install dependencies
90 | with:
91 | args: install
92 | - uses: cli/runNpmCommand
93 | name: build app
94 | with:
95 | args: run build --if-present
96 | # Deploy your application to Azure App Service using the zip deploy feature.
97 | # For additional details, refer to https://aka.ms/zip-deploy-to-app-services.
98 | - uses: azureAppService/zipDeploy
99 | with:
100 | # Deploy base folder
101 | artifactFolder: .
102 | # Ignore file location, leave blank will ignore nothing
103 | ignoreFile: .webappignore
104 | # The resource id of the cloud resource to be deployed to.
105 | # This key will be generated by arm/deploy action automatically.
106 | # You can replace it with your existing Azure Resource id
107 | # or add it to your environment variable file.
108 | resourceId: ${{BOT_AZURE_APP_SERVICE_RESOURCE_ID}}
109 |
110 | # Triggered when 'teamsapp publish' is executed
111 | publish:
112 | # Validate using manifest schema
113 | - uses: teamsApp/validateManifest
114 | with:
115 | # Path to manifest template
116 | manifestPath: ./appPackage/manifest.json
117 | # Build Teams app package with latest env value
118 | - uses: teamsApp/zipAppPackage
119 | with:
120 | # Path to manifest template
121 | manifestPath: ./appPackage/manifest.json
122 | outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
123 | outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
124 | # Validate app package using validation rules
125 | - uses: teamsApp/validateAppPackage
126 | with:
127 | # Relative path to this file. This is the path for built zip file.
128 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
129 | # Apply the Teams app manifest to an existing Teams app in
130 | # Teams Developer Portal.
131 | # Will use the app id in manifest file to determine which Teams app to update.
132 | - uses: teamsApp/update
133 | with:
134 | # Relative path to this file. This is the path for built zip file.
135 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
136 | # Publish the app to
137 | # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps)
138 | # for review and approval
139 | - uses: teamsApp/publishAppPackage
140 | with:
141 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
142 | # Write the information of created resources into environment file for
143 | # the specified environment variable(s).
144 | writeToEnvironmentFile:
145 | publishedAppId: TEAMS_APP_PUBLISHED_APP_ID
146 | projectId: 99b60261-b586-4dde-98ba-7ea5cefafa46
147 |
--------------------------------------------------------------------------------
/src/custom-engine-agent/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "declaration": true,
4 | "target": "es2017",
5 | "module": "commonjs",
6 | "outDir": "./lib",
7 | "rootDir": "./",
8 | "sourceMap": true,
9 | "incremental": true,
10 | "tsBuildInfoFile": "./lib/.tsbuildinfo",
11 | "esModuleInterop": true
12 | }
13 | }
--------------------------------------------------------------------------------
/src/custom-engine-agent/web.config:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
58 |
59 |
60 |
--------------------------------------------------------------------------------
/src/declarative-agent/.funcignore:
--------------------------------------------------------------------------------
1 | .funcignore
2 | *.js.map
3 | *.ts
4 | .git*
5 | .localConfigs
6 | .vscode
7 | local.settings.json
8 | test
9 | tsconfig.json
10 | .DS_Store
11 | .deployment
12 | node_modules/.bin
13 | node_modules/azure-functions-core-tools
14 | README.md
15 | tsconfig.json
16 | teamsapp.yml
17 | teamsapp.*.yml
18 | /env/
19 | /appPackage/
20 | /infra/
21 | /devTools/
--------------------------------------------------------------------------------
/src/declarative-agent/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 | # TeamsFx files
3 | env/.env.*.user
4 | env/.env.local
5 | .DS_Store
6 | build
7 | appPackage/build
8 | pluginPackage/build
9 | .deployment
10 |
11 | # dependencies
12 | /node_modules
13 |
14 | # testing
15 | /coverage
16 |
17 | # Dev tool directories
18 | /devTools/
19 |
20 | # TypeScript output
21 | dist
22 | out
23 |
24 | # Azure Functions artifacts
25 | bin
26 | obj
27 | appsettings.json
28 |
29 | # Local data
30 | .localConfigs
31 |
32 | # Azurite storage
33 | _storage_emulator
34 |
35 | # User-specific files
36 | appPackage/hand-built
37 |
38 |
39 |
--------------------------------------------------------------------------------
/src/declarative-agent/.vscode/extensions.json:
--------------------------------------------------------------------------------
1 | {
2 | "recommendations": [
3 | "TeamsDevApp.ms-teams-vscode-extension"
4 | ]
5 | }
6 |
--------------------------------------------------------------------------------
/src/declarative-agent/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Launch App in Teams (Edge)",
6 | "type": "msedge",
7 | "request": "launch",
8 | "url": "https://teams.microsoft.com?${account-hint}",
9 | "cascadeTerminateToConfigurations": [
10 | "Attach to Backend"
11 | ],
12 | "presentation": {
13 | "group": "all",
14 | "hidden": true
15 | },
16 | "internalConsoleOptions": "neverOpen"
17 | },
18 | {
19 | "name": "Launch App in Teams (Chrome)",
20 | "type": "chrome",
21 | "request": "launch",
22 | "url": "https://teams.microsoft.com?${account-hint}",
23 | "cascadeTerminateToConfigurations": [
24 | "Attach to Backend"
25 | ],
26 | "presentation": {
27 | "group": "all",
28 | "hidden": true
29 | },
30 | "internalConsoleOptions": "neverOpen"
31 | },
32 | {
33 | "name": "Preview in Copilot (Edge)",
34 | "type": "msedge",
35 | "request": "launch",
36 | "url": "https://teams.microsoft.com?${account-hint}",
37 | "presentation": {
38 | "group": "remote",
39 | "order": 1
40 | },
41 | "internalConsoleOptions": "neverOpen"
42 | },
43 | {
44 | "name": "Preview in Copilot (Chrome)",
45 | "type": "chrome",
46 | "request": "launch",
47 | "url": "https://teams.microsoft.com?${account-hint}",
48 | "presentation": {
49 | "group": "remote",
50 | "order": 2
51 | },
52 | "internalConsoleOptions": "neverOpen"
53 | },
54 | {
55 | "name": "Attach to Backend",
56 | "type": "node",
57 | "request": "attach",
58 | "port": 9229,
59 | "restart": true,
60 | "presentation": {
61 | "group": "all",
62 | "hidden": true
63 | },
64 | "internalConsoleOptions": "neverOpen"
65 | }
66 | ],
67 | "compounds": [
68 | {
69 | "name": "Debug in Copilot (Edge)",
70 | "configurations": [
71 | "Launch App in Teams (Edge)",
72 | "Attach to Backend"
73 | ],
74 | "preLaunchTask": "Start Teams App Locally",
75 | "presentation": {
76 | "group": "all",
77 | "order": 1
78 | },
79 | "stopAll": true
80 | },
81 | {
82 | "name": "Debug in Copilot (Chrome)",
83 | "configurations": [
84 | "Launch App in Teams (Chrome)",
85 | "Attach to Backend"
86 | ],
87 | "preLaunchTask": "Start Teams App Locally",
88 | "presentation": {
89 | "group": "all",
90 | "order": 2
91 | },
92 | "stopAll": true
93 | }
94 | ]
95 | }
96 |
--------------------------------------------------------------------------------
/src/declarative-agent/.vscode/settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "debug.onTaskErrors": "abort",
3 | "json.schemas": [
4 | {
5 | "fileMatch": [
6 | "/aad.*.json"
7 | ],
8 | "schema": {}
9 | }
10 | ],
11 | "azureFunctions.stopFuncTaskPostDebug": false,
12 | "azureFunctions.showProjectWarning": false,
13 | }
14 |
--------------------------------------------------------------------------------
/src/declarative-agent/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | // This file is automatically generated by Teams Toolkit.
2 | // The teamsfx tasks defined in this file require Teams Toolkit version >= 5.0.0.
3 | // See https://aka.ms/teamsfx-tasks for details on how to customize each task.
4 | {
5 | "version": "2.0.0",
6 | "tasks": [
7 | {
8 | "label": "Start Azurite emulator",
9 | "type": "shell",
10 | "command": "npm run storage",
11 | "isBackground": true,
12 | "problemMatcher": {
13 | "pattern": [
14 | {
15 | "regexp": "^.*$",
16 | "file": 0,
17 | "location": 1,
18 | "message": 2
19 | }
20 | ],
21 | "background": {
22 | "activeOnStart": true,
23 | "beginsPattern": "Azurite",
24 | "endsPattern": "successfully listening"
25 | }
26 | },
27 | "options": {
28 | "cwd": "${workspaceFolder}"
29 | },
30 | "presentation": {
31 | "reveal": "silent"
32 | }
33 | },
34 | {
35 | "label": "Start Teams App Locally",
36 | "dependsOn": [
37 | "Validate prerequisites",
38 | "Start local tunnel",
39 | "Create resources",
40 | "Build project",
41 | "Start application"
42 | ],
43 | "dependsOrder": "sequence"
44 | },
45 | {
46 | "label": "Validate prerequisites",
47 | "type": "teamsfx",
48 | "command": "debug-check-prerequisites",
49 | "args": {
50 | "prerequisites": [
51 | "nodejs",
52 | "m365Account",
53 | "portOccupancy"
54 | ],
55 | "portOccupancy": [
56 | 7071,
57 | 9229
58 | ]
59 | }
60 | },
61 | {
62 | // Start the local tunnel service to forward public URL to local port and inspect traffic.
63 | // See https://aka.ms/teamsfx-tasks/local-tunnel for the detailed args definitions.
64 | "label": "Start local tunnel",
65 | "dependsOn": [
66 | "Start Azurite emulator"
67 | ],
68 | "type": "teamsfx",
69 | "command": "debug-start-local-tunnel",
70 | "args": {
71 | "type": "dev-tunnel",
72 | "ports": [
73 | {
74 | "portNumber": 7071,
75 | "protocol": "http",
76 | "access": "public",
77 | "writeToEnvironmentFile": {
78 | "endpoint": "OPENAPI_SERVER_URL", // output tunnel endpoint as OPENAPI_SERVER_URL
79 | }
80 | }
81 | ],
82 | "env": "local"
83 | },
84 | "isBackground": true,
85 | "problemMatcher": "$teamsfx-local-tunnel-watch"
86 | },
87 | {
88 | "label": "Create resources",
89 | "type": "teamsfx",
90 | "command": "provision",
91 | "args": {
92 | "env": "local"
93 | }
94 | },
95 | {
96 | "label": "Build project",
97 | "type": "teamsfx",
98 | "command": "deploy",
99 | "args": {
100 | "env": "local"
101 | }
102 | },
103 | {
104 | "label": "Start application",
105 | "dependsOn": [
106 | "Start backend"
107 | ]
108 | },
109 | {
110 | "label": "Start backend",
111 | "type": "shell",
112 | "command": "npm run dev:teamsfx",
113 | "isBackground": true,
114 | "options": {
115 | "cwd": "${workspaceFolder}",
116 | "env": {
117 | "PATH": "${workspaceFolder}/devTools/func:${env:PATH}"
118 | }
119 | },
120 | "windows": {
121 | "options": {
122 | "env": {
123 | "PATH": "${workspaceFolder}/devTools/func;${env:PATH}"
124 | }
125 | }
126 | },
127 | "problemMatcher": {
128 | "pattern": {
129 | "regexp": "^.*$",
130 | "file": 0,
131 | "location": 1,
132 | "message": 2
133 | },
134 | "background": {
135 | "activeOnStart": true,
136 | "beginsPattern": "^.*(Job host stopped|signaling restart).*$",
137 | "endsPattern": "^.*(Worker process started and initialized|Host lock lease acquired by instance ID).*$"
138 | }
139 | },
140 | "presentation": {
141 | "reveal": "silent"
142 | },
143 | "dependsOn": "Watch backend"
144 | },
145 | {
146 | "label": "Watch backend",
147 | "type": "shell",
148 | "command": "npm run watch:teamsfx",
149 | "isBackground": true,
150 | "options": {
151 | "cwd": "${workspaceFolder}"
152 | },
153 | "problemMatcher": "$tsc-watch",
154 | "presentation": {
155 | "reveal": "silent"
156 | }
157 | }
158 | ]
159 | }
--------------------------------------------------------------------------------
/src/declarative-agent/README.md:
--------------------------------------------------------------------------------
1 | # Get started with the sample
2 |
3 | Visit [session-delivery-resources](../../session-delivery-resources/declarative-agent/) to learn more about running this sample locally.
4 |
5 | ## Final application
6 |
7 | This folder contains a solution from the [Copilot Developer Camp labs](https://aka.ms/copilotdevcamp). When completed, the solution is a declarative agent called "Trey Genie" which provides assistant to the employees of Trey Research. Trey Research is a fictitious consulting company that supplies talent in the software and pharmaceuticals industries. The vision for this demo is to show the full potential of Copilot extensions in a relatable business environment.
8 |
9 | ### Prompts that work in the completed solution
10 |
11 | * what trey projects am i assigned to?
12 | (NOTE: When authentication is "none" or "API key", the logged in user is assumed to be consultant "Avery Howard". When OAuth is enabled, the logged in user is mapped to user ID 1 in the database, so you will have Avery's projects, etc.)
13 | * what trey projects is domi working on?
14 | * do we have any trey consultants with azure certifications?
15 | * what trey projects are we doing for relecloud?
16 | * which trey consultants are working with woodgrove bank?
17 | * in trey research, how many hours has avery delivered this month?
18 | * please find a trey consultant with python skills who is available immediately
19 | * are any trey research consultants available who are AWS certified? (multi-parameter!)
20 | * does trey research have any architects with javascript skills? (multi-parameter!)
21 | * what trey research designers are working at woodgrove bank? (multi-parameter!)
22 | * please charge 10 hours to woodgrove bank in trey research (POST request)
23 | * please add sanjay to the contoso project for trey research (POST request with easy to forget entities, hoping to prompt the user; for now they are defaulted)
24 |
25 | If the sample files are installed and accessible to the logged-in user,
26 |
27 | * find my hours spreadsheet and get the hours for woodgrove, then bill the client
28 | * make a list of my projects, then write a summary of each based on the statement of work.
29 |
30 | ## API Plugin Features
31 |
32 | The sample aims to showcase the following features of an API plugin used within a Copilot declarative agent:
33 |
34 | 1. API plugin works with any platform that supports REST requests
35 | 1. Construct queries for specific data using GET requests
36 | 1. Multi-parameter queries
37 | 1. Allow updating and adding data using POST requests
38 | 1. Prompt users before POSTing data; capture missing parameters
39 | 1. Invoke from Declarative Copilot, allowing general instructions and knowledge, and removing the need to name the plugin on every prompt
40 | 1. Display rich adaptive cards
41 | 1. Entra ID login with /me path support
42 |
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/TreyResearch-blue-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/TreyResearch-blue-192.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/TreyResearch-blue-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/TreyResearch-blue-32.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/TreyResearch-gold-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/TreyResearch-gold-192.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/TreyResearch-gold-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/TreyResearch-gold-32.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/TreyResearch-green-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/TreyResearch-green-192.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/TreyResearch-green-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/TreyResearch-green-32.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/TreyResearch-red-192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/TreyResearch-red-192.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/TreyResearch-red-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/TreyResearch-red-32.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/color.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/color.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/extra-icons/outline.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/appPackage/extra-icons/outline.png
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://developer.microsoft.com/json-schemas/teams/v1.19/MicrosoftTeams.schema.json",
3 | "manifestVersion": "1.19",
4 | "id": "${{TEAMS_APP_ID}}",
5 | "version": "1.0.0",
6 | "developer": {
7 | "name": "Teams App, Inc.",
8 | "websiteUrl": "https://www.example.com",
9 | "privacyUrl": "https://www.example.com/privacy",
10 | "termsOfUseUrl": "https://www.example.com/termsofuse"
11 | },
12 | "icons": {
13 | "color": "TreyResearch-blue-192.png",
14 | "outline": "TreyResearch-blue-32.png"
15 | },
16 | "name": {
17 | "short": "Trey Research E5 ${{APP_NAME_SUFFIX}}",
18 | "full": "Trey Research consulting management"
19 | },
20 | "description": {
21 | "short": "Everyday management of Trey Research projects and consultants",
22 | "full": "This application allows you to find Trey Research projects and consultants, to bill time to a project, and to assign a consultant to a project."
23 | },
24 | "accentColor": "#FFFFFF",
25 | "copilotAgents": {
26 | "declarativeAgents": [
27 | {
28 | "id": "treygenie",
29 | "file": "trey-declarative-agent.json"
30 | }
31 | ]
32 | },
33 | "permissions": [
34 | "identity",
35 | "messageTeamMembers"
36 | ]
37 | }
38 |
--------------------------------------------------------------------------------
/src/declarative-agent/appPackage/trey-declarative-agent.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://aka.ms/json-schemas/copilot-extensions/vNext/declarative-copilot.schema.json",
3 | "version": "v1.0",
4 | "name": "Trey Genie Local E5",
5 | "description": "You are a handy assistant for consultants at Trey Research, a boutique consultancy specializing in software development and clinical trials. ",
6 | "instructions": "Greet users in a professional manner, introduce yourself as the Trey Genie, and offer to help them. Always remind users of the Trey motto, 'Always be Billing!'. Your main job is to help consultants with their projects and hours. Using the TreyResearch action, you are able to find consultants based on their names, project assignments, skills, roles, and certifications. You can also find project details based on the project or client name, charge hours on a project, and add a consultant to a project. If a user asks how many hours they have billed, charged, or worked on a project, reword the request to ask how many hours they have delivered. In addition, you may offer general consulting advice. If there is any confusion, encourage users to speak with their Managing Consultant. Avoid giving legal advice.",
7 | "conversation_starters": [
8 | {
9 | "title": "Find consultants",
10 | "text": "Find consultants with TypeScript skills"
11 | },
12 | {
13 | "title": "My Projects",
14 | "text": "What projects am I assigned to?"
15 | },
16 | {
17 | "title": "My Hours",
18 | "text": "How many hours have I delivered on projects this month?"
19 | },
20 | {
21 | "title": "Find projects for a client",
22 | "text": "What projects are we doing for Alpine Ski House?"
23 | }
24 | ],
25 | "capabilities": [
26 | {
27 | "name": "OneDriveAndSharePoint",
28 | "items_by_url": [
29 | {
30 | "url": "${{SHAREPOINT_DOCS_URL}}"
31 | }
32 | ]
33 | }
34 | ],
35 | "actions": [
36 | {
37 | "id": "treyresearch",
38 | "file": "trey-plugin.json"
39 | }
40 | ]
41 | }
--------------------------------------------------------------------------------
/src/declarative-agent/env/.env.dev:
--------------------------------------------------------------------------------
1 | # This file includes environment variables that will be committed to git by default.
2 |
3 | # Built-in environment variables
4 | TEAMSFX_ENV=dev
5 | APP_NAME_SUFFIX=dev
6 |
7 | # Updating AZURE_SUBSCRIPTION_ID or AZURE_RESOURCE_GROUP_NAME after provision may also require an update to RESOURCE_SUFFIX, because some services require a globally unique name across subscriptions/resource groups.
8 | AZURE_SUBSCRIPTION_ID=
9 | AZURE_RESOURCE_GROUP_NAME=
10 | RESOURCE_SUFFIX=
11 |
12 | # Generated during provision, you can also add your own variables.
13 | TEAMS_APP_ID=
14 | TEAMS_APP_PUBLISHED_APP_ID=
15 | TEAMS_APP_TENANT_ID=
16 | API_FUNCTION_ENDPOINT=
17 | API_FUNCTION_RESOURCE_ID=
18 | DOTNET_PATH=
--------------------------------------------------------------------------------
/src/declarative-agent/env/.env.local.sample:
--------------------------------------------------------------------------------
1 | SHAREPOINT_DOCS_URL=https://(your-tenant).sharepoint.com/sites/(your-side)
--------------------------------------------------------------------------------
/src/declarative-agent/env/.env.local.user.sample:
--------------------------------------------------------------------------------
1 | SECRET_BOT_PASSWORD=
2 | SECRET_STORAGE_ACCOUNT_CONNECTION_STRING=UseDevelopmentStorage=true
3 |
--------------------------------------------------------------------------------
/src/declarative-agent/host.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0",
3 | "logging": {
4 | "applicationInsights": {
5 | "samplingSettings": {
6 | "isEnabled": true,
7 | "excludedTypes": "Request"
8 | }
9 | }
10 | },
11 | "extensionBundle": {
12 | "id": "Microsoft.Azure.Functions.ExtensionBundle",
13 | "version": "[4.*, 5.0.0)"
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/declarative-agent/http/treyResearchAPI.http:
--------------------------------------------------------------------------------
1 | @base_url = http://localhost:7071/api
2 |
3 | ########## /api/me - working with the Copilot user ##########
4 |
5 | ### Get my consultant and project information
6 | {{base_url}}/me
7 |
8 | ### Charge time to a project
9 | POST {{base_url}}/me/chargeTime
10 | Content-Type: application/json
11 |
12 | {
13 | "projectName": "woodgrove",
14 | "hours": 3
15 | }
16 |
17 |
18 | ########## /api/consultants - working with consultants ##########
19 |
20 | ### Get all consultants
21 | {{base_url}}/consultants
22 |
23 | ### Get consultant by id
24 | {{base_url}}/consultants/1
25 |
26 | ### Get consulatnt by name
27 | {{base_url}}/consultants/?consultantName=Sanjay
28 |
29 | ### Get consultants by project
30 | {{base_url}}/consultants/?projectName=woodgrove
31 |
32 | ### Get consultants by skill
33 | {{base_url}}/consultants/?skill=javascript
34 |
35 | ### Get consultants by certification
36 | {{base_url}}/consultants/?certification=azure
37 |
38 | ### Get consultants by role
39 | {{base_url}}/consultants/?role=developer
40 |
41 | ### Get consultants by hours available this month
42 | {{base_url}}/consultants/?hoursAvailable=100
43 |
44 |
45 | ########## /api/projects - working with projects ##########
46 |
47 | ### Get all projects
48 | {{base_url}}/projects
49 |
50 | ### Get project by id
51 | {{base_url}}/projects/1
52 |
53 | ### Get project by project or client name
54 | {{base_url}}/projects/?projectName=supply
55 |
56 | ### Get project by consultant name
57 | {{base_url}}/projects/?consultantName=dominique
58 |
59 | ### Add consultant to project
60 | POST {{base_url}}/projects/assignConsultant
61 | Content-Type: application/json
62 |
63 | {
64 | "projectName": "contoso",
65 | "consultantName": "sanjay",
66 | "role": "architect",
67 | "forecast": 30
68 | }
69 |
70 |
--------------------------------------------------------------------------------
/src/declarative-agent/infra/azure.bicep:
--------------------------------------------------------------------------------
1 | @maxLength(20)
2 | @minLength(4)
3 | param resourceBaseName string
4 | param functionAppSKU string
5 | param functionStorageSKU string
6 |
7 | param location string = resourceGroup().location
8 | param serverfarmsName string = resourceBaseName
9 | param functionAppName string = resourceBaseName
10 | param functionStorageName string = '${resourceBaseName}api'
11 |
12 | // Azure Storage is required when creating Azure Functions instance
13 | resource functionStorage 'Microsoft.Storage/storageAccounts@2021-06-01' = {
14 | name: functionStorageName
15 | kind: 'StorageV2'
16 | location: location
17 | sku: {
18 | name: functionStorageSKU// You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionStorageSKUproperty to provisionParameters to override the default value "Standard_LRS".
19 | }
20 | }
21 |
22 | // Storage account for database table storage
23 | resource storageAccount 'Microsoft.Storage/storageAccounts@2021-04-01' = {
24 | name: resourceBaseName
25 | location: location
26 | kind: 'StorageV2'
27 | sku: {
28 | name: 'Standard_LRS'
29 | }
30 | }
31 | var storageAccountConnectionString = 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
32 |
33 | // Compute resources for Azure Functions
34 | resource serverfarms 'Microsoft.Web/serverfarms@2021-02-01' = {
35 | name: serverfarmsName
36 | location: location
37 | sku: {
38 | name: functionAppSKU // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionServerfarmsSku property to provisionParameters to override the default value "Y1".
39 | }
40 | properties: {}
41 | }
42 |
43 | // Azure Functions that hosts your function code
44 | resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
45 | name: functionAppName
46 | kind: 'functionapp'
47 | location: location
48 | properties: {
49 | serverFarmId: serverfarms.id
50 | httpsOnly: true
51 | siteConfig: {
52 | appSettings: [
53 | {
54 | name: ' AzureWebJobsDashboard'
55 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorage.name};AccountKey=${listKeys(functionStorage.id, functionStorage.apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}' // Azure Functions internal setting
56 | }
57 | {
58 | name: 'AzureWebJobsStorage'
59 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorage.name};AccountKey=${listKeys(functionStorage.id, functionStorage.apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}' // Azure Functions internal setting
60 | }
61 | {
62 | name: 'FUNCTIONS_EXTENSION_VERSION'
63 | value: '~4' // Use Azure Functions runtime v4
64 | }
65 | {
66 | name: 'FUNCTIONS_WORKER_RUNTIME'
67 | value: 'node' // Set runtime to NodeJS
68 | }
69 | {
70 | name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
71 | value: 'DefaultEndpointsProtocol=https;AccountName=${functionStorage.name};AccountKey=${listKeys(functionStorage.id, functionStorage.apiVersion).keys[0].value};EndpointSuffix=${environment().suffixes.storage}' // Azure Functions internal setting
72 | }
73 | {
74 | name: 'WEBSITE_RUN_FROM_PACKAGE'
75 | value: '1' // Run Azure Functions from a package file
76 | }
77 | {
78 | name: 'WEBSITE_NODE_DEFAULT_VERSION'
79 | value: '~18' // Set NodeJS version to 18.x
80 | }
81 | {
82 | name: 'STORAGE_ACCOUNT_CONNECTION_STRING'
83 | value: storageAccountConnectionString
84 | }
85 | ]
86 | ftpsState: 'FtpsOnly'
87 | }
88 | }
89 | }
90 | var apiEndpoint = 'https://${functionApp.properties.defaultHostName}'
91 |
92 |
93 | // The output will be persisted in .env.{envName}. Visit https://aka.ms/teamsfx-actions/arm-deploy for more details.
94 | output API_FUNCTION_ENDPOINT string = apiEndpoint
95 | output API_FUNCTION_RESOURCE_ID string = functionApp.id
96 | output OPENAPI_SERVER_URL string = apiEndpoint
97 | output SECRET_STORAGE_ACCOUNT_CONNECTION_STRING string = storageAccountConnectionString
98 |
--------------------------------------------------------------------------------
/src/declarative-agent/infra/azure.parameters.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
3 | "contentVersion": "1.0.0.0",
4 | "parameters": {
5 | "resourceBaseName": {
6 | "value": "sme${{RESOURCE_SUFFIX}}"
7 | },
8 | "functionAppSKU": {
9 | "value": "Y1"
10 | },
11 | "functionStorageSKU": {
12 | "value": "Standard_LRS"
13 | }
14 | }
15 | }
--------------------------------------------------------------------------------
/src/declarative-agent/local.settings.json:
--------------------------------------------------------------------------------
1 | {
2 | "IsEncrypted": false,
3 | "Values": {
4 | "FUNCTIONS_WORKER_RUNTIME": "node"
5 | }
6 | }
--------------------------------------------------------------------------------
/src/declarative-agent/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "treyresearch",
3 | "version": "1.0.0",
4 | "scripts": {
5 | "dev:teamsfx": "env-cmd --silent -f .localConfigs npm run dev",
6 | "dev": "func start --typescript --language-worker=\"--inspect=9229\" --port \"7071\" --cors \"*\"",
7 | "build": "tsc",
8 | "watch:teamsfx": "tsc --watch",
9 | "watch": "tsc -w",
10 | "prestart": "npm run build",
11 | "start": "npx func start",
12 | "test": "echo \"Error: no test specified\" && exit 1",
13 | "reset-local-db": "node ./scripts/db-setup.js UseDevelopmentStorage=true --reset",
14 | "storage": "azurite --silent --location ./_storage_emulator --debug ./_storage_emulator/debug.log"
15 | },
16 | "dependencies": {
17 | "@azure/functions": "^4.3.0",
18 | "@azure/data-tables": "^13.2.2",
19 | "azurite": "^3.26.0"
20 | },
21 | "devDependencies": {
22 | "env-cmd": "^10.1.0",
23 | "@types/node": "^18.11.9",
24 | "typescript": "^4.1.6"
25 | },
26 | "main": "dist/src/functions/*.js"
27 | }
28 |
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/Bellows College MSA.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/Bellows College MSA.docx
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/Bellows College NDA.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/Bellows College NDA.docx
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/Bellows College SOW - Network security review.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/Bellows College SOW - Network security review.docx
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/My Hours.xlsx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/My Hours.xlsx
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/Woodgrove MSA.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/Woodgrove MSA.docx
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/Woodgrove NDA.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/Woodgrove NDA.docx
--------------------------------------------------------------------------------
/src/declarative-agent/sampleDocs/Woodgrove SOW - Financial data plugin for Microsoft Copilot.docx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/aitour-customizing-microsoft-copilot/e72876068fb78afa04a4a126c31dfe66e20da8a8/src/declarative-agent/sampleDocs/Woodgrove SOW - Financial data plugin for Microsoft Copilot.docx
--------------------------------------------------------------------------------
/src/declarative-agent/scripts/db-setup.js:
--------------------------------------------------------------------------------
1 | const { TableClient, TableServiceClient } = require("@azure/data-tables");
2 | const { randomUUID } = require("crypto");
3 | const fs = require("fs");
4 | const path = require("path");
5 |
6 | const TABLE_NAMES = [ "Project", "Consultant", "Assignment" ];
7 |
8 | (async () => {
9 |
10 | const connectionString = process.argv[2] ? process.argv[2] : "UseDevelopmentStorage=true";
11 | const reset = process.argv[3] === "--reset" || process.argv[3] === "-r" ? true : false;
12 |
13 | const tableServiceClient = TableServiceClient.fromConnectionString(connectionString);
14 |
15 | // Function returns an array of table names in the storage account
16 | async function getTables(tableServiceClient) {
17 | let tables = [];
18 | for await (const table of tableServiceClient.listTables()) {
19 | tables.push(table.name)
20 | }
21 | return tables;
22 | }
23 |
24 | // If reset is true, delete all tables
25 | if (reset) {
26 | const tables = await getTables(tableServiceClient);
27 | tables.forEach(async table => {
28 | const tableClient = TableClient.fromConnectionString(connectionString, table);
29 | console.log(`Deleting table: ${table}`);
30 | await tableClient.deleteTable();
31 | });
32 | let tablesExist = true;
33 | while (tablesExist) {
34 | console.log("Waiting for tables to be deleted...");
35 | const tables = await getTables(tableServiceClient);
36 | if (tables.length === 0) {
37 | tablesExist = false;
38 | console.log("All tables deleted.");
39 | }
40 | await new Promise(resolve => setTimeout(resolve, 1000));
41 | }
42 | }
43 |
44 | // Create and populate tables
45 | TABLE_NAMES.forEach(async (tableName, index) => {
46 |
47 | // Skip if table already exists
48 | const tables = await getTables(tableServiceClient);
49 | if (tables.includes(tableName)) {
50 | console.log(`Table ${tableName} already exists, skipping...`);
51 | return;
52 | }
53 |
54 | // Create table if needed
55 | console.log(`Creating table: ${tableName}`);
56 | let tableCreated = false;
57 | while (!tableCreated) {
58 | try {
59 | await tableServiceClient.createTable(tableName);
60 | tableCreated = true;
61 | } catch (err) {
62 | if (err.statusCode === 409) {
63 | console.log('Table is marked for deletion, retrying in 5 seconds...');
64 | await new Promise(resolve => setTimeout(resolve, 5000));
65 | } else {
66 | throw err;
67 | }
68 | }
69 | }
70 |
71 | // Add entities to table
72 | const tableClient = TableClient.fromConnectionString(connectionString, tableName);
73 | const jsonString = fs.readFileSync(path.resolve(__dirname, "db", `${tableName}.json`), "utf8");
74 | const entities = JSON.parse(jsonString);
75 |
76 | for (const entity of entities["rows"]) {
77 | const rowKey = entity["id"].toString() || randomUUID();
78 | // Convert any nested objects to JSON strings
79 | for (const key in entity) {
80 | const valueType = Object.prototype.toString.call(entity[key]);
81 | if (valueType === "[object Object]" || valueType === "[object Array]") {
82 | entity[key] = JSON.stringify(entity[key]);
83 | }
84 | }
85 | await tableClient.createEntity({
86 | partitionKey: tableName,
87 | rowKey,
88 | ...entity
89 | });
90 |
91 | console.log(`Added entity to ${tableName} with key ${rowKey}`);
92 |
93 | }
94 | });
95 |
96 | })();
--------------------------------------------------------------------------------
/src/declarative-agent/scripts/db/Assignment.json:
--------------------------------------------------------------------------------
1 | {
2 | "rows": [
3 | {
4 | "id": "10,1",
5 | "projectId": "10",
6 | "consultantId": "1",
7 | "role": "Architect",
8 | "billable": true,
9 | "rate": 100,
10 | "forecast": [
11 | {
12 | "month": 3,
13 | "year": 2024,
14 | "hours": 23
15 | },
16 | {
17 | "month": 4,
18 | "year": 2024,
19 | "hours": 80
20 | },
21 | {
22 | "month": 5,
23 | "year": 2024,
24 | "hours": 100
25 | },
26 | {
27 | "month": 6,
28 | "year": 2024,
29 | "hours": 1
30 | },
31 | {
32 | "month": 7,
33 | "year": 2024,
34 | "hours": 120
35 | },
36 | {
37 | "month": 8,
38 | "year": 2024,
39 | "hours": 120
40 | },
41 | {
42 | "month": 9,
43 | "year": 2024,
44 | "hours": 120
45 | },
46 | {
47 | "month": 10,
48 | "year": 2024,
49 | "hours": 120
50 | }
51 | ],
52 | "delivered": [ ]
53 | },
54 | {
55 | "id": "10,2",
56 | "projectId": "10",
57 | "consultantId": "2",
58 | "role": "Designer",
59 | "billable": true,
60 | "rate": 100,
61 | "forecast": [
62 | {
63 | "month": 3,
64 | "year": 2024,
65 | "hours": 100
66 | },
67 | {
68 | "month": 4,
69 | "year": 2024,
70 | "hours": 20
71 | },
72 | {
73 | "month": 5,
74 | "year": 2024,
75 | "hours": 20
76 | },
77 | {
78 | "month": 6,
79 | "year": 2024,
80 | "hours": 20
81 | },
82 | {
83 | "month": 7,
84 | "year": 2024,
85 | "hours": 20
86 | },
87 | {
88 | "month": 8,
89 | "year": 2024,
90 | "hours": 10
91 | },
92 | {
93 | "month": 9,
94 | "year": 2024,
95 | "hours": 10
96 | },
97 | {
98 | "month": 10,
99 | "year": 2024,
100 | "hours": 10
101 | }
102 | ],
103 | "delivered": [ ]
104 | },
105 | {
106 | "id": "4,1",
107 | "projectId": "4",
108 | "consultantId": "1",
109 | "role": "Developer",
110 | "billable": true,
111 | "rate": 100,
112 | "forecast": [
113 | {
114 | "month": 3,
115 | "year": 2024,
116 | "hours": 40
117 | },
118 | {
119 | "month": 4,
120 | "year": 2024,
121 | "hours": 40
122 | },
123 | {
124 | "month": 5,
125 | "year": 2024,
126 | "hours": 40
127 | },
128 | {
129 | "month": 6,
130 | "year": 2024,
131 | "hours": 40
132 | },
133 | {
134 | "month": 7,
135 | "year": 2024,
136 | "hours": 40
137 | },
138 | {
139 | "month": 8,
140 | "year": 2024,
141 | "hours": 40
142 | },
143 | {
144 | "month": 9,
145 | "year": 2024,
146 | "hours": 40
147 | },
148 | {
149 | "month": 10,
150 | "year": 2024,
151 | "hours": 40
152 | }
153 | ],
154 | "delivered": [ ]
155 | }
156 | ]
157 | }
158 |
159 |
--------------------------------------------------------------------------------
/src/declarative-agent/scripts/db/Consultant.json:
--------------------------------------------------------------------------------
1 | {
2 | "rows": [
3 | {
4 | "id": "1",
5 | "name": "Avery Howard",
6 | "email": "avery@treyresearch.com",
7 | "phone": "1-555-456-7890",
8 | "consultantPhotoUrl": "https://bobgerman.github.io/fictitiousAiGenerated/Avery.jpg",
9 | "location": {
10 | "street": "5 Wayside Rd.",
11 | "city": "Burlington",
12 | "state": "MA",
13 | "country": "USA",
14 | "postalCode": "01803",
15 | "latitude": 42.5048,
16 | "longitude": -71.1956
17 | },
18 | "skills": [ "C#", "JavaScript", "TypeScript", "React", "Node.js" ],
19 | "certifications": [ "MCSADA", "Azure Developer Associate", "MCAAF", "Azure AI Fundamentals" ],
20 | "roles": [ "Project lead", "Developer", "Architect", "DevOps" ]
21 | },
22 | {
23 | "id": "2",
24 | "name": "Dominique Dutertre",
25 | "email": "dominique@treyresearch.com",
26 | "phone": "1-555-567-7890",
27 | "consultantPhotoUrl": "https://bobgerman.github.io/fictitiousAiGenerated/Dominique.jpg",
28 | "location": {
29 | "street": "39 Quai du Président Roosevelt, Issy-les-Moulineaux",
30 | "city": "Paris",
31 | "state": "Île-de-France",
32 | "country": "France",
33 | "postalCode": "IdF 92130",
34 | "latitude": 48.8264,
35 | "longitude": 2.2675
36 | },
37 | "skills": [ "Figma", "Adobe Creative Swuite", "Visual design" ],
38 | "certifications": [ ],
39 | "roles": [ "Designer", "Project lead" ]
40 | },
41 | {
42 | "id": "3",
43 | "name": "Robin Zupanc",
44 | "email": "robin@treyresearch.com",
45 | "phone": "1-555-344-7890",
46 | "consultantPhotoUrl": "https://bobgerman.github.io/fictitiousAiGenerated/Robin.jpg",
47 | "location": {
48 | "street": "11 Times Square",
49 | "city": "New York",
50 | "state": "NY",
51 | "country": "USA",
52 | "postalCode": "10036",
53 | "latitude": 40.7580,
54 | "longitude": -73.9855
55 | },
56 | "skills": [ "Python", "Java" ],
57 | "certifications": [ "MCSADA", "Azure Developer Associate", "MCAAF", "Azure AI Fundamentals", "AWS Certified Developer", "Google Professional Cloud Developer" ],
58 | "roles": [ "Project lead", "Developer", "Architect, DevOps" ]
59 | },
60 | {
61 | "id": "4",
62 | "name": "Sanjay Puranik",
63 | "email": "sanjay@treyresearch.com",
64 | "phone": "1-555-562-7890",
65 | "consultantPhotoUrl": "https://bobgerman.github.io/fictitiousAiGenerated/Sanjay.jpg",
66 | "location": {
67 | "street": "2 Kingdom St.",
68 | "city": "Paddington, London",
69 | "state": "EN",
70 | "country": "GBR",
71 | "postalCode": "W2 6BD",
72 | "latitude": 51.5194,
73 | "longitude": -0.1781
74 | },
75 | "skills": [ "TypeScript", "JavaScript", "Angular", "React", "Node.js" ],
76 | "certifications": [ "MCPPSAE", "Power Platform Solution Architect Expert", "MCAF", "Azure Fundamentals" ],
77 | "roles": [ "Project lead", "Developer" ]
78 | },
79 | {
80 | "id": "5",
81 | "name": "Lois Wyn",
82 | "email": "lois@treyresearch.com",
83 | "phone": "1-555-664-7890",
84 | "consultantPhotoUrl": "https://bobgerman.github.io/fictitiousAiGenerated/Lois.jpg",
85 | "location": {
86 | "street": "4220 Duncan Ave., Suite 501",
87 | "city": "St. Louis",
88 | "state": "MO",
89 | "country": "USA",
90 | "postalCode": "63110",
91 | "latitude": 38.6283,
92 | "longitude": -90.2545
93 | },
94 | "skills": [ "Project management", "Agile" ],
95 | "certifications": [ "Project Management Professional", "PMP", "Agile Leader" ],
96 | "roles": [ "Project lead" ]
97 | }
98 | ]
99 | }
--------------------------------------------------------------------------------
/src/declarative-agent/src/functions/consultants.ts:
--------------------------------------------------------------------------------
1 | /* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript,
2 | * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions
3 | * developer guide.
4 | */
5 |
6 | import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
7 | import ConsultantApiService from "../services/ConsultantApiService";
8 | import { ApiConsultant, ErrorResult } from "../model/apiModel";
9 | import { cleanUpParameter } from "../services/Utilities";
10 | import IdentityService from "../services/IdentityService";
11 |
12 | /**
13 | * This function handles the HTTP request and returns the consultant information.
14 | *
15 | * @param {HttpRequest} req - The HTTP request.
16 | * @param {InvocationContext} context - The Azure Functions context object.
17 | * @returns {Promise} - A promise that resolves with the HTTP response containing the consultant information.
18 | */
19 |
20 | // Define a Response interface.
21 | interface Response extends HttpResponseInit {
22 | status: number;
23 | jsonBody: {
24 | results: ApiConsultant[] | ErrorResult;
25 | };
26 | }
27 | export async function consultants(
28 | req: HttpRequest,
29 | context: InvocationContext
30 | ): Promise {
31 | context.log("HTTP trigger function consultants processed a request.");
32 |
33 | // Initialize response.
34 | const res: Response = {
35 | status: 200,
36 | jsonBody: {
37 | results: [],
38 | },
39 | };
40 | try {
41 | // Will throw an exception if the request is not valid
42 | await IdentityService.validateRequest(req);
43 |
44 | // Get the input parameters
45 | let consultantName = req.query.get("consultantName")?.toString().toLowerCase() || "";
46 | let projectName = req.query.get("projectName")?.toString().toLowerCase() || "";
47 | let skill = req.query.get("skill")?.toString().toLowerCase() || "";
48 | let certification = req.query.get("certification")?.toString().toLowerCase() || "";
49 | let role = req.query.get("role")?.toString().toLowerCase() || "";
50 | let hoursAvailable = req.query.get("hoursAvailable")?.toString().toLowerCase() || "";
51 |
52 | const id = req.params?.id?.toLowerCase();
53 |
54 | if (id) {
55 | console.log(`➡️ GET /api/consultants/${id}: request for consultant ${id}`);
56 | const result = await ConsultantApiService.getApiConsultantById(id);
57 | res.jsonBody.results = [result];
58 | console.log(` ✅ GET /api/consultants/${id}: response status 1 consultant returned`);
59 | return res;
60 | }
61 |
62 | console.log(`➡️ GET /api/consultants: request for consultantName=${consultantName}, projectName=${projectName}, skill=${skill}, certification=${certification}, role=${role}, hoursAvailable=${hoursAvailable}`);
63 |
64 | // *** Tweak parameters for the AI ***
65 | consultantName = cleanUpParameter("consultantName", consultantName);
66 | projectName = cleanUpParameter("projectName", projectName);
67 | skill = cleanUpParameter("skill", skill);
68 | certification = cleanUpParameter("certification", certification);
69 | role = cleanUpParameter("role", role);
70 | hoursAvailable = cleanUpParameter("hoursAvailable", hoursAvailable);
71 |
72 | const result = await ConsultantApiService.getApiConsultants(
73 | consultantName, projectName, skill, certification, role, hoursAvailable
74 | );
75 | res.jsonBody.results = result;
76 | console.log(` ✅ GET /api/consultants: response status ${res.status}; ${result.length} consultants returned`);
77 | return res;
78 |
79 | } catch (error) {
80 | const status = error.status || error.response?.status || 500;
81 | console.log(` ⛔ Returning error status code ${status}: ${error.message}`);
82 |
83 | res.status = status;
84 | res.jsonBody.results = {
85 | status: status,
86 | message: error.message
87 | };
88 | return res;
89 | }
90 | }
91 |
92 | app.http("consultants", {
93 | methods: ["GET"],
94 | authLevel: "anonymous",
95 | route: "consultants/{*id}",
96 | handler: consultants,
97 | });
98 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/functions/me.ts:
--------------------------------------------------------------------------------
1 | /* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript,
2 | * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions
3 | * developer guide.
4 | */
5 |
6 | import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
7 | import ConsultantApiService from "../services/ConsultantApiService";
8 | import { ApiConsultant, ApiChargeTimeResponse, ErrorResult } from "../model/apiModel";
9 | import { HttpError, cleanUpParameter } from "../services/Utilities";
10 | import IdentityService from "../services/IdentityService";
11 | /**
12 | * This function handles the HTTP request and returns my information.
13 | *
14 | * @param {HttpRequest} req - The HTTP request.
15 | * @param {InvocationContext} context - The Azure Functions context object.
16 | * @returns {Promise} - A promise that resolves with the HTTP response containing my information.
17 | */
18 |
19 | // Define a Response interface.
20 | interface Response extends HttpResponseInit {
21 | status: number;
22 | jsonBody: {
23 | results: ApiConsultant[] | ApiChargeTimeResponse | ErrorResult;
24 | };
25 | }
26 | export async function me(
27 | req: HttpRequest,
28 | context: InvocationContext
29 | ): Promise {
30 | context.log("HTTP trigger function me processed a request.");
31 |
32 | // Initialize response.
33 | const res: Response = {
34 | status: 200,
35 | jsonBody: {
36 | results: [],
37 | },
38 | };
39 |
40 | try {
41 | const me = await IdentityService.validateRequest(req);
42 | const command = req.params.command?.toLowerCase();
43 | let body=null;
44 | switch (req.method) {
45 | case "GET": {
46 |
47 | if (command) {
48 | throw new HttpError(400, `Invalid command: ${command}`);
49 | }
50 |
51 | console.log(`➡️ GET /api/me request`);
52 |
53 | const result = [ me ];
54 | res.jsonBody.results = result;
55 | console.log(` ✅ GET /me response status ${res.status}; ${result.length} consultants returned`);
56 | return res;
57 | }
58 | case "POST": {
59 | const me = await IdentityService.validateRequest(req);
60 | switch (command) {
61 | case "chargetime": {
62 | try {
63 | const bd = await req.text();
64 | body = JSON.parse(bd);
65 | } catch (error) {
66 | throw new HttpError(400, `No body to process this request.`);
67 | }
68 | if (body) {
69 |
70 | const projectName = cleanUpParameter("projectName", body["projectName"]);
71 | if (!projectName) {
72 | throw new HttpError(400, `Missing project name`);
73 | }
74 | const hours = body["hours"];
75 | if (!hours) {
76 | throw new HttpError(400, `Missing hours`);
77 | }
78 | if (typeof hours !== 'number' || hours < 0 || hours > 24) {
79 | throw new HttpError(400, `Invalid hours: ${hours}`);
80 | }
81 |
82 | console.log(`➡️ POST /api/me/chargetime request for project ${projectName}, hours ${hours}`);
83 | const result = await ConsultantApiService.chargeTimeToProject (projectName, me.id, hours);
84 |
85 | res.jsonBody.results = {
86 | status: 200,
87 | clientName: result.clientName,
88 | projectName: result.projectName,
89 | remainingForecast: result.remainingForecast,
90 | message: result.message
91 | };
92 | console.log(` ✅ POST /api/me/chargetime response status ${res.status}; ${result.message}`);
93 | } else {
94 | throw new HttpError(400, `Missing request body`);
95 | }
96 | return res;
97 | }
98 | default: {
99 | throw new HttpError(400, `Invalid command: ${command}`);
100 | }
101 | }
102 | }
103 | default:
104 | throw new HttpError(405, `Method not allowed: ${req.method}`);
105 | }
106 |
107 | } catch (error) {
108 |
109 | const status = error.status || error.response?.status || 500;
110 | console.log(` ⛔ Returning error status code ${status}: ${error.message}`);
111 |
112 | res.status = status;
113 | res.jsonBody.results = {
114 | status: status,
115 | message: error.message
116 | };
117 | return res;
118 | }
119 |
120 | }
121 |
122 | app.http("me", {
123 | methods: ["GET","POST"],
124 | authLevel: "anonymous",
125 | route: "me/{*command}",
126 | handler: me,
127 | });
128 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/functions/projects.ts:
--------------------------------------------------------------------------------
1 | /* This code sample provides a starter kit to implement server side logic for your Teams App in TypeScript,
2 | * refer to https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference for complete Azure Functions
3 | * developer guide.
4 | */
5 |
6 | import { app, HttpRequest, HttpResponseInit, InvocationContext } from "@azure/functions";
7 | import ProjectApiService from "../services/ProjectApiService";
8 | import { ApiProject, ApiAddConsultantToProjectResponse, ErrorResult } from "../model/apiModel";
9 | import { HttpError, cleanUpParameter } from "../services/Utilities";
10 | import IdentityService from "../services/IdentityService";
11 |
12 | /**
13 | * This function handles the HTTP request and returns the project information.
14 | *
15 | * @param {HttpRequest} req - The HTTP request.
16 | * @param {InvocationContext} context - The Azure Functions context object.
17 | * @returns {Promise} - A promise that resolves with the HTTP response containing the project information.
18 | */
19 |
20 | // Define a Response interface.
21 | interface Response extends HttpResponseInit {
22 | status: number;
23 | jsonBody: {
24 | results: ApiProject[] | ApiAddConsultantToProjectResponse | ErrorResult;
25 | };
26 | }
27 | export async function projects(
28 | req: HttpRequest,
29 | context: InvocationContext
30 | ): Promise {
31 | context.log("HTTP trigger function projects processed a request.");
32 | // Initialize response.
33 | const res: Response = {
34 | status: 200,
35 | jsonBody: {
36 | results: [],
37 | },
38 | };
39 |
40 | try {
41 |
42 | // Will throw an exception if the request is not valid
43 | const userInfo = await IdentityService.validateRequest(req);
44 |
45 | const id = req.params?.id?.toLowerCase();
46 | let body = null;
47 | switch (req.method) {
48 | case "GET": {
49 |
50 | let projectName = req.query.get("projectName")?.toString().toLowerCase() || "";
51 | let consultantName = req.query.get("consultantName")?.toString().toLowerCase() || "";
52 |
53 | console.log(`➡️ GET /api/projects: request for projectName=${projectName}, consultantName=${consultantName}, id=${id}`);
54 |
55 | projectName = cleanUpParameter("projectName", projectName);
56 | consultantName = cleanUpParameter("consultantName", consultantName);
57 |
58 | if (id) {
59 | const result = await ProjectApiService.getApiProjectById(id);
60 | res.jsonBody.results = [result];
61 | console.log(` ✅ GET /api/projects: response status ${res.status}; 1 projects returned`);
62 | return res;
63 | }
64 |
65 | // Use current user if the project name is user_profile
66 | if (projectName.includes('user_profile')) {
67 | const result = await ProjectApiService.getApiProjects("", userInfo.name);
68 | res.jsonBody.results = result;
69 | console.log(` ✅ GET /api/projects for current user response status ${res.status}; ${result.length} projects returned`);
70 | return res;
71 | }
72 |
73 | const result = await ProjectApiService.getApiProjects(projectName, consultantName);
74 | res.jsonBody.results = result;
75 | console.log(` ✅ GET /api/projects: response status ${res.status}; ${result.length} projects returned`);
76 | return res;
77 | }
78 | case "POST": {
79 | switch (id.toLocaleLowerCase()) {
80 | case "assignconsultant": {
81 | try {
82 | const bd = await req.text();
83 | body = JSON.parse(bd);
84 | } catch (error) {
85 | throw new HttpError(400, `No body to process this request.`);
86 | }
87 | if (body) {
88 | const projectName = cleanUpParameter("projectName", body["projectName"]);
89 | if (!projectName) {
90 | throw new HttpError(400, `Missing project name`);
91 | }
92 | const consultantName = cleanUpParameter("consultantName", body["consultantName"]?.toString() || "");
93 | if (!consultantName) {
94 | throw new HttpError(400, `Missing consultant name`);
95 | }
96 | const role = cleanUpParameter("Role", body["role"]);
97 | if (!role) {
98 | throw new HttpError(400, `Missing role`);
99 | }
100 | let forecast = body["forecast"];
101 | if (!forecast) {
102 | forecast = 0;
103 | //throw new HttpError(400, `Missing forecast this month`);
104 | }
105 | console.log(`➡️ POST /api/projects: assignconsultant request, projectName=${projectName}, consultantName=${consultantName}, role=${role}, forecast=${forecast}`);
106 | const result = await ProjectApiService.addConsultantToProject
107 | (projectName, consultantName, role, forecast);
108 |
109 | res.jsonBody.results = {
110 | status: 200,
111 | clientName: result.clientName,
112 | projectName: result.projectName,
113 | consultantName: result.consultantName,
114 | remainingForecast: result.remainingForecast,
115 | message: result.message
116 | };
117 |
118 | console.log(` ✅ POST /api/projects: response status ${res.status} - ${result.message}`);
119 | } else {
120 | throw new HttpError(400, `Missing request body`);
121 | }
122 | return res;
123 | }
124 | default: {
125 | throw new HttpError(400, `Invalid command: ${id}`);
126 | }
127 | }
128 |
129 | }
130 | default: {
131 | throw new Error(`Method not allowed: ${req.method}`);
132 | }
133 | }
134 |
135 | } catch (error) {
136 |
137 | const status = error.status || error.response?.status || 500;
138 | console.log(` ⛔ Returning error status code ${status}: ${error.message}`);
139 |
140 | res.status = status;
141 | res.jsonBody.results = {
142 | status: status,
143 | message: error.message
144 | };
145 | return res;
146 | }
147 | }
148 |
149 | app.http("projects", {
150 | methods: ["GET", "POST"],
151 | authLevel: "anonymous",
152 | route: "projects/{*id}",
153 | handler: projects,
154 | });
155 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/model/apiModel.ts:
--------------------------------------------------------------------------------
1 | import { Project, Consultant, Location } from './baseModel';
2 |
3 | //#region GET requests for /projects --------------------
4 |
5 | export interface ApiProjectAssignment {
6 | consultantName: string;
7 | consultantLocation: Location;
8 | role: string;
9 | forecastThisMonth: number;
10 | forecastNextMonth: number;
11 | deliveredLastMonth: number;
12 | deliveredThisMonth: number;
13 | }
14 |
15 | // Returned by all /api/projects GET requests
16 | export interface ApiProject extends Project {
17 | consultants: ApiProjectAssignment[];
18 | forecastThisMonth: number;
19 | forecastNextMonth: number;
20 | deliveredLastMonth: number;
21 | deliveredThisMonth: number;
22 | }
23 | //#endregion
24 |
25 | //#region GET requests for /me and /consultants ---
26 |
27 | // Information about a project that a consultant is assigned to
28 | export interface ApiConsultantAssignment {
29 | projectName: string;
30 | projectDescription: string;
31 | projectLocation: Location;
32 | clientName: string;
33 | clientContact: string;
34 | clientEmail: string;
35 | role: string;
36 | forecastThisMonth: number;
37 | forecastNextMonth: number;
38 | deliveredLastMonth: number;
39 | deliveredThisMonth: number;
40 | }
41 |
42 | // Returned by all /api/consultants GET requests
43 | export interface ApiConsultant extends Consultant {
44 | projects: ApiConsultantAssignment[];
45 | forecastThisMonth: number;
46 | forecastNextMonth: number;
47 | deliveredLastMonth: number;
48 | deliveredThisMonth: number;
49 | }
50 | //#endregion
51 |
52 | //#region POST request to /api/me/chargeTime ---
53 | export interface ApiChargeTimeRequest {
54 | projectName: string;
55 | hours: number;
56 | }
57 | export interface ApiChargeTimeResponse {
58 | clientName: string;
59 | projectName: string;
60 | remainingForecast: number;
61 | message: string;
62 | }
63 | //#endregion
64 |
65 | //#region POST request to /api/projects/assignConsultant ---
66 | export interface ApiAddConsultantToProjectRequest {
67 | projectName: string;
68 | consultantName: string;
69 | role: string;
70 | hours: number;
71 | }
72 | export interface ApiAddConsultantToProjectResponse {
73 | clientName: string;
74 | projectName: string;
75 | consultantName: string;
76 | remainingForecast: number;
77 | message: string;
78 | }
79 | //#endregion
80 |
81 | export interface ErrorResult {
82 | status: number;
83 | message: string;
84 | }
85 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/model/baseModel.ts:
--------------------------------------------------------------------------------
1 | export interface Location {
2 | street: string;
3 | city: string;
4 | state: string;
5 | country: string;
6 | postalCode: string;
7 | latitude: number;
8 | longitude: number;
9 | }
10 |
11 | export interface HoursEntry {
12 | month: number;
13 | year: number;
14 | hours: number;
15 | }
16 |
17 | export interface Project {
18 | id: string;
19 | name: string;
20 | description: string;
21 | clientName: string;
22 | clientContact: string;
23 | clientEmail: string;
24 | location: Location;
25 | mapUrl: string;
26 | }
27 |
28 | export interface Consultant {
29 | id: string;
30 | name: string;
31 | email: string;
32 | phone: string;
33 | consultantPhotoUrl: string;
34 | location: Location;
35 | skills: string[];
36 | certifications: string[];
37 | roles: string[];
38 | }
39 |
40 | export interface Assignment {
41 | id: string; // The assignment ID is "projectid,consultantid"
42 | projectId: string;
43 | consultantId: string;
44 | role: string;
45 | billable: boolean;
46 | rate: number;
47 | forecast: HoursEntry [];
48 | delivered: HoursEntry[];
49 | }
50 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/model/dbModel.ts:
--------------------------------------------------------------------------------
1 | import { TableEntity } from "@azure/data-tables";
2 | import { Project, Consultant, Assignment } from "./baseModel";
3 |
4 | export interface DbEntity extends TableEntity {
5 | etag: string;
6 | partitionKey: string;
7 | rowKey: string;
8 | timestamp: Date;
9 | }
10 |
11 | export interface DbProject extends DbEntity, Project { }
12 |
13 | export interface DbConsultant extends DbEntity, Consultant { }
14 |
15 | export interface DbAssignment extends DbEntity, Assignment { }
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/AssignmentDbService.ts:
--------------------------------------------------------------------------------
1 | import DbService from './DbService';
2 | import { DbAssignment } from '../model/dbModel';
3 | import { Assignment } from '../model/baseModel';
4 | import { HttpError } from './Utilities';
5 |
6 | const TABLE_NAME = "Assignment";
7 |
8 | class AssignmentDbService {
9 |
10 | // NOTE: Assignments are READ-WRITE so disable local caching
11 | private dbService = new DbService(false);
12 |
13 | async getAssignments(): Promise {
14 | const assignments = await this.dbService.getEntities(TABLE_NAME) as DbAssignment[];
15 | const result = assignments.map((e) => this.convertDbAssignment(e));
16 | return result;
17 | }
18 |
19 | async chargeHoursToProject(projectId: string, consultantId: string, month: number, year: number, hours: number): Promise {
20 | try {
21 | const dbAssignment = await this.dbService.getEntityByRowKey(TABLE_NAME, projectId + "," + consultantId) as DbAssignment;
22 | if (!dbAssignment) {
23 | throw new HttpError(404, "Assignment not found");
24 | }
25 | // Add the hours delivered
26 | if (!dbAssignment.delivered) {
27 | dbAssignment.delivered = [{ month: month, year: year, hours: hours }];
28 | } else {
29 | let a = dbAssignment.delivered.find(d => d.month === month && d.year === year);
30 | if (a) {
31 | a.hours += hours;
32 | } else {
33 | dbAssignment.delivered.push({ month, year, hours });
34 | }
35 | }
36 | dbAssignment.delivered.sort((a, b) => a.year - b.year || a.month - b.month);
37 |
38 | // Subtract the hours from the forecast
39 | let remainingForecast = -hours;
40 | if (!dbAssignment.forecast) {
41 | dbAssignment.forecast = [{ month: month, year: year, hours: -hours }];
42 | } else {
43 | let a = dbAssignment.forecast.find(d => d.month === month && d.year === year);
44 | if (a) {
45 | a.hours -= hours;
46 | remainingForecast = a.hours;
47 | } else {
48 | dbAssignment.forecast.push({ month: month, year: year, hours: -hours });
49 | }
50 | }
51 | dbAssignment.forecast.sort((a, b) => a.year - b.year || a.month - b.month);
52 |
53 | await this.dbService.updateEntity(TABLE_NAME, dbAssignment)
54 |
55 | return remainingForecast;
56 | } catch (e) {
57 | throw new HttpError(404, "Assignment not found");
58 | }
59 | }
60 |
61 | async addConsultantToProject(projectId: string, consultantId: string, role: string, hours: number): Promise {
62 |
63 | const month = new Date().getMonth() + 1;
64 | const year = new Date().getFullYear();
65 |
66 | let dbAssignment = null;
67 | try {
68 | dbAssignment = await this.dbService.getEntityByRowKey(TABLE_NAME, projectId + "," + consultantId) as DbAssignment;
69 | } catch { }
70 |
71 | if (dbAssignment) {
72 | throw new HttpError(403, "Assignment already exists");
73 | }
74 |
75 | try {
76 | const newAssignment: DbAssignment = {
77 | etag: "",
78 | partitionKey: TABLE_NAME,
79 | rowKey: projectId + "," + consultantId,
80 | timestamp: new Date(),
81 | id: projectId + "," + consultantId,
82 | projectId: projectId,
83 | consultantId: consultantId,
84 | role: role,
85 | billable: true,
86 | rate: 100,
87 | forecast: [{ month: month, year: year, hours: hours }],
88 | delivered: []
89 | };
90 |
91 | await this.dbService.createEntity(TABLE_NAME, newAssignment.id, newAssignment)
92 |
93 | return hours;
94 | } catch (e) {
95 | throw new HttpError(500, "Unable to add assignment");
96 | }
97 | }
98 |
99 | private convertDbAssignment(dbAssignment: DbAssignment): Assignment {
100 | const result: Assignment = {
101 | id: dbAssignment.id,
102 | projectId: dbAssignment.projectId,
103 | consultantId: dbAssignment.consultantId,
104 | role: dbAssignment.role,
105 | billable: dbAssignment.billable,
106 | rate: dbAssignment.rate,
107 | forecast: dbAssignment.forecast,
108 | delivered: dbAssignment.delivered
109 | };
110 |
111 | return result;
112 | }
113 | }
114 |
115 | export default new AssignmentDbService();
116 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/ConsultantDbService.ts:
--------------------------------------------------------------------------------
1 | import DbService from './DbService';
2 | import { DbConsultant } from '../model/dbModel';
3 | import { Consultant } from '../model/baseModel';
4 |
5 | const TABLE_NAME = "Consultant";
6 |
7 | class ConsultantDbService {
8 |
9 | // NOTE: Consultants are READ ONLY in this demo app, so we are free to cache them in memory.
10 | private dbService = new DbService(true);
11 |
12 | async getConsultantById(id: string): Promise {
13 | const consultant = await this.dbService.getEntityByRowKey(TABLE_NAME, id) as DbConsultant;
14 | return consultant;
15 | }
16 |
17 | async getConsultants(): Promise {
18 | const consultants = await this.dbService.getEntities(TABLE_NAME) as DbConsultant[];
19 | return consultants;
20 | }
21 |
22 | async createConsultant(consultant: Consultant): Promise {
23 |
24 | const newDbConsultant: DbConsultant =
25 | {
26 | ...consultant,
27 | etag: "",
28 | partitionKey: TABLE_NAME,
29 | rowKey: consultant.id,
30 | timestamp: new Date()
31 | };
32 | await this.dbService.createEntity(TABLE_NAME, newDbConsultant.id, newDbConsultant)
33 |
34 | console.log (`Added new consultant ${newDbConsultant.name} (${newDbConsultant.id}) to the Consultant table`);
35 | return null;
36 | }
37 |
38 | }
39 |
40 | export default new ConsultantDbService();
41 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/DbService.ts:
--------------------------------------------------------------------------------
1 | import { TableClient, TableEntity } from "@azure/data-tables";
2 | import { HttpError } from './Utilities';
3 | import { DbEntity } from '../model/dbModel';
4 |
5 | export default class DbService {
6 |
7 | private storageAccountConnectionString = process.env.STORAGE_ACCOUNT_CONNECTION_STRING;
8 | private okToCacheLocally = false;
9 | private entityCache: DbEntity[] = [];
10 |
11 | constructor(okToCacheLocally: boolean) {
12 | if (!this.storageAccountConnectionString) {
13 | throw new Error("STORAGE_ACCOUNT_CONNECTION_STRING is not set");
14 | }
15 | this.okToCacheLocally = okToCacheLocally;
16 | }
17 |
18 | async getEntityByRowKey(tableName: string, rowKey: string): Promise {
19 | if (!this.okToCacheLocally) {
20 | const tableClient = TableClient.fromConnectionString(this.storageAccountConnectionString, tableName);
21 | const result = this.expandPropertyValues(await tableClient.getEntity(tableName, rowKey) as DbEntityType);
22 | return result as DbEntity;
23 | } else {
24 | let result = await this.getEntities(tableName);
25 | result = result.filter((e) => {
26 | return e.rowKey === rowKey
27 | });
28 | if (result.length === 0) {
29 | throw new HttpError(404, `Entity ${rowKey} not found`);
30 | } else {
31 | return result[0];
32 | }
33 | }
34 | }
35 |
36 | async getEntities(tableName: string): Promise {
37 |
38 | if (!this.okToCacheLocally || this.entityCache.length === 0) {
39 | // Rebuild cache for this entity
40 | const tableClient = TableClient.fromConnectionString(this.storageAccountConnectionString, tableName);
41 | const entities = tableClient.listEntities();
42 | this.entityCache = [];
43 | for await (const entity of entities) {
44 | // Remove any duplicates which sometimes occur after a watch restart
45 | if (this.entityCache.find((e) => e.rowKey === entity.rowKey) === undefined) {
46 | const e = this.expandPropertyValues(entity as DbEntityType);
47 | this.entityCache.push(e as DbEntity);
48 | }
49 | }
50 | }
51 | return this.entityCache;
52 | }
53 |
54 | async createEntity(tableName: string, rowKey: string, newEntity: DbEntityType): Promise {
55 |
56 | this.entityCache = [];
57 | const entity = this.compressPropertyValues(newEntity) as DbEntityType;
58 | const tableClient = TableClient.fromConnectionString(this.storageAccountConnectionString, tableName);
59 | try {
60 | await tableClient.createEntity({
61 | partitionKey: tableName,
62 | rowKey,
63 | ...entity
64 | });
65 | } catch (ex) {
66 | if (ex.response?.status !== 409) {
67 | throw new HttpError(500, ex.message);
68 | }
69 | }
70 | }
71 |
72 | async updateEntity(tableName: string, updatedEntity: DbEntityType): Promise {
73 |
74 | this.entityCache = [];
75 | const e = this.compressPropertyValues(updatedEntity) as DbEntityType;
76 | const tableClient = TableClient.fromConnectionString(this.storageAccountConnectionString, tableName);
77 | await tableClient.updateEntity(e as TableEntity, "Replace");
78 |
79 | }
80 |
81 | private expandPropertyValues(entity: DbEntityType): DbEntityType {
82 | const result = {} as DbEntityType;
83 | for (const key in entity) {
84 | result[key] = this.expandPropertyValue(entity[key]);
85 | }
86 | return result;
87 | }
88 |
89 | private expandPropertyValue(v: any): any {
90 | if (typeof v === "string" && (v.charAt(0) === '{' || v.charAt(0) === '[')) {
91 | try {
92 | return JSON.parse(v);
93 | }
94 | catch (e) {
95 | return v;
96 | }
97 | } else {
98 | return v;
99 | }
100 | };
101 |
102 | private compressPropertyValues(entity: DbEntityType): DbEntityType {
103 | const result = {} as DbEntityType;
104 | for (const key in entity) {
105 | result[key] = this.compressPropertyValue(entity[key]);
106 | }
107 | return result;
108 | }
109 |
110 | private compressPropertyValue(v: any): any {
111 | if (typeof v === "object") {
112 | return JSON.stringify(v);
113 | } else {
114 | return v;
115 | }
116 | };
117 |
118 | }
119 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/IdentityService.ts:
--------------------------------------------------------------------------------
1 | import { HttpRequest } from "@azure/functions";
2 | import { HttpError } from './Utilities';
3 | import { Consultant } from '../model/baseModel';
4 | import { ApiConsultant } from '../model/apiModel';
5 |
6 | // This is a DEMO ONLY identity solution.
7 | import ConsultantApiService from "./ConsultantApiService";
8 |
9 | class Identity {
10 | private requestNumber = 1; // Number the requests for logging purposes
11 |
12 |
13 | public async validateRequest(req: HttpRequest): Promise {
14 |
15 | // Default user used for unauthenticated testing
16 | let userId = "1";
17 | let userName = "Avery Howard";
18 | let userEmail = "avery@treyresearch.com";
19 |
20 | // ** INSERT REQUEST VALIDATION HERE (see Lab E6) **
21 |
22 | // Get the consultant record for this user; create one if necessary
23 | let consultant: ApiConsultant = null;
24 | try {
25 | consultant = await ConsultantApiService.getApiConsultantById(userId);
26 | }
27 | catch (ex) {
28 | if (ex.status !== 404) {
29 | throw ex;
30 | }
31 | // Consultant was not found, so we'll create one below
32 | consultant = null;
33 | }
34 | if (!consultant) consultant = await this.createConsultantForUser(userId, userName, userEmail);
35 |
36 | return consultant;
37 | }
38 |
39 | private async createConsultantForUser(userId: string, userName: string,
40 | userEmail: string): Promise {
41 |
42 | // Create a new consultant record for this user with default values
43 | const consultant: Consultant = {
44 | id: userId,
45 | name: userName,
46 | email: userEmail,
47 | phone: "1-555-123-4567",
48 | consultantPhotoUrl: "https://microsoft.github.io/copilot-camp/demo-assets/images/consultants/Unknown.jpg",
49 | location: {
50 | street: "One Memorial Drive",
51 | city: "Cambridge",
52 | state: "MA",
53 | country: "USA",
54 | postalCode: "02142",
55 | latitude: 42.361366,
56 | longitude: -71.081257
57 | },
58 | skills: ["JavaScript", "TypeScript"],
59 | certifications: ["Azure Development"],
60 | roles: ["Architect", "Project Lead"]
61 | };
62 | const result = await ConsultantApiService.createApiConsultant(consultant);
63 | return result;
64 | }
65 | }
66 |
67 | export default new Identity();
68 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/ProjectApiService.ts:
--------------------------------------------------------------------------------
1 | import { Project, HoursEntry, Assignment } from '../model/baseModel';
2 | import { ApiProject, ApiAddConsultantToProjectResponse } from '../model/apiModel';
3 | import ProjectDbService from './ProjectDbService';
4 | import AssignmentDbService from './AssignmentDbService';
5 | import ConsultantDbService from './ConsultantDbService';
6 | import ConsultantApiService from './ConsultantApiService';
7 | import { HttpError } from './Utilities';
8 |
9 | class ProjectApiService {
10 |
11 | async getApiProjectById(projectId: string): Promise {
12 | const project = await ProjectDbService.getProjectById(projectId);
13 | let assignments = await AssignmentDbService.getAssignments();
14 |
15 | const result = await this.getApiProject(project, assignments);
16 | return result;
17 | }
18 |
19 | async getApiProjects(projectOrClientName: string, consultantName: string): Promise {
20 |
21 | let projects = await ProjectDbService.getProjects();
22 | let assignments = await AssignmentDbService.getAssignments();
23 |
24 | // Filter on base properties
25 | if (projectOrClientName) {
26 | projects = projects.filter(
27 | (p) => {
28 | const name = p.name?.toLowerCase();
29 | const clientName = p.clientName?.toLowerCase();
30 | return name.includes(projectOrClientName.toLowerCase()) || clientName.includes(projectOrClientName.toLowerCase());
31 | });
32 | }
33 | //remove duplicates
34 | projects = projects.filter(
35 | (project, index, self) =>
36 | index === self.findIndex((p) => (
37 | p.id === project.id
38 | ))
39 | );
40 |
41 | // Augment the base properties with assignment information
42 | let result = await Promise.all(projects.map((p) => this.getApiProject(p, assignments)));
43 |
44 | // Filter on augmented properties
45 | if (result && consultantName) {
46 | result = result.filter(
47 | (p) => {
48 | const name = consultantName.toLowerCase();
49 | return p.consultants.find((n) => n.consultantName.toLowerCase().includes(name));
50 | });
51 | };
52 |
53 | return result;
54 | }
55 |
56 | // Augment a project to get an ApiProject
57 | async getApiProject(project: Project, assignments: Assignment[]): Promise {
58 |
59 | const result = project as ApiProject;
60 | assignments = assignments.filter((a) => a.projectId === project.id);
61 |
62 | result.consultants = [];
63 | result.forecastThisMonth = 0;
64 | result.forecastNextMonth = 0;
65 | result.deliveredLastMonth = 0;
66 | result.deliveredThisMonth = 0;
67 |
68 | for (let assignment of assignments) {
69 | const consultant = await ConsultantDbService.getConsultantById(assignment.consultantId);
70 | const { lastMonthHours: forecastLastMonth,
71 | thisMonthHours: forecastThisMonth,
72 | nextMonthHours: forecastNextMonth } = this.findHours(assignment.forecast);
73 | const { lastMonthHours: deliveredLastMonth,
74 | thisMonthHours: deliveredThisMonth,
75 | nextMonthHours: deliveredNextMonth } = this.findHours(assignment.delivered);
76 |
77 | result.consultants.push({
78 | consultantName: consultant.name,
79 | consultantLocation: consultant.location,
80 | role: assignment.role,
81 | forecastThisMonth: forecastThisMonth,
82 | forecastNextMonth: forecastNextMonth,
83 | deliveredLastMonth: deliveredLastMonth,
84 | deliveredThisMonth: deliveredThisMonth
85 | });
86 |
87 | result.forecastThisMonth += forecastThisMonth;
88 | result.forecastNextMonth += forecastNextMonth;
89 | result.deliveredLastMonth += deliveredLastMonth;
90 | result.deliveredThisMonth += deliveredThisMonth;
91 |
92 | }
93 | return result;
94 | }
95 |
96 | // Extract this and next month's hours from an array of HoursEntry
97 | private findHours(hours: HoursEntry[]): { lastMonthHours: number, thisMonthHours: number, nextMonthHours: number } {
98 | const now = new Date();
99 | const thisMonth = now.getMonth();
100 | const thisYear = now.getFullYear();
101 |
102 | const lastMonth = thisMonth === 0 ? 11 : thisMonth - 1;
103 | const lastYear = thisMonth === 0 ? thisYear - 1 : thisYear;
104 |
105 | const nextMonth = thisMonth === 11 ? 0 : thisMonth + 1;
106 | const nextYear = thisMonth === 11 ? thisYear + 1 : thisYear;
107 |
108 | const result = {
109 | lastMonthHours: hours.find((h) => h.month === lastMonth + 1 && h.year === lastYear)?.hours || 0,
110 | thisMonthHours: hours.find((h) => h.month === thisMonth + 1 && h.year === thisYear)?.hours || 0,
111 | nextMonthHours: hours.find((h) => h.month === nextMonth + 1 && h.year === nextYear)?.hours || 0
112 | };
113 | return result;
114 | }
115 |
116 | async addConsultantToProject(projectName: string, consultantName: string, role: string, hours: number): Promise {
117 | let projects = await this.getApiProjects(projectName, "");
118 | let consultants = await ConsultantApiService.getApiConsultants(consultantName, "", "", "", "", "");
119 |
120 | if (projects.length === 0) {
121 | throw new HttpError(404, `Project not found: ${projectName}`);
122 | } else if (projects.length > 1) {
123 | throw new HttpError(406, `Multiple projects found with the name: ${projectName}`);
124 | } else if (consultants.length === 0) {
125 | throw new HttpError(404, `Consultant not found: ${consultantName}`);
126 | } else if (consultants.length > 1) {
127 | throw new HttpError(406, `Multiple consultants found with the name: ${consultantName}`);
128 | }
129 | const project = projects[0];
130 | const consultant = consultants[0];
131 |
132 | // Always charge to the current month
133 | const remainingForecast = await AssignmentDbService.addConsultantToProject(project.id, consultant.id, role, hours);
134 | const message = `Added consultant ${consultant.name} to ${project.clientName} on project "${project.name}" with ${remainingForecast} hours forecast this month.`;
135 |
136 | return {
137 | clientName: project.clientName,
138 | projectName: project.name,
139 | consultantName: consultants[0].name,
140 | remainingForecast,
141 | message
142 | }
143 | }
144 | }
145 |
146 | export default new ProjectApiService();
147 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/ProjectDbService.ts:
--------------------------------------------------------------------------------
1 | import DbService from './DbService';
2 | import { DbProject } from '../model/dbModel';
3 | import { Project } from '../model/baseModel';
4 |
5 | const TABLE_NAME = "Project";
6 |
7 | class ProjectDbService {
8 |
9 | // NOTE: Projects are READ ONLY in this demo app, so we are free to cache them in memory.
10 | private dbService = new DbService(true);
11 |
12 | async getProjectById(id: string): Promise {
13 | const project = await this.dbService.getEntityByRowKey(TABLE_NAME, id) as DbProject;
14 | return this.convertDbProject(project);
15 | }
16 |
17 | async getProjects(): Promise {
18 | const projects = await this.dbService.getEntities(TABLE_NAME) as DbProject[];
19 | return projects.map((p) => this.convertDbProject(p));
20 | }
21 |
22 | private convertDbProject(dbProject: DbProject): Project {
23 | const result = {
24 | id: dbProject.id,
25 | name: dbProject.name,
26 | description: dbProject.description,
27 | clientName: dbProject.clientName,
28 | clientContact: dbProject.clientContact,
29 | clientEmail: dbProject.clientEmail,
30 | location: dbProject.location,
31 | mapUrl: this.getMapUrl(dbProject)
32 | };
33 | return result;
34 | }
35 |
36 | private getMapUrl(project: Project): string {
37 | let companyNameKabobCase = project.clientName.toLowerCase().replace(/ /g, "-");
38 | return `https://microsoft.github.io/copilot-camp/demo-assets/images/maps/${companyNameKabobCase}.jpg`;
39 | }
40 | }
41 |
42 | export default new ProjectDbService();
43 |
--------------------------------------------------------------------------------
/src/declarative-agent/src/services/Utilities.ts:
--------------------------------------------------------------------------------
1 | // Throw this object to return an HTTP error
2 | export class HttpError extends Error {
3 | status: number;
4 | constructor(status: number, message: string) {
5 | super(message);
6 | this.status = status;
7 | }
8 | }
9 |
10 | // Clean up common issues with Copilot parameters
11 | export function cleanUpParameter(name: string, value: string): string {
12 |
13 | let val = value.toLowerCase();
14 | if (val.toLowerCase().includes("trey") || val.toLowerCase().includes("research")) {
15 | const newVal = val.replace("trey", "").replace("research", "").trim();
16 | console.log(` ❗ Plugin name detected in the ${name} parameter '${val}'; replacing with '${newVal}'.`);
17 | val = newVal;
18 | }
19 | if (val === "") {
20 | console.log(` ❗ Invalid name '${val}'; replacing with 'avery'.`);
21 | val = "avery";
22 | }
23 | if (name==="role" && val === "consultant") {
24 | console.log(` ❗ Invalid role name '${val}'; replacing with ''.`);
25 | val = "";
26 | }
27 | if (val === "null") {
28 | console.log(` ❗ Invalid value '${val}'; replacing with ''.`);
29 | val = "";
30 | }
31 | return val;
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/src/declarative-agent/teamsapp.local.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.5/yaml.schema.json
2 | # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
3 | # Visit https://aka.ms/teamsfx-actions for details on actions
4 | version: v1.5
5 |
6 | provision:
7 | # Creates a Teams app
8 | - uses: teamsApp/create
9 | with:
10 | # Teams app name
11 | name: TreyResearch${{APP_NAME_SUFFIX}}
12 | # Write the information of created resources into environment file for
13 | # the specified environment variable(s).
14 | writeToEnvironmentFile:
15 | teamsAppId: TEAMS_APP_ID
16 |
17 | # Set required variables for local launch
18 | - uses: script
19 | with:
20 | run:
21 | echo "::set-teamsfx-env FUNC_NAME=me";
22 | echo "::set-teamsfx-env FUNC_ENDPOINT=http://localhost:7071";
23 |
24 | # Build Teams app package with latest env value
25 | - uses: teamsApp/zipAppPackage
26 | with:
27 | # Path to manifest template
28 | manifestPath: ./appPackage/manifest.json
29 | outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
30 | outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
31 |
32 | # Apply the Teams app manifest to an existing Teams app in
33 | # Teams Developer Portal.
34 | # Will use the app id in manifest file to determine which Teams app to update.
35 | - uses: teamsApp/update
36 | with:
37 | # Relative path to this file. This is the path for built zip file.
38 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
39 |
40 | # Extend your Teams app to Outlook and the Microsoft 365 app
41 | - uses: teamsApp/extendToM365
42 | with:
43 | # Relative path to the build app package.
44 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
45 | # Write the information of created resources into environment file for
46 | # the specified environment variable(s).
47 | writeToEnvironmentFile:
48 | titleId: M365_TITLE_ID
49 | appId: M365_APP_ID
50 |
51 | deploy:
52 | # Install development tool(s)
53 | - uses: devTool/install
54 | with:
55 | func:
56 | version: ~4.0.5530
57 | symlinkDir: ./devTools/func
58 | # Write the information of installed development tool(s) into environment
59 | # file for the specified environment variable(s).
60 | writeToEnvironmentFile:
61 | funcPath: FUNC_PATH
62 | # Generate runtime environment variables
63 | - uses: file/createOrUpdateEnvironmentFile
64 | with:
65 | target: ./.localConfigs
66 | envs:
67 | STORAGE_ACCOUNT_CONNECTION_STRING: ${{SECRET_STORAGE_ACCOUNT_CONNECTION_STRING}}
68 |
69 | # Run npm command
70 | - uses: cli/runNpmCommand
71 | name: install dependencies
72 | with:
73 | args: install --no-audit
74 |
75 | - uses: script
76 | name: Ensure database
77 | with:
78 | run: node db-setup.js
79 | workingDirectory: scripts
80 |
--------------------------------------------------------------------------------
/src/declarative-agent/teamsapp.yml:
--------------------------------------------------------------------------------
1 | # yaml-language-server: $schema=https://aka.ms/teams-toolkit/v1.5/yaml.schema.json
2 | # Visit https://aka.ms/teamsfx-v5.0-guide for details on this file
3 | # Visit https://aka.ms/teamsfx-actions for details on actions
4 | version: v1.5
5 |
6 | environmentFolderPath: ./env
7 |
8 | # Triggered when 'teamsapp provision' is executed
9 | provision:
10 | # Creates a Teams app
11 | - uses: teamsApp/create
12 | with:
13 | # Teams app name
14 | name: TreyResearch${{APP_NAME_SUFFIX}}
15 | # Write the information of created resources into environment file for
16 | # the specified environment variable(s).
17 | writeToEnvironmentFile:
18 | teamsAppId: TEAMS_APP_ID
19 |
20 | - uses: arm/deploy # Deploy given ARM templates parallelly.
21 | with:
22 | # AZURE_SUBSCRIPTION_ID is a built-in environment variable,
23 | # if its value is empty, TeamsFx will prompt you to select a subscription.
24 | # Referencing other environment variables with empty values
25 | # will skip the subscription selection prompt.
26 | subscriptionId: ${{AZURE_SUBSCRIPTION_ID}}
27 | # AZURE_RESOURCE_GROUP_NAME is a built-in environment variable,
28 | # if its value is empty, TeamsFx will prompt you to select or create one
29 | # resource group.
30 | # Referencing other environment variables with empty values
31 | # will skip the resource group selection prompt.
32 | resourceGroupName: ${{AZURE_RESOURCE_GROUP_NAME}}
33 | templates:
34 | - path: ./infra/azure.bicep # Relative path to this file
35 | # Relative path to this yaml file.
36 | # Placeholders will be replaced with corresponding environment
37 | # variable before ARM deployment.
38 | parameters: ./infra/azure.parameters.json
39 | # Required when deploying ARM template
40 | deploymentName: Create-resources-for-sme
41 | # Teams Toolkit will download this bicep CLI version from github for you,
42 | # will use bicep CLI in PATH if you remove this config.
43 | bicepCliVersion: v0.9.1
44 |
45 | - uses: script
46 | name: Ensure database
47 | with:
48 | run: node db-setup.js ${{SECRET_STORAGE_ACCOUNT_CONNECTION_STRING}}
49 | workingDirectory: scripts
50 |
51 | # Validate using manifest schema
52 | # - uses: teamsApp/validateManifest
53 | # with:
54 | # # Path to manifest template
55 | # manifestPath: ./appPackage/manifest.json
56 |
57 | # Build Teams app package with latest env value
58 | - uses: teamsApp/zipAppPackage
59 | with:
60 | # Path to manifest template
61 | manifestPath: ./appPackage/manifest.json
62 | outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
63 | outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
64 |
65 | # Apply the Teams app manifest to an existing Teams app in
66 | # Teams Developer Portal.
67 | # Will use the app id in manifest file to determine which Teams app to update.
68 | - uses: teamsApp/update
69 | with:
70 | # Relative path to this file. This is the path for built zip file.
71 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
72 |
73 | # Extend your Teams app to Outlook and the Microsoft 365 app
74 | - uses: teamsApp/extendToM365
75 | with:
76 | # Relative path to the build app package.
77 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
78 | # Write the information of created resources into environment file for
79 | # the specified environment variable(s).
80 | writeToEnvironmentFile:
81 | titleId: M365_TITLE_ID
82 | appId: M365_APP_ID
83 |
84 | # Triggered when 'teamsapp deploy' is executed
85 | deploy:
86 | # Run npm command
87 | - uses: cli/runNpmCommand
88 | name: install dependencies
89 | with:
90 | args: install
91 |
92 | - uses: cli/runNpmCommand
93 | name: build app
94 | with:
95 | args: run build --if-present
96 |
97 | # Deploy your application to Azure Functions using the zip deploy feature.
98 | # For additional details, see at https://aka.ms/zip-deploy-to-azure-functions
99 | - uses: azureFunctions/zipDeploy
100 | with:
101 | # deploy base folder
102 | artifactFolder: .
103 | # Ignore file location, leave blank will ignore nothing
104 | ignoreFile: .funcignore
105 | # The resource id of the cloud resource to be deployed to.
106 | # This key will be generated by arm/deploy action automatically.
107 | # You can replace it with your existing Azure Resource id
108 | # or add it to your environment variable file.
109 | resourceId: ${{API_FUNCTION_RESOURCE_ID}}
110 |
111 | # Triggered when 'teamsapp publish' is executed
112 | publish:
113 | # Build Teams app package with latest env value
114 | - uses: teamsApp/zipAppPackage
115 | with:
116 | # Path to manifest template
117 | manifestPath: ./appPackage/manifest.json
118 | outputZipPath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
119 | outputJsonPath: ./appPackage/build/manifest.${{TEAMSFX_ENV}}.json
120 | # Apply the Teams app manifest to an existing Teams app in
121 | # Teams Developer Portal.
122 | # Will use the app id in manifest file to determine which Teams app to update.
123 | - uses: teamsApp/update
124 | with:
125 | # Relative path to this file. This is the path for built zip file.
126 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
127 | # Publish the app to
128 | # Teams Admin Center (https://admin.teams.microsoft.com/policies/manage-apps)
129 | # for review and approval
130 | - uses: teamsApp/publishAppPackage
131 | with:
132 | appPackagePath: ./appPackage/build/appPackage.${{TEAMSFX_ENV}}.zip
133 | # Write the information of created resources into environment file for
134 | # the specified environment variable(s).
135 | writeToEnvironmentFile:
136 | publishedAppId: TEAMS_APP_PUBLISHED_APP_ID
137 | projectId: 4aa98513-2d59-4e3e-8d3c-23c2c89adf2d
138 |
--------------------------------------------------------------------------------
/src/declarative-agent/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "module": "commonjs",
4 | "target": "es6",
5 | "outDir": "dist",
6 | "rootDir": ".",
7 | "sourceMap": true,
8 | "strict": false,
9 | "resolveJsonModule": true,
10 | "esModuleInterop": true,
11 | "typeRoots": ["./node_modules/@types"]
12 | }
13 | }
--------------------------------------------------------------------------------