Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3daed114ee | ||
|
|
5e097276a0 | ||
|
|
383d551b86 | ||
|
|
2a345e5d3b | ||
|
|
632f67b92e | ||
|
|
5e6eb6f121 | ||
|
|
dbfacbfc4a | ||
|
|
29cec0d27b | ||
|
|
96d242fa7f | ||
|
|
9486c76ef8 | ||
|
|
caa75f7cdd | ||
|
|
cfb033e5d4 | ||
|
|
d222079df9 | ||
|
|
e89e745bc0 | ||
|
|
bad05a2bdc | ||
|
|
7e0767a0a0 | ||
|
|
af772997af | ||
|
|
b343625c1f | ||
|
|
9bc7b27813 | ||
|
|
6a2efeae7c | ||
|
|
6169df04cb | ||
|
|
331201294b | ||
|
|
fa9e086576 | ||
|
|
dcc218dbac | ||
|
|
6bd2cfec03 | ||
|
|
ab8ba8fc61 | ||
|
|
9cafd7e58b | ||
|
|
d5335fbeae | ||
|
|
bf4ad48cf2 | ||
|
|
01fc518b29 | ||
|
|
f3c3efed43 | ||
|
|
bbccb7c28c | ||
|
|
dbc312a78a | ||
|
|
bb9ed63a34 | ||
|
|
6b94315161 | ||
|
|
bc0e65d716 | ||
|
|
4268080c20 | ||
|
|
fe08574a7c | ||
|
|
f970a699d8 | ||
|
|
f29bae8fbc | ||
|
|
c3901a4ddd | ||
|
|
ba617fc3b5 | ||
|
|
afa7834e2e | ||
|
|
c6951c21f6 | ||
|
|
a982428916 | ||
|
|
90a3e5de47 | ||
|
|
49a6e433a3 | ||
|
|
6086a74302 | ||
|
|
fa8cfb7567 | ||
|
|
6de4c24fdd | ||
|
|
a5f355e04a | ||
|
|
0cf48923cd | ||
|
|
cdc4809b8a | ||
|
|
043208241a | ||
|
|
05ebb966cf | ||
|
|
c83a944e85 | ||
|
|
5d98beddf5 | ||
|
|
e0d0ad16a0 | ||
|
|
f36096ce1a | ||
|
|
03e14ca187 | ||
|
|
b298113d15 | ||
|
|
8091d052d8 | ||
|
|
52a8045bb6 |
@@ -0,0 +1,64 @@
|
||||
name: Free runner disk space
|
||||
description: |
|
||||
Reclaims disk space on GitHub-hosted Ubuntu runners by removing
|
||||
pre-installed toolchains we do not use (Android SDK, GHC/Haskell,
|
||||
CodeQL bundle), Docker images, and swap. Also relocates the
|
||||
NuGet package cache to /mnt (which has ~75 GB free vs ~14 GB
|
||||
on /). No-op on non-Linux runners.
|
||||
|
||||
runs:
|
||||
using: composite
|
||||
steps:
|
||||
- name: Free disk space (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
echo "::group::Disk usage before cleanup"
|
||||
df -h /
|
||||
echo "::endgroup::"
|
||||
|
||||
# Remove pre-installed toolchains we never use on this repo's
|
||||
# dotnet/python jobs. These reclaim ~25-30 GB on ubuntu-latest.
|
||||
sudo rm -rf \
|
||||
/usr/local/lib/android \
|
||||
/usr/share/dotnet/sdk/NuGetFallbackFolder \
|
||||
/opt/ghc \
|
||||
/usr/local/.ghcup \
|
||||
/opt/hostedtoolcache/CodeQL \
|
||||
/opt/hostedtoolcache/PyPy \
|
||||
/opt/hostedtoolcache/Ruby \
|
||||
/opt/hostedtoolcache/go \
|
||||
/usr/local/share/boost \
|
||||
/usr/local/share/powershell \
|
||||
/usr/local/share/chromium \
|
||||
/usr/local/share/vcpkg \
|
||||
/usr/local/lib/heroku \
|
||||
"${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}/PyPy" \
|
||||
"${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}/Ruby" \
|
||||
"${AGENT_TOOLSDIRECTORY:-/opt/hostedtoolcache}/go" || true
|
||||
|
||||
# Drop docker images shipped on the runner; jobs that need
|
||||
# docker pull what they need fresh.
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
sudo docker image prune --all --force >/dev/null 2>&1 || true
|
||||
fi
|
||||
|
||||
# Disable swap to free its backing file.
|
||||
sudo swapoff -a || true
|
||||
sudo rm -f /mnt/swapfile /swapfile || true
|
||||
|
||||
echo "::group::Disk usage after cleanup"
|
||||
df -h /
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Relocate NuGet package cache to /mnt (Linux only)
|
||||
if: runner.os == 'Linux'
|
||||
shell: bash
|
||||
run: |
|
||||
set -euo pipefail
|
||||
sudo mkdir -p /mnt/nuget
|
||||
sudo chown -R "$USER":"$USER" /mnt/nuget
|
||||
echo "NUGET_PACKAGES=/mnt/nuget" >> "$GITHUB_ENV"
|
||||
echo "Relocated NuGet package cache to /mnt/nuget"
|
||||
df -h /mnt || true
|
||||
@@ -8,6 +8,7 @@ function getPullRequest(context) {
|
||||
|
||||
return {
|
||||
author: pullRequest.user.login,
|
||||
authorType: pullRequest.user.type,
|
||||
labels: pullRequest.labels?.map((label) => label.name).filter(Boolean) ?? [],
|
||||
number: pullRequest.number,
|
||||
};
|
||||
@@ -49,6 +50,10 @@ function hasLabel(labels, labelName) {
|
||||
return labels.some((label) => label.toLowerCase() === labelName.toLowerCase());
|
||||
}
|
||||
|
||||
function isDependabotAuthor({ author, authorType }) {
|
||||
return authorType === 'Bot' && author.toLowerCase() === 'dependabot[bot]';
|
||||
}
|
||||
|
||||
function buildLimitMessage({ author, exemptLabelName, maxOpenPrs, openPrCount }) {
|
||||
return [
|
||||
`Thank you for your contribution, @${author}.`,
|
||||
@@ -63,24 +68,37 @@ function buildLimitMessage({ author, exemptLabelName, maxOpenPrs, openPrCount })
|
||||
}
|
||||
|
||||
async function getOpenPrCount({ github, owner, repo, author, pullRequestNumber }) {
|
||||
const query = `repo:${owner}/${repo} is:pr is:open author:${author}`;
|
||||
const response = await github.rest.search.issuesAndPullRequests({
|
||||
q: query,
|
||||
const openPullRequests = await github.paginate(github.rest.pulls.list, {
|
||||
owner,
|
||||
repo,
|
||||
state: 'open',
|
||||
per_page: 100,
|
||||
});
|
||||
|
||||
const indexedPrNumbers = response.data.items.map((item) => item.number);
|
||||
const currentPrIsIndexed = indexedPrNumbers.includes(pullRequestNumber);
|
||||
if (currentPrIsIndexed || response.data.total_count >= 100) {
|
||||
return response.data.total_count;
|
||||
}
|
||||
const authorOpenPullRequestNumbers = openPullRequests
|
||||
.filter((pullRequest) => pullRequest.user?.login === author)
|
||||
.map((pullRequest) => pullRequest.number);
|
||||
const currentPrIsOpen = authorOpenPullRequestNumbers.includes(pullRequestNumber);
|
||||
const existingOpenPrCount = currentPrIsOpen
|
||||
? authorOpenPullRequestNumbers.length - 1
|
||||
: authorOpenPullRequestNumbers.length;
|
||||
|
||||
return response.data.total_count + 1;
|
||||
return existingOpenPrCount + 1;
|
||||
}
|
||||
|
||||
async function enforcePrLimit({ github, context, core, exemptLabelName, maxOpenPrs, labelName }) {
|
||||
const { owner, repo } = context.repo;
|
||||
const { author, labels, number } = getPullRequest(context);
|
||||
const { author, authorType, labels, number } = getPullRequest(context);
|
||||
|
||||
if (isDependabotAuthor({ author, authorType })) {
|
||||
core.info(`Author ${author} is Dependabot; skipping open PR limit enforcement.`);
|
||||
return {
|
||||
author,
|
||||
closed: false,
|
||||
dependabotExempt: true,
|
||||
openPrCount: null,
|
||||
};
|
||||
}
|
||||
|
||||
if (hasLabel(labels, exemptLabelName)) {
|
||||
core.info(`PR #${number} has the ${exemptLabelName} label; skipping open PR limit enforcement.`);
|
||||
|
||||
@@ -16,7 +16,7 @@ const { enforcePrLimit } = require('../scripts/pr_limit_moderation.js');
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
function createContext({ author = 'community-user', labels = [], number = 123 } = {}) {
|
||||
function createContext({ author = 'community-user', authorType = 'User', labels = [], number = 123 } = {}) {
|
||||
return {
|
||||
repo: {
|
||||
owner: 'microsoft',
|
||||
@@ -28,6 +28,7 @@ function createContext({ author = 'community-user', labels = [], number = 123 }
|
||||
labels: labels.map((name) => ({ name })),
|
||||
user: {
|
||||
login: author,
|
||||
type: authorType,
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -44,23 +45,20 @@ function createCore() {
|
||||
};
|
||||
}
|
||||
|
||||
function createGithub({ totalCount, itemNumbers, labelExists = true }) {
|
||||
function createGithub({
|
||||
itemNumbers,
|
||||
labelExists = true,
|
||||
pullRequests = createPullRequestPage({ numbers: itemNumbers }),
|
||||
}) {
|
||||
const calls = [];
|
||||
|
||||
return {
|
||||
calls,
|
||||
async paginate(method, params) {
|
||||
calls.push({ api: 'paginate', method, params });
|
||||
return pullRequests;
|
||||
},
|
||||
rest: {
|
||||
search: {
|
||||
async issuesAndPullRequests(params) {
|
||||
calls.push({ api: 'search.issuesAndPullRequests', params });
|
||||
return {
|
||||
data: {
|
||||
total_count: totalCount,
|
||||
items: itemNumbers.map((number) => ({ number })),
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
issues: {
|
||||
async getLabel(params) {
|
||||
calls.push({ api: 'issues.getLabel', params });
|
||||
@@ -85,6 +83,10 @@ function createGithub({ totalCount, itemNumbers, labelExists = true }) {
|
||||
},
|
||||
},
|
||||
pulls: {
|
||||
async list(params) {
|
||||
calls.push({ api: 'pulls.list', params });
|
||||
return { data: pullRequests };
|
||||
},
|
||||
async update(params) {
|
||||
calls.push({ api: 'pulls.update', params });
|
||||
return { data: { state: params.state } };
|
||||
@@ -94,6 +96,15 @@ function createGithub({ totalCount, itemNumbers, labelExists = true }) {
|
||||
};
|
||||
}
|
||||
|
||||
function createPullRequestPage({ author = 'community-user', numbers }) {
|
||||
return numbers.map((number) => ({
|
||||
number,
|
||||
user: {
|
||||
login: author,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// PR limit enforcement
|
||||
@@ -102,7 +113,6 @@ function createGithub({ totalCount, itemNumbers, labelExists = true }) {
|
||||
describe('PR limit enforcement', () => {
|
||||
it('does not close the PR when the author is at the open PR limit', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 10,
|
||||
itemNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 123],
|
||||
});
|
||||
|
||||
@@ -119,14 +129,13 @@ describe('PR limit enforcement', () => {
|
||||
assert.equal(result.openPrCount, 10);
|
||||
assert.deepEqual(
|
||||
github.calls.map((call) => call.api),
|
||||
['search.issuesAndPullRequests'],
|
||||
['paginate'],
|
||||
);
|
||||
});
|
||||
|
||||
it('counts the new PR when search has not indexed it yet', async () => {
|
||||
it('counts the new PR when the pull list includes it', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 10,
|
||||
itemNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
itemNumbers: [123, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10],
|
||||
});
|
||||
|
||||
const result = await enforcePrLimit({
|
||||
@@ -143,7 +152,7 @@ describe('PR limit enforcement', () => {
|
||||
assert.deepEqual(
|
||||
github.calls.map((call) => call.api),
|
||||
[
|
||||
'search.issuesAndPullRequests',
|
||||
'paginate',
|
||||
'issues.getLabel',
|
||||
'issues.addLabels',
|
||||
'issues.createComment',
|
||||
@@ -152,9 +161,31 @@ describe('PR limit enforcement', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('counts the current PR on top of existing open PRs', async () => {
|
||||
const github = createGithub({
|
||||
itemNumbers: [123, ...Array.from({ length: 24 }, (_, index) => index + 1)],
|
||||
pullRequests: createPullRequestPage({
|
||||
numbers: [123, ...Array.from({ length: 25 }, (_, index) => index + 1)],
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await enforcePrLimit({
|
||||
github,
|
||||
context: createContext(),
|
||||
core: createCore(),
|
||||
exemptLabelName: 'pr-limit-exempt',
|
||||
maxOpenPrs: 10,
|
||||
labelName: 'too-many-prs',
|
||||
});
|
||||
|
||||
assert.equal(result.closed, true);
|
||||
assert.equal(result.openPrCount, 26);
|
||||
const comment = github.calls.find((call) => call.api === 'issues.createComment').params.body;
|
||||
assert.match(comment, /This PR would put you at 26 open pull requests/);
|
||||
});
|
||||
|
||||
it('creates the label when it does not already exist', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 11,
|
||||
itemNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 123],
|
||||
labelExists: false,
|
||||
});
|
||||
@@ -172,7 +203,7 @@ describe('PR limit enforcement', () => {
|
||||
assert.deepEqual(
|
||||
github.calls.map((call) => call.api),
|
||||
[
|
||||
'search.issuesAndPullRequests',
|
||||
'paginate',
|
||||
'issues.getLabel',
|
||||
'issues.createLabel',
|
||||
'issues.addLabels',
|
||||
@@ -188,7 +219,6 @@ describe('PR limit enforcement', () => {
|
||||
|
||||
it('tolerates a 422 race when creating the label', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 11,
|
||||
itemNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 123],
|
||||
labelExists: false,
|
||||
});
|
||||
@@ -212,7 +242,7 @@ describe('PR limit enforcement', () => {
|
||||
assert.deepEqual(
|
||||
github.calls.map((call) => call.api),
|
||||
[
|
||||
'search.issuesAndPullRequests',
|
||||
'paginate',
|
||||
'issues.getLabel',
|
||||
'issues.createLabel',
|
||||
'issues.addLabels',
|
||||
@@ -224,8 +254,11 @@ describe('PR limit enforcement', () => {
|
||||
|
||||
it('uses a diplomatic close message with the configured limit', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 11,
|
||||
itemNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 123],
|
||||
pullRequests: createPullRequestPage({
|
||||
author: 'octo-contributor',
|
||||
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 123],
|
||||
}),
|
||||
});
|
||||
|
||||
await enforcePrLimit({
|
||||
@@ -246,7 +279,6 @@ describe('PR limit enforcement', () => {
|
||||
|
||||
it('does not close an exempt PR when it is reopened', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 11,
|
||||
itemNumbers: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 123],
|
||||
});
|
||||
|
||||
@@ -265,10 +297,33 @@ describe('PR limit enforcement', () => {
|
||||
assert.deepEqual(github.calls, []);
|
||||
});
|
||||
|
||||
it('does not over-count when the current PR is not on the first search page', async () => {
|
||||
it('does not close Dependabot PRs', async () => {
|
||||
const github = createGithub({
|
||||
totalCount: 101,
|
||||
itemNumbers: Array.from({ length: 100 }, (_, index) => index + 1),
|
||||
itemNumbers: [123, ...Array.from({ length: 25 }, (_, index) => index + 1)],
|
||||
pullRequests: createPullRequestPage({
|
||||
author: 'dependabot[bot]',
|
||||
numbers: [123, ...Array.from({ length: 25 }, (_, index) => index + 1)],
|
||||
}),
|
||||
});
|
||||
|
||||
const result = await enforcePrLimit({
|
||||
github,
|
||||
context: createContext({ author: 'dependabot[bot]', authorType: 'Bot' }),
|
||||
core: createCore(),
|
||||
exemptLabelName: 'pr-limit-exempt',
|
||||
maxOpenPrs: 10,
|
||||
labelName: 'too-many-prs',
|
||||
});
|
||||
|
||||
assert.equal(result.closed, false);
|
||||
assert.equal(result.dependabotExempt, true);
|
||||
assert.equal(result.openPrCount, null);
|
||||
assert.deepEqual(github.calls, []);
|
||||
});
|
||||
|
||||
it('counts the current PR when the author has more than one page of open PRs', async () => {
|
||||
const github = createGithub({
|
||||
itemNumbers: [123, ...Array.from({ length: 100 }, (_, index) => index + 1)],
|
||||
});
|
||||
|
||||
const result = await enforcePrLimit({
|
||||
|
||||
@@ -121,6 +121,9 @@ jobs:
|
||||
python
|
||||
declarative-agents
|
||||
|
||||
- name: Free runner disk space
|
||||
uses: ./.github/actions/free-runner-disk-space
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
@@ -191,6 +194,9 @@ jobs:
|
||||
python
|
||||
declarative-agents
|
||||
|
||||
- name: Free runner disk space
|
||||
uses: ./.github/actions/free-runner-disk-space
|
||||
|
||||
# Start Cosmos DB Emulator for all integration tests and only for unit tests when CosmosDB changes happened)
|
||||
- name: Start Azure Cosmos DB Emulator
|
||||
if: ${{ runner.os == 'Windows' && (needs.paths-filter.outputs.cosmosDbChanges == 'true' || (github.event_name != 'pull_request' && matrix.integration-tests)) }}
|
||||
@@ -365,6 +371,9 @@ jobs:
|
||||
dotnet
|
||||
python
|
||||
|
||||
- name: Free runner disk space
|
||||
uses: ./.github/actions/free-runner-disk-space
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
@@ -452,6 +461,9 @@ jobs:
|
||||
python
|
||||
declarative-agents
|
||||
|
||||
- name: Free runner disk space
|
||||
uses: ./.github/actions/free-runner-disk-space
|
||||
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@c2fa09f4bde5ebb9d1777cf28262a3eb3db3ced7 # v5.2.0
|
||||
with:
|
||||
|
||||
@@ -474,6 +474,45 @@ jobs:
|
||||
path: ./python/pytest.xml
|
||||
if-no-files-found: ignore
|
||||
|
||||
# GitHub Copilot integration tests
|
||||
python-tests-github-copilot:
|
||||
name: Python Integration Tests - GitHub Copilot
|
||||
runs-on: ubuntu-latest
|
||||
environment: integration
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
GITHUB_COPILOT_TIMEOUT: "120"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
with:
|
||||
ref: ${{ inputs.checkout-ref }}
|
||||
persist-credentials: false
|
||||
- name: Set up python and install the project
|
||||
id: python-setup
|
||||
uses: ./.github/actions/python-setup
|
||||
with:
|
||||
python-version: ${{ env.UV_PYTHON }}
|
||||
os: ${{ runner.os }}
|
||||
- name: Test with pytest (GitHub Copilot integration)
|
||||
run: >
|
||||
uv run pytest --import-mode=importlib
|
||||
packages/github_copilot/tests
|
||||
-m integration
|
||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||
--retries 2 --retry-delay 5
|
||||
--junitxml=pytest.xml
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: test-results-github-copilot
|
||||
path: ./python/pytest.xml
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Integration test trend report (aggregates per-job JUnit XML results)
|
||||
python-integration-test-report:
|
||||
name: Integration Test Report
|
||||
@@ -490,6 +529,7 @@ jobs:
|
||||
python-tests-foundry,
|
||||
python-tests-foundry-hosting,
|
||||
python-tests-cosmos,
|
||||
python-tests-github-copilot,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
@@ -553,7 +593,8 @@ jobs:
|
||||
python-tests-functions,
|
||||
python-tests-foundry,
|
||||
python-tests-foundry-hosting,
|
||||
python-tests-cosmos
|
||||
python-tests-cosmos,
|
||||
python-tests-github-copilot
|
||||
]
|
||||
steps:
|
||||
- name: Fail workflow if tests failed
|
||||
|
||||
@@ -40,6 +40,7 @@ jobs:
|
||||
foundryChanged: ${{ steps.filter.outputs.foundry }}
|
||||
foundryHostingChanged: ${{ steps.filter.outputs.foundry_hosting }}
|
||||
cosmosChanged: ${{ steps.filter.outputs.cosmos }}
|
||||
githubCopilotChanged: ${{ steps.filter.outputs.github_copilot }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: dorny/paths-filter@d1c1ffe0248fe513906c8e24db8ea791d46f8590 # v3
|
||||
@@ -85,6 +86,8 @@ jobs:
|
||||
- 'python/packages/foundry_hosting/**'
|
||||
cosmos:
|
||||
- 'python/packages/azure-cosmos/**'
|
||||
github_copilot:
|
||||
- 'python/packages/github_copilot/**'
|
||||
# run only if 'python' files were changed
|
||||
- name: python tests
|
||||
if: steps.filter.outputs.python == 'true'
|
||||
@@ -658,6 +661,58 @@ jobs:
|
||||
path: ./python/pytest.xml
|
||||
if-no-files-found: ignore
|
||||
|
||||
# GitHub Copilot integration tests
|
||||
python-tests-github-copilot:
|
||||
name: Python Tests - GitHub Copilot Integration
|
||||
needs: paths-filter
|
||||
if: >
|
||||
github.event_name != 'pull_request' &&
|
||||
needs.paths-filter.outputs.pythonChanges == 'true' &&
|
||||
(github.event_name != 'merge_group' ||
|
||||
needs.paths-filter.outputs.githubCopilotChanged == 'true' ||
|
||||
needs.paths-filter.outputs.coreChanged == 'true')
|
||||
runs-on: ubuntu-latest
|
||||
environment: integration
|
||||
timeout-minutes: 60
|
||||
env:
|
||||
COPILOT_GITHUB_TOKEN: ${{ secrets.COPILOT_GITHUB_TOKEN }}
|
||||
GITHUB_COPILOT_TIMEOUT: "120"
|
||||
defaults:
|
||||
run:
|
||||
working-directory: python
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- name: Set up python and install the project
|
||||
id: python-setup
|
||||
uses: ./.github/actions/python-setup
|
||||
with:
|
||||
python-version: ${{ env.UV_PYTHON }}
|
||||
os: ${{ runner.os }}
|
||||
- name: Test with pytest (GitHub Copilot integration)
|
||||
run: >
|
||||
uv run pytest --import-mode=importlib
|
||||
packages/github_copilot/tests
|
||||
-m integration
|
||||
--timeout=120 --session-timeout=900 --timeout_method thread
|
||||
--retries 2 --retry-delay 5
|
||||
--junitxml=pytest.xml
|
||||
- name: Surface failing tests
|
||||
if: always()
|
||||
uses: pmeier/pytest-results-action@20b595761ba9bf89e115e875f8bc863f913bc8ad # v0.7.2
|
||||
with:
|
||||
path: ./python/pytest.xml
|
||||
summary: true
|
||||
display-options: fEX
|
||||
fail-on-empty: false
|
||||
title: GitHub Copilot integration test results
|
||||
- name: Upload test results
|
||||
if: always()
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: test-results-github-copilot
|
||||
path: ./python/pytest.xml
|
||||
if-no-files-found: ignore
|
||||
|
||||
# Integration test trend report (aggregates per-job JUnit XML results)
|
||||
python-integration-test-report:
|
||||
name: Integration Test Report
|
||||
@@ -674,6 +729,7 @@ jobs:
|
||||
python-tests-foundry,
|
||||
python-tests-foundry-hosting,
|
||||
python-tests-cosmos,
|
||||
python-tests-github-copilot,
|
||||
]
|
||||
runs-on: ubuntu-latest
|
||||
defaults:
|
||||
@@ -735,6 +791,7 @@ jobs:
|
||||
python-tests-foundry,
|
||||
python-tests-foundry-hosting,
|
||||
python-tests-cosmos,
|
||||
python-tests-github-copilot,
|
||||
]
|
||||
steps:
|
||||
- name: Fail workflow if tests failed
|
||||
|
||||
@@ -8,6 +8,7 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
@@ -23,7 +24,7 @@ jobs:
|
||||
- name: Download coverage report
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
with:
|
||||
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
|
||||
github-token: ${{ github.token }}
|
||||
run-id: ${{ github.event.workflow_run.id }}
|
||||
path: ./python
|
||||
merge-multiple: true
|
||||
@@ -38,9 +39,9 @@ jobs:
|
||||
echo "PR number file 'pr_number' is missing or empty"
|
||||
exit 1
|
||||
fi
|
||||
PR_NUMBER=$(head -1 pr_number | tr -dc '0-9')
|
||||
if [ -z "$PR_NUMBER" ]; then
|
||||
echo "PR number file 'pr_number' does not contain a valid PR number"
|
||||
PR_NUMBER=$(cat pr_number)
|
||||
if ! [[ "$PR_NUMBER" =~ ^[0-9]+$ ]]; then
|
||||
echo "::error::PR number file contains invalid content"
|
||||
exit 1
|
||||
fi
|
||||
echo "PR_NUMBER=$PR_NUMBER" >> "$GITHUB_ENV"
|
||||
@@ -48,7 +49,7 @@ jobs:
|
||||
id: coverageComment
|
||||
uses: MishaKav/pytest-coverage-comment@26f986d2599c288bb62f623d29c2da98609e9cd4 # v1.6.0
|
||||
with:
|
||||
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
|
||||
github-token: ${{ github.token }}
|
||||
issue-number: ${{ env.PR_NUMBER }}
|
||||
pytest-xml-coverage-path: python/python-coverage.xml
|
||||
title: "Python Test Coverage Report"
|
||||
|
||||
@@ -248,3 +248,4 @@ dotnet/filtered-*.slnx
|
||||
.omx/
|
||||
|
||||
**/issues/
|
||||
.test_*
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
||||
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
|
||||
issues before filing new issues to avoid duplicates. For new issues, file your bug or
|
||||
feature request as a new Issue.
|
||||
|
||||
For help and questions about using this project, please create a GitHub issue.
|
||||
|
||||
AI Support team will support Microsoft Agent Framework issues for customers under a **Unified support agreement when the issue arises from usage of Azure AI services** (Foundry Models, Foundry Agents etc.) in conjunction with the SDK. Conversely, if customer has any other / non unified support agreement and/or Agent Framework SDK is used in a way **not involving an Azure service**, it is treated as a purely open-source tool – Microsoft’s support organization will not handle it, and users should use GitHub or forums for assistance
|
||||
|
||||
For Copilot Studio SDK implementation issues, customers should use GitHub Issues for assistance, as outlined above. Conversely, for prerequisites managed within the Copilot Studio portal, customers can rely on the standard Microsoft Copilot Studio support channels.
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
|
||||
# Support
|
||||
|
||||
## How to file issues and get help
|
||||
|
||||
This project uses GitHub Issues to track bugs and feature requests. Please search the existing
|
||||
issues before filing new issues to avoid duplicates. For new issues, file your bug or
|
||||
feature request as a new Issue.
|
||||
|
||||
For help and questions about using this project, please create a GitHub issue.
|
||||
|
||||
AI Support team will support Microsoft Agent Framework issues for customers under a **Unified support agreement when the issue arises from usage of Azure AI services** (Foundry Models, Foundry Agents etc.) in conjunction with the SDK. Conversely, if customer has any other / non unified support agreement and/or Agent Framework SDK is used in a way **not involving an Azure service**, it is treated as a purely open-source tool – Microsoft’s support organization will not handle it, and users should use GitHub or forums for assistance
|
||||
|
||||
For Copilot Studio SDK implementation issues, customers should use GitHub Issues for assistance, as outlined above. Conversely, for prerequisites managed within the Copilot Studio portal, customers can rely on the standard Microsoft Copilot Studio support channels.
|
||||
|
||||
## Microsoft Support Policy
|
||||
|
||||
Support for this **PROJECT or PRODUCT** is limited to the resources listed above.
|
||||
|
||||
|
After Width: | Height: | Size: 219 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 19 KiB |
|
After Width: | Height: | Size: 20 KiB |
@@ -0,0 +1,55 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" transform="matrix(-1 0 0 1 256 0)" fill="white"/>
|
||||
<path d="M163.006 36.0262C155.129 31.4785 145.424 31.4759 137.533 36.0284L104.002 55.3877C107.838 53.2763 112.07 52.2274 116.296 52.2248C120.633 52.2231 124.976 53.3247 128.871 55.5411L174.091 81.6479C175.11 82.2362 175.738 83.3236 175.738 84.5004V151.263C175.738 152.716 177.313 153.621 178.568 152.895L196.967 142.28C204.845 137.732 209.704 129.326 209.704 120.221V77.7299C209.704 68.6342 204.855 60.227 196.967 55.6697L190.983 52.2141C190.65 52.0008 190.313 51.7921 189.969 51.5933L163.006 36.0262Z" fill="url(#paint0_linear_481_4810)"/>
|
||||
<path d="M163.006 36.0262C155.129 31.4785 145.424 31.4759 137.533 36.0284L104.002 55.3877C107.838 53.2763 112.07 52.2274 116.296 52.2248C120.633 52.2231 124.976 53.3247 128.871 55.5411L174.091 81.6479C175.11 82.2362 175.738 83.3236 175.738 84.5004V151.263C175.738 152.716 177.313 153.621 178.568 152.895L196.967 142.28C204.845 137.732 209.704 129.326 209.704 120.221V77.7299C209.704 68.6342 204.855 60.227 196.967 55.6697L190.983 52.2141C190.65 52.0008 190.313 51.7921 189.969 51.5933L163.006 36.0262Z" fill="url(#paint1_linear_481_4810)"/>
|
||||
<path d="M103.548 55.6397L103.557 55.6451L104.002 55.3877C103.851 55.471 103.698 55.5531 103.548 55.6397Z" fill="url(#paint2_linear_481_4810)"/>
|
||||
<path d="M103.548 55.6397L103.557 55.6451L104.002 55.3877C103.851 55.471 103.698 55.5531 103.548 55.6397Z" fill="url(#paint3_linear_481_4810)"/>
|
||||
<path d="M116.308 52.2209C111.903 52.2271 107.507 53.3498 103.561 55.6366C95.6702 60.1891 90.8239 68.6019 90.8231 77.6986L90.8223 167.846C90.8222 169.786 92.871 171.041 94.599 170.16L103.523 165.611C116.573 158.959 124.788 145.549 124.788 130.902V62.9894C124.778 58.6476 129.49 55.9209 133.25 58.0698L128.879 55.5453C124.976 53.3242 120.645 52.2192 116.308 52.2209Z" fill="url(#paint4_linear_481_4810)"/>
|
||||
<path d="M95.3068 221.682C103.184 226.23 112.889 226.232 120.78 221.68L154.311 202.32C150.475 204.432 146.243 205.481 142.018 205.483C137.68 205.485 133.337 204.383 129.442 202.167L84.2226 176.06C83.2035 175.472 82.5757 174.384 82.5757 173.208L82.5757 106.445C82.5757 104.992 81 104.087 79.7451 104.813L61.3465 115.428C53.4682 119.976 48.6091 128.382 48.6089 137.487L48.6089 179.978C48.6089 189.074 53.4585 197.481 61.3465 202.038L67.3303 205.494C67.6629 205.707 68.0002 205.916 68.3446 206.115L95.3068 221.682Z" fill="url(#paint5_linear_481_4810)"/>
|
||||
<path d="M95.3068 221.682C103.184 226.23 112.889 226.232 120.78 221.68L154.311 202.32C150.475 204.432 146.243 205.481 142.018 205.483C137.68 205.485 133.337 204.383 129.442 202.167L84.2226 176.06C83.2035 175.472 82.5757 174.384 82.5757 173.208L82.5757 106.445C82.5757 104.992 81 104.087 79.7451 104.813L61.3465 115.428C53.4682 119.976 48.6091 128.382 48.6089 137.487L48.6089 179.978C48.6089 189.074 53.4585 197.481 61.3465 202.038L67.3303 205.494C67.6629 205.707 68.0002 205.916 68.3446 206.115L95.3068 221.682Z" fill="url(#paint6_linear_481_4810)"/>
|
||||
<path d="M154.765 202.068L154.756 202.063L154.311 202.32C154.463 202.237 154.615 202.155 154.765 202.068Z" fill="url(#paint7_linear_481_4810)"/>
|
||||
<path d="M154.765 202.068L154.756 202.063L154.311 202.32C154.463 202.237 154.615 202.155 154.765 202.068Z" fill="url(#paint8_linear_481_4810)"/>
|
||||
<path d="M142.003 205.487C146.408 205.481 150.805 204.358 154.751 202.071C162.641 197.519 167.488 189.106 167.488 180.009L167.489 89.8618C167.489 87.9222 165.44 86.667 163.712 87.5479L154.788 92.0972C141.739 98.7494 133.523 112.159 133.523 126.806L133.523 194.719C133.533 199.06 128.821 201.787 125.061 199.638L129.432 202.163C133.336 204.384 137.666 205.489 142.003 205.487Z" fill="url(#paint9_linear_481_4810)"/>
|
||||
<defs>
|
||||
<linearGradient id="paint0_linear_481_4810" x1="207.413" y1="61.882" x2="148.999" y2="163.067" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9189F7"/>
|
||||
<stop offset="1" stop-color="#4135E9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint1_linear_481_4810" x1="188.235" y1="204.189" x2="264.21" y2="152.592" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F42FD"/>
|
||||
<stop offset="1" stop-color="#7274FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint2_linear_481_4810" x1="207.413" y1="61.882" x2="148.999" y2="163.067" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9189F7"/>
|
||||
<stop offset="1" stop-color="#4135E9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint3_linear_481_4810" x1="188.235" y1="204.189" x2="264.21" y2="152.592" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F42FD"/>
|
||||
<stop offset="1" stop-color="#7274FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint4_linear_481_4810" x1="93.1761" y1="128.826" x2="66.2399" y2="104.746" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.25" stop-color="#4F42FD"/>
|
||||
<stop offset="1" stop-color="#2C08AC"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint5_linear_481_4810" x1="50.9004" y1="195.826" x2="109.315" y2="94.6412" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9189F7"/>
|
||||
<stop offset="1" stop-color="#4135E9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint6_linear_481_4810" x1="70.0787" y1="53.5188" x2="-5.89657" y2="105.116" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F42FD"/>
|
||||
<stop offset="1" stop-color="#7274FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint7_linear_481_4810" x1="50.9004" y1="195.826" x2="109.315" y2="94.6412" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#9189F7"/>
|
||||
<stop offset="1" stop-color="#4135E9"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint8_linear_481_4810" x1="70.0787" y1="53.5188" x2="-5.89657" y2="105.116" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#4F42FD"/>
|
||||
<stop offset="1" stop-color="#7274FF"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="paint9_linear_481_4810" x1="165.135" y1="128.882" x2="192.072" y2="152.962" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.25" stop-color="#4F42FD"/>
|
||||
<stop offset="1" stop-color="#2C08AC"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.8 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" transform="matrix(-1 0 0 1 256 0)" fill="white"/>
|
||||
<path d="M167.489 89.8618C167.489 87.9224 165.441 86.667 163.713 87.5473L154.788 92.0971C141.739 98.7493 133.523 112.159 133.523 126.806V194.718C133.533 199.053 128.837 201.777 125.08 199.647L84.2227 176.06C83.2036 175.472 82.5762 174.384 82.5762 173.207V106.445C82.5759 104.992 80.9999 104.087 79.7451 104.813L61.3467 115.428C53.4685 119.976 48.6096 128.382 48.6094 137.487V179.978C48.6094 189.074 53.4588 197.481 61.3467 202.039L67.3301 205.494C67.6627 205.707 68.0004 205.916 68.3447 206.115L95.3066 221.682C103.184 226.23 112.889 226.232 120.779 221.679L154.312 202.32C154.271 202.342 154.23 202.362 154.189 202.384C154.283 202.333 154.375 202.281 154.468 202.229L154.312 202.32C154.463 202.237 154.615 202.154 154.765 202.068L154.762 202.065C162.646 197.511 167.487 189.102 167.488 180.009L167.489 89.8618Z" fill="black"/>
|
||||
<path d="M163.007 36.0259C155.13 31.4781 145.424 31.4763 137.533 36.0288L104.002 55.3882C104.029 55.3732 104.057 55.359 104.084 55.3442C104.02 55.3794 103.956 55.4159 103.893 55.4516L104.002 55.3882C103.851 55.4714 103.699 55.5536 103.549 55.6401L103.552 55.6411C95.6664 60.1948 90.8241 68.6053 90.8232 77.6987L90.8223 167.846C90.8223 169.786 92.8707 171.04 94.5986 170.16L103.523 165.611C116.573 158.959 124.788 145.549 124.788 130.902V62.9897C124.778 58.6479 129.49 55.9211 133.25 58.0698L174.091 81.6479C175.11 82.2363 175.737 83.3238 175.737 84.5005V151.263C175.738 152.716 177.314 153.621 178.568 152.895L196.967 142.28C204.845 137.732 209.704 129.326 209.704 120.221V77.73C209.704 68.6343 204.855 60.2267 196.967 55.6694L190.982 52.2143C190.65 52.0011 190.313 51.792 189.969 51.5932L163.007 36.0259Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,5 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" transform="matrix(-1 0 0 1 256 0)" fill="black"/>
|
||||
<path d="M167.489 89.8618C167.489 87.9224 165.441 86.667 163.713 87.5473L154.788 92.0971C141.739 98.7493 133.523 112.159 133.523 126.806V194.718C133.533 199.053 128.837 201.777 125.08 199.647L84.2227 176.06C83.2036 175.472 82.5762 174.384 82.5762 173.207V106.445C82.5759 104.992 80.9999 104.087 79.7451 104.813L61.3467 115.428C53.4685 119.976 48.6096 128.382 48.6094 137.487V179.978C48.6094 189.074 53.4588 197.481 61.3467 202.039L67.3301 205.494C67.6627 205.707 68.0004 205.916 68.3447 206.115L95.3066 221.682C103.184 226.23 112.889 226.232 120.779 221.679L154.312 202.32C154.271 202.342 154.23 202.362 154.189 202.384C154.283 202.333 154.375 202.281 154.468 202.229L154.312 202.32C154.463 202.237 154.615 202.154 154.765 202.068L154.762 202.065C162.646 197.511 167.487 189.102 167.488 180.009L167.489 89.8618Z" fill="white"/>
|
||||
<path d="M163.007 36.0259C155.13 31.4781 145.424 31.4763 137.533 36.0288L104.002 55.3882C104.029 55.3732 104.057 55.359 104.084 55.3442C104.02 55.3794 103.956 55.4159 103.893 55.4516L104.002 55.3882C103.851 55.4714 103.699 55.5536 103.549 55.6401L103.552 55.6411C95.6664 60.1948 90.8241 68.6053 90.8232 77.6987L90.8223 167.846C90.8223 169.786 92.8707 171.04 94.5986 170.16L103.523 165.611C116.573 158.959 124.788 145.549 124.788 130.902V62.9897C124.778 58.6479 129.49 55.9211 133.25 58.0698L174.091 81.6479C175.11 82.2363 175.737 83.3238 175.737 84.5005V151.263C175.738 152.716 177.314 153.621 178.568 152.895L196.967 142.28C204.845 137.732 209.704 129.326 209.704 120.221V77.73C209.704 68.6343 204.855 60.2267 196.967 55.6694L190.982 52.2143C190.65 52.0011 190.313 51.792 189.969 51.5932L163.007 36.0259Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.8 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" transform="matrix(-1 0 0 1 256 0)" fill="white"/>
|
||||
<path d="M48.6094 179.978V137.487C48.6096 128.382 53.4685 119.976 61.3467 115.428L79.7451 104.813C80.9999 104.087 82.5759 104.992 82.5762 106.445V173.207C82.5762 174.384 83.2036 175.472 84.2227 176.06L125.08 199.647C128.837 201.777 133.533 199.053 133.523 194.718V126.806C133.523 112.388 141.484 99.1684 154.18 92.4135L154.788 92.0971L163.713 87.5473C165.441 86.667 167.489 87.9224 167.489 89.8618L167.488 180.009L167.474 180.86C167.181 189.624 162.399 197.653 154.762 202.065L154.765 202.068C154.615 202.154 154.463 202.237 154.312 202.32L154.468 202.229C154.375 202.281 154.283 202.333 154.189 202.384C154.23 202.362 154.271 202.342 154.312 202.32L120.779 221.679L120.034 222.092C112.534 226.093 103.539 226.092 96.0508 222.095L95.3066 221.682L68.3447 206.115C68.0004 205.916 67.6627 205.707 67.3301 205.494L61.3467 202.039C53.7053 197.624 48.9149 189.595 48.623 180.829L48.6094 179.978ZM175.737 84.5005C175.737 83.3974 175.186 82.3728 174.277 81.7641L174.091 81.6479L133.25 58.0698C129.49 55.9211 124.778 58.6479 124.788 62.9897V130.902L124.782 131.587C124.53 145.966 116.369 159.063 103.523 165.611L94.5986 170.16L94.4355 170.236C92.799 170.938 90.9459 169.803 90.8281 168.026L90.8223 167.846L90.8232 77.6987C90.8241 68.6053 95.6664 60.1948 103.552 55.6411L103.549 55.6401C103.699 55.5536 103.851 55.4714 104.002 55.3882L103.893 55.4516C103.956 55.4159 104.02 55.3794 104.084 55.3442C104.057 55.359 104.029 55.3732 104.002 55.3882L137.533 36.0288C145.424 31.4763 155.13 31.4781 163.007 36.0259L189.969 51.5932C190.313 51.792 190.65 52.0011 190.982 52.2143L196.967 55.6694C204.855 60.2267 209.704 68.6343 209.704 77.73V120.221L209.689 121.073C209.397 129.848 204.599 137.874 196.967 142.28L178.568 152.895C177.314 153.621 175.738 152.716 175.737 151.263V84.5005ZM176.814 149.866L176.819 149.864L176.832 149.856C176.826 149.859 176.82 149.862 176.814 149.866ZM137.023 194.71L137.019 195.038C136.802 201.878 129.341 206.087 123.354 202.692L123.33 202.678L82.4727 179.091C80.3698 177.877 79.0762 175.634 79.0762 173.207V109.239L63.0957 118.458C56.5124 122.259 52.3745 129.183 52.1221 136.752L52.1094 137.487V179.978C52.1094 187.823 56.2909 195.075 63.0957 199.007H63.0967L69.0801 202.462L69.1504 202.503L69.2188 202.547C69.5212 202.741 69.8111 202.92 70.0947 203.083L97.0566 218.651L97.6982 219.007C104.372 222.569 112.434 222.453 119.029 218.648L152.514 199.316L152.512 199.312C152.581 199.274 152.65 199.236 152.752 199.178L152.753 199.181C152.775 199.169 152.796 199.158 152.815 199.147L153.011 199.035C159.81 195.107 163.987 187.854 163.988 180.009V91.3344L156.378 95.2153C144.501 101.27 137.023 113.475 137.023 126.806V194.71ZM94.3223 166.372L101.934 162.493C113.811 156.438 121.288 144.233 121.288 130.902V62.9897C121.278 55.9521 128.901 51.5532 134.986 55.0307L135 55.0385L175.841 78.6167L176.036 78.7339C178.023 79.9714 179.237 82.15 179.237 84.5005V148.468L195.217 139.249C202.013 135.326 206.204 128.075 206.204 120.221V77.73C206.204 69.8848 202.022 62.6318 195.217 58.6997L189.232 55.2456L189.162 55.2046L189.094 55.1606C188.788 54.9649 188.5 54.787 188.219 54.6245L161.257 39.0571C154.463 35.135 146.091 35.1311 139.282 39.0591L139.283 39.06L105.765 58.4106L105.767 58.4135C105.713 58.443 105.723 58.4383 105.602 58.5063L105.6 58.5034C105.535 58.5391 105.481 58.5691 105.433 58.5962L105.302 58.6723C98.5016 62.5995 94.324 69.8532 94.3232 77.6987L94.3223 166.372Z" fill="black"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
@@ -0,0 +1,4 @@
|
||||
<svg width="256" height="256" viewBox="0 0 256 256" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="256" height="256" transform="matrix(-1 0 0 1 256 0)" fill="black"/>
|
||||
<path d="M48.6094 179.978V137.487C48.6096 128.382 53.4685 119.976 61.3467 115.428L79.7451 104.813C80.9999 104.087 82.5759 104.992 82.5762 106.445V173.207C82.5762 174.384 83.2036 175.472 84.2227 176.06L125.08 199.647C128.837 201.777 133.533 199.053 133.523 194.718V126.806C133.523 112.388 141.484 99.1684 154.18 92.4135L154.788 92.0971L163.713 87.5473C165.441 86.667 167.489 87.9224 167.489 89.8618L167.488 180.009L167.474 180.86C167.181 189.624 162.399 197.653 154.762 202.065L154.765 202.068C154.615 202.154 154.463 202.237 154.312 202.32L154.468 202.229C154.375 202.281 154.283 202.333 154.189 202.384C154.23 202.362 154.271 202.342 154.312 202.32L120.779 221.679L120.034 222.092C112.534 226.093 103.539 226.092 96.0508 222.095L95.3066 221.682L68.3447 206.115C68.0004 205.916 67.6627 205.707 67.3301 205.494L61.3467 202.039C53.7053 197.624 48.9149 189.595 48.623 180.829L48.6094 179.978ZM175.737 84.5005C175.737 83.3974 175.186 82.3728 174.277 81.7641L174.091 81.6479L133.25 58.0698C129.49 55.9211 124.778 58.6479 124.788 62.9897V130.902L124.782 131.587C124.53 145.966 116.369 159.063 103.523 165.611L94.5986 170.16L94.4355 170.236C92.799 170.938 90.9459 169.803 90.8281 168.026L90.8223 167.846L90.8232 77.6987C90.8241 68.6053 95.6664 60.1948 103.552 55.6411L103.549 55.6401C103.699 55.5536 103.851 55.4714 104.002 55.3882L103.893 55.4516C103.956 55.4159 104.02 55.3794 104.084 55.3442C104.057 55.359 104.029 55.3732 104.002 55.3882L137.533 36.0288C145.424 31.4763 155.13 31.4781 163.007 36.0259L189.969 51.5932C190.313 51.792 190.65 52.0011 190.982 52.2143L196.967 55.6694C204.855 60.2267 209.704 68.6343 209.704 77.73V120.221L209.689 121.073C209.397 129.848 204.599 137.874 196.967 142.28L178.568 152.895C177.314 153.621 175.738 152.716 175.737 151.263V84.5005ZM176.814 149.866L176.819 149.864L176.832 149.856C176.826 149.859 176.82 149.862 176.814 149.866ZM137.023 194.71L137.019 195.038C136.802 201.878 129.341 206.087 123.354 202.692L123.33 202.678L82.4727 179.091C80.3698 177.877 79.0762 175.634 79.0762 173.207V109.239L63.0957 118.458C56.5124 122.259 52.3745 129.183 52.1221 136.752L52.1094 137.487V179.978C52.1094 187.823 56.2909 195.075 63.0957 199.007H63.0967L69.0801 202.462L69.1504 202.503L69.2188 202.547C69.5212 202.741 69.8111 202.92 70.0947 203.083L97.0566 218.651L97.6982 219.007C104.372 222.569 112.434 222.453 119.029 218.648L152.514 199.316L152.512 199.312C152.581 199.274 152.65 199.236 152.752 199.178L152.753 199.181C152.775 199.169 152.796 199.158 152.815 199.147L153.011 199.035C159.81 195.107 163.987 187.854 163.988 180.009V91.3344L156.378 95.2153C144.501 101.27 137.023 113.475 137.023 126.806V194.71ZM94.3223 166.372L101.934 162.493C113.811 156.438 121.288 144.233 121.288 130.902V62.9897C121.278 55.9521 128.901 51.5532 134.986 55.0307L135 55.0385L175.841 78.6167L176.036 78.7339C178.023 79.9714 179.237 82.15 179.237 84.5005V148.468L195.217 139.249C202.013 135.326 206.204 128.075 206.204 120.221V77.73C206.204 69.8848 202.022 62.6318 195.217 58.6997L189.232 55.2456L189.162 55.2046L189.094 55.1606C188.788 54.9649 188.5 54.787 188.219 54.6245L161.257 39.0571C154.463 35.135 146.091 35.1311 139.282 39.0591L139.283 39.06L105.765 58.4106L105.767 58.4135C105.713 58.443 105.723 58.4383 105.602 58.5063L105.6 58.5034C105.535 58.5391 105.481 58.5691 105.433 58.5962L105.302 58.6723C98.5016 62.5995 94.324 69.8532 94.3232 77.6987L94.3223 166.372Z" fill="white"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.5 KiB |
|
Before Width: | Height: | Size: 136 KiB After Width: | Height: | Size: 1.5 MiB |
@@ -99,7 +99,7 @@
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.InMemory" Version="1.67.0-preview" />
|
||||
<PackageVersion Include="Microsoft.SemanticKernel.Connectors.Qdrant" Version="1.67.0-preview" />
|
||||
<!-- Agent SDKs -->
|
||||
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.0-beta.2" />
|
||||
<PackageVersion Include="GitHub.Copilot.SDK" Version="1.0.0" />
|
||||
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
|
||||
<!-- M365 Agents SDK -->
|
||||
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
|
||||
@@ -109,7 +109,7 @@
|
||||
<PackageVersion Include="A2A" Version="1.0.0-preview2" />
|
||||
<PackageVersion Include="A2A.AspNetCore" Version="1.0.0-preview2" />
|
||||
<!-- MCP -->
|
||||
<PackageVersion Include="ModelContextProtocol" Version="1.1.0" />
|
||||
<PackageVersion Include="ModelContextProtocol" Version="1.2.0" />
|
||||
<!-- Hyperlight -->
|
||||
<PackageVersion Include="Hyperlight.HyperlightSandbox.Api" Version="0.4.0" />
|
||||
<PackageVersion Include="Hyperlight.HyperlightSandbox.Guest.Python" Version="0.4.0" />
|
||||
|
||||
@@ -344,6 +344,9 @@
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-Toolbox/HostedToolbox.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/HostedToolboxMcpSkills.csproj" />
|
||||
</Folder>
|
||||
<Folder Name="/Samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/">
|
||||
<Project Path="samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/HostedAzureSearchRag.csproj" />
|
||||
</Folder>
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
<Project>
|
||||
<PropertyGroup>
|
||||
<!-- Central version prefix - applies to all nuget packages. -->
|
||||
<VersionPrefix>1.8.0</VersionPrefix>
|
||||
<VersionPrefix>1.9.0</VersionPrefix>
|
||||
<RCNumber>1</RCNumber>
|
||||
<DateSuffix>260528</DateSuffix>
|
||||
<DateSuffix>260603</DateSuffix>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' == 'true'">$(VersionPrefix)-rc$(RCNumber)</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).$(DateSuffix).1</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleaseCandidate)' != 'true' AND '$(VersionSuffix)' == ''">$(VersionPrefix)-preview.$(DateSuffix).1</PackageVersion>
|
||||
<PackageVersion Condition="'$(IsReleased)' == 'true'">$(VersionPrefix)</PackageVersion>
|
||||
<GitTag>1.8.0</GitTag>
|
||||
<GitTag>1.9.0</GitTag>
|
||||
|
||||
<Configurations>Debug;Release;Publish</Configurations>
|
||||
<IsPackable>true</IsPackable>
|
||||
|
||||
@@ -10,6 +10,11 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -16,6 +16,11 @@ builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
options.SerializerOptions.TypeInfoResolverChain.Add(SampleJsonSerializerContext.Default));
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -10,6 +10,11 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -27,6 +27,11 @@ builder.Services.ConfigureHttpJsonOptions(options =>
|
||||
options.SerializerOptions.TypeInfoResolverChain.Add(ApprovalJsonContext.Default));
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
app.UseHttpLogging();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -17,6 +17,11 @@ builder.Services.AddAGUI();
|
||||
// Configure to listen on port 8888
|
||||
builder.WebHost.UseUrls("http://localhost:8888");
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"]
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<NoWarn>$(NoWarn);GHCP001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -2,21 +2,22 @@
|
||||
|
||||
// This sample shows how to create a GitHub Copilot agent with shell command permissions.
|
||||
|
||||
using GitHub.Copilot.SDK;
|
||||
using GitHub.Copilot;
|
||||
using GitHub.Copilot.Rpc;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
// Permission handler that prompts the user for approval
|
||||
static Task<PermissionRequestResult> PromptPermission(PermissionRequest request, PermissionInvocation invocation)
|
||||
static Task<PermissionDecision> PromptPermission(PermissionRequest request, PermissionInvocation invocation)
|
||||
{
|
||||
Console.WriteLine($"\n[Permission Request: {request.Kind}]");
|
||||
Console.Write("Approve? (y/n): ");
|
||||
|
||||
string? input = Console.ReadLine()?.Trim().ToUpperInvariant();
|
||||
PermissionRequestResultKind kind = input is "Y" or "YES"
|
||||
? PermissionRequestResultKind.Approved
|
||||
: PermissionRequestResultKind.Rejected;
|
||||
PermissionDecision decision = input is "Y" or "YES"
|
||||
? PermissionDecision.ApproveOnce()
|
||||
: PermissionDecision.Reject();
|
||||
|
||||
return Task.FromResult(new PermissionRequestResult { Kind = kind });
|
||||
return Task.FromResult(decision);
|
||||
}
|
||||
|
||||
// Create and start a Copilot client
|
||||
|
||||
@@ -36,7 +36,7 @@ dotnet run
|
||||
You can customize the agent by providing additional configuration:
|
||||
|
||||
```csharp
|
||||
using GitHub.Copilot.SDK;
|
||||
using GitHub.Copilot;
|
||||
using Microsoft.Agents.AI;
|
||||
|
||||
// Create and start a Copilot client
|
||||
|
||||
@@ -79,8 +79,10 @@ AIAgent agent =
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(deploymentName)
|
||||
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
|
||||
.AsHarnessAgent(new HarnessAgentOptions
|
||||
{
|
||||
MaxContextWindowTokens = MaxContextWindowTokens,
|
||||
MaxOutputTokens = MaxOutputTokens,
|
||||
Name = "ResearchAgent",
|
||||
Description = "A research assistant that plans and executes research tasks.",
|
||||
DisableFileAccess = true, // If enabled, this would allow the agent to read/write files in a working directory
|
||||
|
||||
@@ -44,8 +44,10 @@ AIAgent webSearchAgent =
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(deploymentName)
|
||||
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
|
||||
.AsHarnessAgent(new HarnessAgentOptions
|
||||
{
|
||||
MaxContextWindowTokens = MaxContextWindowTokens,
|
||||
MaxOutputTokens = MaxOutputTokens,
|
||||
Name = "WebSearchAgent",
|
||||
Description = "An agent that can search the web to find information.",
|
||||
OpenTelemetrySourceName = TracingSourceName,
|
||||
@@ -92,8 +94,10 @@ AIAgent parentAgent =
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(deploymentName)
|
||||
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
|
||||
.AsHarnessAgent(new HarnessAgentOptions
|
||||
{
|
||||
MaxContextWindowTokens = MaxContextWindowTokens,
|
||||
MaxOutputTokens = MaxOutputTokens,
|
||||
Name = "StockPriceResearcher",
|
||||
Description = "An agent that researches stock prices using background agents.",
|
||||
OpenTelemetrySourceName = TracingSourceName,
|
||||
|
||||
@@ -68,8 +68,10 @@ AIAgent agent =
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(deploymentName)
|
||||
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
|
||||
.AsHarnessAgent(new HarnessAgentOptions
|
||||
{
|
||||
MaxContextWindowTokens = MaxContextWindowTokens,
|
||||
MaxOutputTokens = MaxOutputTokens,
|
||||
Name = "DataAnalyst",
|
||||
Description = "A data analyst assistant that reads, analyzes, and processes data files.",
|
||||
OpenTelemetrySourceName = TracingSourceName,
|
||||
|
||||
@@ -89,8 +89,10 @@ AIAgent agent =
|
||||
.GetProjectOpenAIClient()
|
||||
.GetResponsesClient()
|
||||
.AsIChatClient(deploymentName)
|
||||
.AsHarnessAgent(MaxContextWindowTokens, MaxOutputTokens, new HarnessAgentOptions
|
||||
.AsHarnessAgent(new HarnessAgentOptions
|
||||
{
|
||||
MaxContextWindowTokens = MaxContextWindowTokens,
|
||||
MaxOutputTokens = MaxOutputTokens,
|
||||
Name = "CodeExecutionAgent",
|
||||
Description = "A technical assistant with sandboxed code execution and skill-based workflows.",
|
||||
OpenTelemetrySourceName = TracingSourceName,
|
||||
|
||||
@@ -50,12 +50,16 @@ internal static partial class WorkflowHelper
|
||||
/// <summary>
|
||||
/// Executor that starts the concurrent processing by sending messages to the agents.
|
||||
/// </summary>
|
||||
private sealed partial class ConcurrentStartExecutor() : Executor("ConcurrentStartExecutor")
|
||||
[SendsMessage(typeof(List<ChatMessage>))]
|
||||
[SendsMessage(typeof(TurnToken))]
|
||||
private sealed partial class ConcurrentStartExecutor()
|
||||
: Executor("ConcurrentStartExecutor", declareCrossRunShareable: true), IResettableExecutor
|
||||
{
|
||||
[MessageHandler]
|
||||
internal ValueTask RouteMessages(List<ChatMessage> messages, IWorkflowContext context, CancellationToken cancellationToken)
|
||||
internal ValueTask RouteMessages(IEnumerable<ChatMessage> messages, IWorkflowContext context, CancellationToken cancellationToken)
|
||||
{
|
||||
return context.SendMessageAsync(messages, cancellationToken: cancellationToken);
|
||||
List<ChatMessage> payload = messages as List<ChatMessage> ?? messages.ToList();
|
||||
return context.SendMessageAsync(payload, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
[MessageHandler]
|
||||
@@ -63,13 +67,16 @@ internal static partial class WorkflowHelper
|
||||
{
|
||||
return context.SendMessageAsync(token, cancellationToken: cancellationToken);
|
||||
}
|
||||
|
||||
public ValueTask ResetAsync() => default;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Executor that aggregates the results from the concurrent agents.
|
||||
/// </summary>
|
||||
[YieldsOutput(typeof(List<ChatMessage>))]
|
||||
private sealed partial class ConcurrentAggregationExecutor() : Executor<List<ChatMessage>>("ConcurrentAggregationExecutor")
|
||||
[YieldsOutput(typeof(string))]
|
||||
private sealed partial class ConcurrentAggregationExecutor() :
|
||||
Executor<List<ChatMessage>>("ConcurrentAggregationExecutor"), IResettableExecutor
|
||||
{
|
||||
private readonly List<ChatMessage> _messages = [];
|
||||
|
||||
@@ -90,5 +97,11 @@ internal static partial class WorkflowHelper
|
||||
await context.YieldOutputAsync(formattedMessages, cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
public ValueTask ResetAsync()
|
||||
{
|
||||
this._messages.Clear();
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>",
|
||||
"REDIS_CONNECTION_STRING": "localhost:6379",
|
||||
"REDIS_STREAM_TTL_MINUTES": "10"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
|
||||
}
|
||||
}
|
||||
@@ -1,10 +0,0 @@
|
||||
{
|
||||
"IsEncrypted": false,
|
||||
"Values": {
|
||||
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
|
||||
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
|
||||
"DURABLE_TASK_SCHEDULER_CONNECTION_STRING": "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None",
|
||||
"AZURE_OPENAI_ENDPOINT": "<AZURE_OPENAI_ENDPOINT>",
|
||||
"AZURE_OPENAI_DEPLOYMENT_NAME": "<AZURE_OPENAI_DEPLOYMENT_NAME>"
|
||||
}
|
||||
}
|
||||
@@ -71,6 +71,34 @@ curl -X POST http://localhost:8088/invocations \
|
||||
-d "Hello from Docker!"
|
||||
```
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-invocations-echo-agent && cd hosted-invocations-echo-agent
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/invocations/Hosted-Invocations-EchoAgent/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-invocations-echo-agent
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `Hosted-Invocations-EchoAgent.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -107,3 +107,29 @@ azd env set SKILL_NAMES "support-style,escalation-policy"
|
||||
The deployed agent's Managed Identity needs **Azure AI User** on the Foundry project to download skills at startup.
|
||||
|
||||
> The `skills/` source folder is **not** deployed to Foundry — only the downloaded skills are used at runtime. The provisioning step must have been run against the same Foundry project before the agent can download the skills.
|
||||
|
||||
### Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-agent-skills && cd hosted-agent-skills
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AgentSkills/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-agent-skills
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
@@ -174,6 +174,34 @@ The model receives the top three search results as additional context and cites
|
||||
|
||||
Replace the seed documents (or point the sample at an existing index with your own content) to ground the agent in your own knowledge base.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-azure-search-rag && cd hosted-azure-search-rag
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-AzureSearchRag/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-azure-search-rag
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedAzureSearchRag.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -104,6 +104,32 @@ curl -X POST http://localhost:8088/responses \
|
||||
-d '{"input": "Hello!", "model": "hosted-chat-client-agent"}'
|
||||
```
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-chat-client-agent && cd hosted-chat-client-agent
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ChatClientAgent/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-chat-client-agent
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor` — it performs a full `dotnet restore` and `dotnet publish` inside the container. See the commented section in `HostedChatClientAgent.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -112,6 +112,34 @@ docker run --rm -p 8088:8088 \
|
||||
|
||||
The bundled `resources/` folder is part of the published output and ships inside the image.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-files && cd hosted-files
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Files/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-files
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If consuming the Agent Framework as a NuGet package, use the standard `Dockerfile` instead of `Dockerfile.contributor` and switch the `ProjectReference` entries in `HostedFiles.csproj` to `PackageReference` (commented section in the csproj).
|
||||
|
||||
@@ -107,6 +107,32 @@ curl -X POST http://localhost:8088/responses \
|
||||
-d '{"input": "Hello!", "model": "<your-agent-name>"}'
|
||||
```
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-foundry-agent && cd hosted-foundry-agent
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-FoundryAgent/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-foundry-agent
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor` — it performs a full `dotnet restore` and `dotnet publish` inside the container. See the commented section in `HostedFoundryAgent.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -108,6 +108,34 @@ The agent has a single tool `GetAvailableHotels` defined as a C# method with `[D
|
||||
|
||||
The tool searches a mock database of 6 Seattle hotels and returns formatted results with name, location, rating, and pricing.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-local-tools && cd hosted-local-tools
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-LocalTools/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-local-tools
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedLocalTools.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="ModelContextProtocol" VersionOverride="1.2.0" />
|
||||
<PackageReference Include="ModelContextProtocol" />
|
||||
<PackageReference Include="DotNetEnv" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -78,6 +78,34 @@ docker run --rm -p 8088:8088 \
|
||||
hosted-mcp-tools
|
||||
```
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir mcp-tools && cd mcp-tools
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-McpTools/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME mcp-tools
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
Use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedMcpTools.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -139,6 +139,34 @@ The script publishes the project, builds the image, runs the container with two
|
||||
`HOSTED_USER_ISOLATION_KEY` values, drives a multi-turn conversation per user, asserts that each
|
||||
user only sees their own memories, and exits non-zero on failure.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-memory-agent && cd hosted-memory-agent
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-MemoryAgent/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-memory-agent
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the
|
||||
|
||||
@@ -104,6 +104,34 @@ docker run --rm -p 8088:8088 \
|
||||
|
||||
Once deployed, telemetry flows to the Application Insights instance attached to your Foundry project. In the Foundry UI, the **Traces** tab next to **Playground** lists conversations and lets you drill into the span tree for any request.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-observability && cd hosted-observability
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Observability/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-observability
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If consuming the Agent Framework as a NuGet package, use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedObservability.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -111,6 +111,34 @@ The `TextSearchProvider` runs a mock search **before each model invocation**:
|
||||
|
||||
The model receives the search results as additional context and cites the source in its response. In production, replace `MockSearchAsync` with a call to Azure AI Search or your preferred search provider.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-text-rag && cd hosted-text-rag
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-TextRag/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-text-rag
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedTextRag.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
AZURE_AI_PROJECT_ENDPOINT=<your-azure-ai-project-endpoint>
|
||||
ASPNETCORE_URLS=http://+:8088
|
||||
ASPNETCORE_ENVIRONMENT=Development
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-5
|
||||
FOUNDRY_TOOLBOX_NAME=<your-toolbox-name>
|
||||
AZURE_BEARER_TOKEN=DefaultAzureCredential
|
||||
@@ -0,0 +1,26 @@
|
||||
# Dockerfile for end-users consuming the Agent Framework via NuGet packages.
|
||||
#
|
||||
# This Dockerfile performs a full `dotnet restore` and `dotnet publish` inside the container,
|
||||
# which only succeeds when the project references its dependencies via PackageReference (see the
|
||||
# commented-out section in HostedToolboxMcpSkills.csproj). Contributors building from the
|
||||
# agent-framework repository source must use Dockerfile.contributor instead because
|
||||
# ProjectReference dependencies live outside this folder and cannot be restored from inside
|
||||
# this build context.
|
||||
#
|
||||
# Use the official .NET 10.0 ASP.NET runtime as a parent image
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS base
|
||||
WORKDIR /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build
|
||||
WORKDIR /src
|
||||
COPY . .
|
||||
RUN dotnet restore
|
||||
RUN dotnet publish -c Release -o /app/publish
|
||||
|
||||
# Final stage
|
||||
FROM base AS final
|
||||
WORKDIR /app
|
||||
COPY --from=build /app/publish .
|
||||
EXPOSE 8088
|
||||
ENV ASPNETCORE_URLS=http://+:8088
|
||||
ENTRYPOINT ["dotnet", "HostedToolboxMcpSkills.dll"]
|
||||
@@ -0,0 +1,18 @@
|
||||
# Dockerfile for contributors building from the agent-framework repository source.
|
||||
#
|
||||
# This project uses ProjectReference to the local source, which means a standard
|
||||
# multi-stage Docker build cannot resolve dependencies outside this folder.
|
||||
# Pre-publish the app targeting the container runtime and copy the output:
|
||||
#
|
||||
# dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
|
||||
# docker build -f Dockerfile.contributor -t hosted-toolbox-mcp-skills .
|
||||
# docker run --rm -p 8088:8088 -e AGENT_NAME=hosted-toolbox-mcp-skills -e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN --env-file .env hosted-toolbox-mcp-skills
|
||||
#
|
||||
# For end-users consuming the NuGet package (not ProjectReference), use the standard
|
||||
# Dockerfile which performs a full dotnet restore + publish inside the container.
|
||||
FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
|
||||
WORKDIR /app
|
||||
COPY out/ .
|
||||
EXPOSE 8088
|
||||
ENV ASPNETCORE_URLS=http://+:8088
|
||||
ENTRYPOINT ["dotnet", "HostedToolboxMcpSkills.dll"]
|
||||
@@ -0,0 +1,36 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net10.0</TargetFrameworks>
|
||||
<Nullable>enable</Nullable>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<CentralPackageTransitivePinningEnabled>false</CentralPackageTransitivePinningEnabled>
|
||||
<RootNamespace>HostedToolboxMcpSkills</RootNamespace>
|
||||
<AssemblyName>HostedToolboxMcpSkills</AssemblyName>
|
||||
<NoWarn>$(NoWarn);</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Identity" />
|
||||
<PackageReference Include="ModelContextProtocol" VersionOverride="1.2.0" />
|
||||
<PackageReference Include="DotNetEnv" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- For contributors: uses ProjectReference to build against local source -->
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry\Microsoft.Agents.AI.Foundry.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Foundry.Hosting\Microsoft.Agents.AI.Foundry.Hosting.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\..\src\Microsoft.Agents.AI.Mcp\Microsoft.Agents.AI.Mcp.csproj" />
|
||||
<ProjectReference Include="..\Hosted_Shared_Contributor_Setup\Hosted_Shared_Contributor_Setup.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- For end-users: uncomment the PackageReference below and remove the ProjectReference above
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Agents.AI.Foundry" Version="1.6.1-preview.260514.1" />
|
||||
<PackageReference Include="Microsoft.Agents.AI.Foundry.Hosting" Version="1.6.1-preview.260514.1" />
|
||||
<PackageReference Include="Microsoft.Agents.AI.Mcp" Version="1.6.1-preview.260514.1" />
|
||||
</ItemGroup>
|
||||
-->
|
||||
|
||||
</Project>
|
||||
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
// Hosted Toolbox MCP Skills Agent
|
||||
//
|
||||
// Demonstrates how to host an agent that discovers MCP-based skills from a
|
||||
// Foundry Toolbox MCP endpoint and injects them as AIContextProviders using
|
||||
// AgentSkillsProviderBuilder.UseMcpSkills().
|
||||
//
|
||||
// Required environment variables:
|
||||
// AZURE_AI_PROJECT_ENDPOINT - Azure AI Foundry project endpoint
|
||||
// FOUNDRY_TOOLBOX_NAME - Name of the Foundry Toolbox to connect to
|
||||
// AZURE_AI_MODEL_DEPLOYMENT_NAME - Model deployment name (default: gpt-5)
|
||||
|
||||
using System.Net.Http.Headers;
|
||||
using Azure.AI.Projects;
|
||||
using Azure.Core;
|
||||
using Azure.Identity;
|
||||
using DotNetEnv;
|
||||
using Hosted_Shared_Contributor_Setup;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Agents.AI.Foundry.Hosting;
|
||||
using ModelContextProtocol.Client;
|
||||
|
||||
// Load .env file if present (for local development)
|
||||
Env.TraversePath().Load();
|
||||
|
||||
var projectEndpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")
|
||||
?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
|
||||
var deployment = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5";
|
||||
var toolboxName = Environment.GetEnvironmentVariable("FOUNDRY_TOOLBOX_NAME")
|
||||
?? throw new InvalidOperationException("FOUNDRY_TOOLBOX_NAME is not set.");
|
||||
|
||||
// Build the Toolbox MCP URL from the project endpoint and toolbox name.
|
||||
var toolboxMcpServerUrl = $"{projectEndpoint.TrimEnd('/')}/toolboxes/{toolboxName}/mcp?api-version=v1";
|
||||
|
||||
// Use a chained credential: try a temporary dev token first (for local Docker debugging),
|
||||
// then fall back to DefaultAzureCredential (for local dev via dotnet run / managed identity in production).
|
||||
TokenCredential credential = new ChainedTokenCredential(
|
||||
new DevTemporaryTokenCredential(),
|
||||
new DefaultAzureCredential());
|
||||
|
||||
// ── Connect to the Foundry Toolbox MCP endpoint ─────────────────────────────
|
||||
// Create an HttpClient that attaches a fresh Foundry bearer token to every request.
|
||||
using var httpClient = new HttpClient(new BearerTokenHandler(credential, "https://ai.azure.com/.default") { CheckCertificateRevocationList = true });
|
||||
|
||||
Console.WriteLine($"Connecting to Foundry Toolbox '{toolboxName}' MCP server...");
|
||||
|
||||
await using var mcpClient = await McpClient.CreateAsync(
|
||||
new HttpClientTransport(
|
||||
new HttpClientTransportOptions
|
||||
{
|
||||
Endpoint = new Uri(toolboxMcpServerUrl),
|
||||
Name = toolboxName,
|
||||
TransportMode = HttpTransportMode.StreamableHttp,
|
||||
AdditionalHeaders = new Dictionary<string, string>
|
||||
{
|
||||
["Foundry-Features"] = "Toolboxes=V1Preview",
|
||||
},
|
||||
},
|
||||
httpClient));
|
||||
|
||||
// ── Configure MCP-based skills provider ──────────────────────────────────────
|
||||
var skillsProvider = new AgentSkillsProviderBuilder()
|
||||
.UseMcpSkills(mcpClient)
|
||||
.Build();
|
||||
|
||||
// ── Create the agent ─────────────────────────────────────────────────────────
|
||||
AIAgent agent = new AIProjectClient(new Uri(projectEndpoint), credential)
|
||||
.AsAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Name = Environment.GetEnvironmentVariable("AGENT_NAME") ?? "hosted-toolbox-mcp-skills",
|
||||
Description = "Hosted agent with MCP skills discovered from a Foundry Toolbox",
|
||||
ChatOptions = new()
|
||||
{
|
||||
ModelId = deployment,
|
||||
Instructions = "You are a helpful assistant.",
|
||||
},
|
||||
AIContextProviders = [skillsProvider],
|
||||
});
|
||||
|
||||
// ── Build the host ───────────────────────────────────────────────────────────
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddFoundryResponses(agent);
|
||||
builder.Services.AddDevTemporaryLocalContributorSetup(); // Local Docker debugging only - must not be used in production.
|
||||
|
||||
var app = builder.Build();
|
||||
app.MapFoundryResponses();
|
||||
|
||||
// Contributor-only: in Development, also map the per-agent OpenAI route shape that live Foundry uses
|
||||
// so a local REPL client can target this server via AIProjectClient.AsAIAgent(Uri agentEndpoint).
|
||||
// Do not use this in production. Hosted Foundry agents only support the agent-endpoint path.
|
||||
app.MapDevTemporaryLocalAgentEndpoint();
|
||||
|
||||
app.Run();
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// HttpClientHandler: attaches a fresh Foundry bearer token to every request
|
||||
// ---------------------------------------------------------------------------
|
||||
internal sealed class BearerTokenHandler(TokenCredential credential, string scope) : HttpClientHandler
|
||||
{
|
||||
private readonly TokenRequestContext _tokenContext = new([scope]);
|
||||
|
||||
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
AccessToken token = await credential.GetTokenAsync(this._tokenContext, cancellationToken).ConfigureAwait(false);
|
||||
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", token.Token);
|
||||
return await base.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,131 @@
|
||||
# Hosted-ToolboxMcpSkills
|
||||
|
||||
A hosted agent that discovers **MCP-based skills from a Foundry Toolbox** and makes them available to the agent using `AgentSkillsProviderBuilder.UseMcpSkills(mcpClient)`.
|
||||
|
||||
The `AgentSkillsProvider` is attached to the agent as a context provider and implements the [Agent Skills](https://agentskills.io/) progressive-disclosure pattern. When the agent is prompted, it discovers available skills in the Foundry Toolbox via the provider:
|
||||
|
||||
1. **Advertise** - skill names and descriptions are injected into the system prompt so the agent knows what is available.
|
||||
2. **Load** - when the agent decides a skill is relevant, it retrieves the full skill body with detailed instructions via the provider.
|
||||
3. **Read resources** - if a skill includes supplementary content (reference documents, assets), the agent reads them on demand via the provider.
|
||||
|
||||
This way the full skill body and resources are only loaded when the agent actually needs them, reducing token usage.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
|
||||
- An Azure AI Foundry project with a deployed model (e.g., `gpt-5`)
|
||||
- A Foundry Toolbox already configured with skills provisioned
|
||||
- Azure CLI logged in (`az login`)
|
||||
|
||||
## Configuration
|
||||
|
||||
Copy the template and fill in your values:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
Edit `.env` and set your Azure AI Foundry project endpoint and toolbox name:
|
||||
|
||||
```env
|
||||
AZURE_AI_PROJECT_ENDPOINT=https://<your-account>.services.ai.azure.com/api/projects/<your-project>
|
||||
ASPNETCORE_URLS=http://+:8088
|
||||
ASPNETCORE_ENVIRONMENT=Development
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-5
|
||||
FOUNDRY_TOOLBOX_NAME=my-toolbox
|
||||
```
|
||||
|
||||
> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.
|
||||
|
||||
## Running directly (contributors)
|
||||
|
||||
This project uses `ProjectReference` to build against the local Agent Framework source.
|
||||
|
||||
```bash
|
||||
cd dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills
|
||||
dotnet run
|
||||
```
|
||||
|
||||
The agent will start on `http://localhost:8088`.
|
||||
|
||||
### Test it
|
||||
|
||||
Using the Azure Developer CLI:
|
||||
|
||||
```bash
|
||||
azd ai agent invoke --local "What skills do you have available?"
|
||||
```
|
||||
|
||||
## Running with Docker
|
||||
|
||||
Since this project uses `ProjectReference`, use `Dockerfile.contributor` which takes a pre-published output.
|
||||
|
||||
### 1. Publish for the container runtime (Linux Alpine)
|
||||
|
||||
```bash
|
||||
dotnet publish -c Debug -f net10.0 -r linux-musl-x64 --self-contained false -o out
|
||||
```
|
||||
|
||||
### 2. Build the Docker image
|
||||
|
||||
```bash
|
||||
docker build -f Dockerfile.contributor -t hosted-toolbox-mcp-skills .
|
||||
```
|
||||
|
||||
### 3. Run the container
|
||||
|
||||
Generate a bearer token on your host and pass it to the container:
|
||||
|
||||
```bash
|
||||
# Generate token (expires in ~1 hour)
|
||||
export AZURE_BEARER_TOKEN=$(az account get-access-token --resource https://ai.azure.com --query accessToken -o tsv)
|
||||
|
||||
# Run with token
|
||||
docker run --rm -p 8088:8088 \
|
||||
-e AGENT_NAME=hosted-toolbox-mcp-skills \
|
||||
-e AZURE_BEARER_TOKEN=$AZURE_BEARER_TOKEN \
|
||||
--env-file .env \
|
||||
hosted-toolbox-mcp-skills
|
||||
```
|
||||
|
||||
> **Note:** `AGENT_NAME` is passed via `-e` to simulate the platform injection. `AZURE_BEARER_TOKEN` provides Azure credentials to the container (tokens expire after ~1 hour). The `.env` file provides the remaining configuration.
|
||||
|
||||
### 4. Test it
|
||||
|
||||
Using the Azure Developer CLI:
|
||||
|
||||
```bash
|
||||
azd ai agent invoke --local "What skills do you have available?"
|
||||
```
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-toolbox-mcp-skills && cd hosted-toolbox-mcp-skills
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-ToolboxMcpSkills/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-toolbox-mcp-skills
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-5
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
If you are consuming the Agent Framework as a NuGet package (not building from source), use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedToolboxMcpSkills.csproj` for the `PackageReference` alternative.
|
||||
@@ -0,0 +1,43 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/AgentManifest.yaml
|
||||
name: hosted-toolbox-mcp-skills
|
||||
displayName: "Hosted Toolbox MCP Skills Agent"
|
||||
|
||||
description: >
|
||||
A hosted agent that discovers MCP-based skills from a Foundry Toolbox
|
||||
and makes them available to the agent via the agent skills provider.
|
||||
|
||||
metadata:
|
||||
tags:
|
||||
- AI Agent Hosting
|
||||
- Azure AI AgentServer
|
||||
- Responses Protocol
|
||||
- Agent Framework
|
||||
- MCP
|
||||
- Model Context Protocol
|
||||
- Agent Skills
|
||||
- Foundry Toolbox
|
||||
- Foundry Toolbox Skills
|
||||
|
||||
template:
|
||||
name: hosted-toolbox-mcp-skills
|
||||
kind: hosted
|
||||
protocols:
|
||||
- protocol: responses
|
||||
version: 1.0.0
|
||||
resources:
|
||||
cpu: "0.25"
|
||||
memory: 0.5Gi
|
||||
environment_variables:
|
||||
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
value: "{{AZURE_AI_MODEL_DEPLOYMENT_NAME}}"
|
||||
- name: FOUNDRY_TOOLBOX_NAME
|
||||
value: "{{FOUNDRY_TOOLBOX_NAME}}"
|
||||
parameters:
|
||||
properties:
|
||||
- name: FOUNDRY_TOOLBOX_NAME
|
||||
secret: false
|
||||
description: Name of the Foundry Toolbox to connect to for MCP skill discovery
|
||||
resources:
|
||||
- kind: model
|
||||
id: gpt-5
|
||||
name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
@@ -0,0 +1,14 @@
|
||||
# yaml-language-server: $schema=https://raw.githubusercontent.com/microsoft/AgentSchema/refs/heads/main/schemas/v1.0/ContainerAgent.yaml
|
||||
kind: hosted
|
||||
name: hosted-toolbox-mcp-skills
|
||||
protocols:
|
||||
- protocol: responses
|
||||
version: 1.0.0
|
||||
resources:
|
||||
cpu: "0.25"
|
||||
memory: 0.5Gi
|
||||
environment_variables:
|
||||
- name: AZURE_AI_MODEL_DEPLOYMENT_NAME
|
||||
value: ${AZURE_AI_MODEL_DEPLOYMENT_NAME}
|
||||
- name: FOUNDRY_TOOLBOX_NAME
|
||||
value: ${FOUNDRY_TOOLBOX_NAME}
|
||||
@@ -121,6 +121,34 @@ User message
|
||||
|
||||
The triage agent receives every message and hands off to the appropriate specialist. Specialists route back to the triage agent after responding, allowing for multi-turn conversations.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir triage-workflow && cd triage-workflow
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Handoff/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME triage-workflow
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME gpt-4o
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
Use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedWorkflowHandoff.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -5,7 +5,7 @@ A hosted agent that demonstrates **multi-agent workflow orchestration**. Three t
|
||||
## Prerequisites
|
||||
|
||||
- [.NET 10 SDK](https://dotnet.microsoft.com/download/dotnet/10.0)
|
||||
- An Azure AI Foundry project with a deployed model (e.g., `gpt-4o`)
|
||||
- An Azure AI Foundry project with a deployed model (e.g., `hosted-workflow-simple`)
|
||||
- Azure CLI logged in (`az login`)
|
||||
|
||||
## Configuration
|
||||
@@ -22,7 +22,7 @@ Edit `.env` and set your Azure AI Foundry project endpoint:
|
||||
AZURE_AI_PROJECT_ENDPOINT=https://<your-account>.services.ai.azure.com/api/projects/<your-project>
|
||||
ASPNETCORE_URLS=http://+:8088
|
||||
ASPNETCORE_ENVIRONMENT=Development
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME=gpt-4o
|
||||
AZURE_AI_MODEL_DEPLOYMENT_NAME=hosted-workflow-simple
|
||||
```
|
||||
|
||||
> **Note:** `.env` is gitignored. The `.env.example` template is checked in as a reference.
|
||||
@@ -104,6 +104,34 @@ Input text
|
||||
|
||||
Each agent in the chain receives the output of the previous agent. The final result demonstrates how meaning is preserved (or subtly shifted) through multiple translation hops.
|
||||
|
||||
## Deploying to Foundry (azd spec)
|
||||
|
||||
This sample includes an `azd` manifest (`agent.manifest.yaml`) and hosted agent spec (`agent.yaml`) for deployment to Foundry.
|
||||
|
||||
Initialize an `azd` project from this sample's manifest:
|
||||
|
||||
```bash
|
||||
mkdir hosted-workflows && cd hosted-workflows
|
||||
azd ai agent init -m https://github.com/microsoft/agent-framework/blob/main/dotnet/samples/04-hosting/FoundryHostedAgents/responses/Hosted-Workflow-Simple/agent.manifest.yaml
|
||||
```
|
||||
|
||||
Then deploy:
|
||||
|
||||
```bash
|
||||
azd deploy
|
||||
```
|
||||
|
||||
If you need to override defaults, set deployment-time environment variables in the `azd` environment before deploying:
|
||||
|
||||
```bash
|
||||
azd env set AGENT_NAME hosted-workflow-simple
|
||||
azd env set AZURE_AI_MODEL_DEPLOYMENT_NAME hosted-workflow-simple
|
||||
```
|
||||
|
||||
For end-to-end hosted agent deployment guidance, see the [official deployment guide](https://learn.microsoft.com/en-us/azure/foundry/agents/how-to/deploy-hosted-agent).
|
||||
|
||||
---
|
||||
|
||||
## NuGet package users
|
||||
|
||||
Use the standard `Dockerfile` instead of `Dockerfile.contributor`. See the commented section in `HostedWorkflowSimple.csproj` for the `PackageReference` alternative.
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -19,6 +19,11 @@ builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.ConfigureHttpJsonOptions(options => options.SerializerOptions.TypeInfoResolverChain.Add(AGUIDojoServerSerializerContext.Default));
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
app.UseHttpLogging();
|
||||
|
||||
@@ -49,8 +49,9 @@ var agent = new AzureOpenAIClient(
|
||||
AGUIServerSerializerContext.Default.Options)
|
||||
]);
|
||||
|
||||
// When running in production, make sure to use an SessionIsolationKeyProvider, e.g. ClaimsIdentity-based
|
||||
// if using Claims-based Identity for Authentication/Authorization
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
// Register the agent with the host and configure it to use an in-memory session store
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Hosting.AspNetCore\Microsoft.Agents.AI.Hosting.AspNetCore.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
|
||||
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -12,6 +12,11 @@ WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
|
||||
builder.Services.AddHttpClient().AddLogging();
|
||||
builder.Services.AddAGUI();
|
||||
|
||||
// WARNING: When adding session persistence (e.g., WithInMemorySessionStore), or running in production,
|
||||
// make sure to also register a SessionIsolationKeyProvider to scope sessions by principal in multi-user
|
||||
// deployments, e.g.:
|
||||
// builder.Services.UseClaimsBasedSessionIsolation(new() { ClaimType = ClaimTypes.NameIdentifier });
|
||||
|
||||
WebApplication app = builder.Build();
|
||||
|
||||
string endpoint = builder.Configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
|
||||
|
||||
@@ -44,18 +44,33 @@ public static class HostedFoundryMemoryProviderScopes
|
||||
session => new FoundryMemoryProvider.State(new FoundryMemoryProviderScope(GetRequiredHostedContext(session).ChatId));
|
||||
|
||||
/// <summary>
|
||||
/// Returns a <c>stateInitializer</c> that scopes memories per (user, chat) pair, using
|
||||
/// <c>"{UserId}:{ChatId}"</c> as the partition key. Use this when memories should be visible
|
||||
/// only to the same user within the same conversation.
|
||||
/// Returns a <c>stateInitializer</c> that scopes memories per (user, chat) pair, composing
|
||||
/// <see cref="HostedSessionContext.UserId"/> and <see cref="HostedSessionContext.ChatId"/> into a
|
||||
/// single delimiter-safe partition key. Use this when memories should be visible only to the same
|
||||
/// user within the same conversation.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Both identity values are opaque strings that may contain any characters, including the <c>:</c>
|
||||
/// delimiter. To keep the composite key injective (so two distinct (user, chat) pairs can never
|
||||
/// collide), each part is escaped (<c>\</c> becomes <c>\\</c>, then <c>:</c> becomes <c>\:</c>) before
|
||||
/// being joined with a <c>::</c> separator.
|
||||
/// </remarks>
|
||||
/// <returns>A delegate suitable for the <c>stateInitializer</c> argument of <see cref="FoundryMemoryProvider"/>.</returns>
|
||||
public static Func<AgentSession?, FoundryMemoryProvider.State> PerUserAndChat() =>
|
||||
session =>
|
||||
{
|
||||
var ctx = GetRequiredHostedContext(session);
|
||||
return new FoundryMemoryProvider.State(new FoundryMemoryProviderScope($"{ctx.UserId}:{ctx.ChatId}"));
|
||||
return new FoundryMemoryProvider.State(
|
||||
new FoundryMemoryProviderScope($"{EscapeScopePart(ctx.UserId)}::{EscapeScopePart(ctx.ChatId)}"));
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Escapes special characters in a scope part so that distinct (user, chat) pairs produce distinct
|
||||
/// composite scope keys. Backslashes are escaped first (<c>\</c> becomes <c>\\</c>), then colons
|
||||
/// (<c>:</c> becomes <c>\:</c>), ensuring the <c>{user}::{chat}</c> format is unambiguous.
|
||||
/// </summary>
|
||||
private static string EscapeScopePart(string part) => part.Replace("\\", "\\\\").Replace(":", "\\:");
|
||||
|
||||
private static HostedSessionContext GetRequiredHostedContext(AgentSession? session) =>
|
||||
session?.GetHostedContext()
|
||||
?? throw new InvalidOperationException(
|
||||
|
||||
@@ -281,14 +281,19 @@ internal static class OutputConverter
|
||||
|
||||
var outputText = EncodeFunctionResultAsJsonStringPayload(functionResult.Result);
|
||||
|
||||
var itemId = GenerateItemId("fc");
|
||||
var outputItem = new OutputItemFunctionToolCallOutput(
|
||||
// Use the SDK's convenience method so the OutputItemFunctionToolCallOutput
|
||||
// is constructed with a populated Id. The public OutputItemFunctionToolCallOutput
|
||||
// ctor only sets CallId/Output (Id is read-only), and AddOutputItem<T>+EmitAdded
|
||||
// does not auto-stamp Id — only ResponseId/AgentReference. Without this, the
|
||||
// serialized item arrives at the Foundry storage layer with id=null and is
|
||||
// rejected with "ID cannot be null or empty (Parameter 'id')".
|
||||
foreach (var evt in stream.OutputItemFunctionCallOutput(
|
||||
functionResult.CallId,
|
||||
BinaryData.FromString(outputText));
|
||||
BinaryData.FromString(outputText)))
|
||||
{
|
||||
yield return evt;
|
||||
}
|
||||
|
||||
var outputBuilder = stream.AddOutputItem<OutputItemFunctionToolCallOutput>(itemId);
|
||||
yield return outputBuilder.EmitAdded(outputItem);
|
||||
yield return outputBuilder.EmitDone(outputItem);
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
@@ -24,11 +24,13 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Azure.AI.Projects" />
|
||||
<PackageReference Include="Azure.Core" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.Abstractions" />
|
||||
<PackageReference Include="Microsoft.Extensions.AI.OpenAI" />
|
||||
<PackageReference Include="Microsoft.Extensions.Compliance.Abstractions" />
|
||||
<PackageReference Include="OpenAI" />
|
||||
<PackageReference Include="System.ClientModel" />
|
||||
</ItemGroup>
|
||||
|
||||
<!-- Evaluation support requires net8.0+ (MEAI.Evaluation does not support legacy TFMs) -->
|
||||
|
||||
@@ -6,7 +6,7 @@ using Microsoft.Agents.AI.GitHub.Copilot;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
namespace GitHub.Copilot.SDK;
|
||||
namespace GitHub.Copilot;
|
||||
|
||||
/// <summary>
|
||||
/// Provides extension methods for <see cref="CopilotClient"/>
|
||||
|
||||
@@ -9,7 +9,7 @@ using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using GitHub.Copilot.SDK;
|
||||
using GitHub.Copilot;
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
@@ -169,7 +169,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
Channel<AgentResponseUpdate> channel = Channel.CreateUnbounded<AgentResponseUpdate>();
|
||||
|
||||
// Subscribe to session events
|
||||
using IDisposable subscription = copilotSession.On(evt =>
|
||||
using IDisposable subscription = copilotSession.On<SessionEvent>(evt =>
|
||||
{
|
||||
switch (evt)
|
||||
{
|
||||
@@ -210,7 +210,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
string prompt = string.Join("\n", messages.Select(m => m.Text));
|
||||
|
||||
// Handle DataContent as attachments
|
||||
(List<UserMessageAttachmentFile>? attachments, tempDir) = await ProcessDataContentAttachmentsAsync(
|
||||
(List<AttachmentFile>? attachments, tempDir) = await ProcessDataContentAttachmentsAsync(
|
||||
messages,
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -262,10 +262,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
|
||||
private async Task EnsureClientStartedAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
if (this._copilotClient.State != ConnectionState.Connected)
|
||||
{
|
||||
await this._copilotClient.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
await this._copilotClient.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private ResumeSessionConfig CreateResumeConfig()
|
||||
@@ -275,36 +272,18 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
|
||||
/// <summary>
|
||||
/// Copies all supported properties from a source <see cref="SessionConfig"/> into a new instance
|
||||
/// with <see cref="SessionConfig.Streaming"/> set to <c>true</c>.
|
||||
/// with <see cref="SessionConfigBase.Streaming"/> set to <c>true</c>.
|
||||
/// </summary>
|
||||
internal static SessionConfig CopySessionConfig(SessionConfig source)
|
||||
{
|
||||
return new SessionConfig
|
||||
{
|
||||
Model = source.Model,
|
||||
ReasoningEffort = source.ReasoningEffort,
|
||||
Tools = source.Tools,
|
||||
SystemMessage = source.SystemMessage,
|
||||
AvailableTools = source.AvailableTools,
|
||||
ExcludedTools = source.ExcludedTools,
|
||||
Provider = source.Provider,
|
||||
OnPermissionRequest = source.OnPermissionRequest,
|
||||
OnUserInputRequest = source.OnUserInputRequest,
|
||||
Hooks = source.Hooks,
|
||||
WorkingDirectory = source.WorkingDirectory,
|
||||
ConfigDir = source.ConfigDir,
|
||||
McpServers = source.McpServers,
|
||||
CustomAgents = source.CustomAgents,
|
||||
SkillDirectories = source.SkillDirectories,
|
||||
DisabledSkills = source.DisabledSkills,
|
||||
InfiniteSessions = source.InfiniteSessions,
|
||||
Streaming = true
|
||||
};
|
||||
SessionConfig copy = source.Clone();
|
||||
copy.Streaming = true;
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Copies all supported properties from a source <see cref="SessionConfig"/> into a new
|
||||
/// <see cref="ResumeSessionConfig"/> with <see cref="ResumeSessionConfig.Streaming"/> set to <c>true</c>.
|
||||
/// <see cref="ResumeSessionConfig"/> with <see cref="SessionConfigBase.Streaming"/> set to <c>true</c>.
|
||||
/// </summary>
|
||||
internal static ResumeSessionConfig CopyResumeSessionConfig(SessionConfig? source)
|
||||
{
|
||||
@@ -321,7 +300,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
OnUserInputRequest = source?.OnUserInputRequest,
|
||||
Hooks = source?.Hooks,
|
||||
WorkingDirectory = source?.WorkingDirectory,
|
||||
ConfigDir = source?.ConfigDir,
|
||||
ConfigDirectory = source?.ConfigDirectory,
|
||||
McpServers = source?.McpServers,
|
||||
CustomAgents = source?.CustomAgents,
|
||||
SkillDirectories = source?.SkillDirectories,
|
||||
@@ -394,10 +373,10 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
|
||||
AdditionalPropertiesDictionary<long>? additionalCounts = null;
|
||||
|
||||
if (usageEvent.Data.CacheWriteTokens is double cacheWriteTokens)
|
||||
if (usageEvent.Data.CacheWriteTokens is long cacheWriteTokens)
|
||||
{
|
||||
additionalCounts ??= [];
|
||||
additionalCounts[nameof(AssistantUsageData.CacheWriteTokens)] = (long)cacheWriteTokens;
|
||||
additionalCounts[nameof(AssistantUsageData.CacheWriteTokens)] = cacheWriteTokens;
|
||||
}
|
||||
|
||||
if (usageEvent.Data.Cost is double cost)
|
||||
@@ -406,10 +385,10 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
additionalCounts[nameof(AssistantUsageData.Cost)] = (long)cost;
|
||||
}
|
||||
|
||||
if (usageEvent.Data.Duration is double duration)
|
||||
if (usageEvent.Data.Duration is TimeSpan duration)
|
||||
{
|
||||
additionalCounts ??= [];
|
||||
additionalCounts[nameof(AssistantUsageData.Duration)] = (long)duration;
|
||||
additionalCounts[nameof(AssistantUsageData.Duration)] = (long)duration.TotalMilliseconds;
|
||||
}
|
||||
|
||||
return additionalCounts;
|
||||
@@ -432,7 +411,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
|
||||
private static SessionConfig? GetSessionConfig(IList<AITool>? tools, string? instructions)
|
||||
{
|
||||
List<AIFunction>? mappedTools = tools is { Count: > 0 } ? tools.OfType<AIFunction>().ToList() : null;
|
||||
List<AIFunctionDeclaration>? mappedTools = tools is { Count: > 0 } ? tools.OfType<AIFunctionDeclaration>().ToList() : null;
|
||||
SystemMessageConfig? systemMessage = instructions is not null ? new SystemMessageConfig { Mode = SystemMessageMode.Append, Content = instructions } : null;
|
||||
|
||||
if (mappedTools is null && systemMessage is null)
|
||||
@@ -443,11 +422,11 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
return new SessionConfig { Tools = mappedTools, SystemMessage = systemMessage };
|
||||
}
|
||||
|
||||
private static async Task<(List<UserMessageAttachmentFile>? Attachments, string? TempDir)> ProcessDataContentAttachmentsAsync(
|
||||
private static async Task<(List<AttachmentFile>? Attachments, string? TempDir)> ProcessDataContentAttachmentsAsync(
|
||||
IEnumerable<ChatMessage> messages,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<UserMessageAttachmentFile>? attachments = null;
|
||||
List<AttachmentFile>? attachments = null;
|
||||
string? tempDir = null;
|
||||
foreach (ChatMessage message in messages)
|
||||
{
|
||||
@@ -461,7 +440,7 @@ public sealed class GitHubCopilotAgent : AIAgent, IAsyncDisposable
|
||||
string tempFilePath = await dataContent.SaveToAsync(tempDir, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
attachments ??= [];
|
||||
attachments.Add(new UserMessageAttachmentFile
|
||||
attachments.Add(new AttachmentFile
|
||||
{
|
||||
Path = tempFilePath,
|
||||
DisplayName = Path.GetFileName(tempFilePath)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
<VersionSuffix>preview</VersionSuffix>
|
||||
<!-- GitHub.Copilot.SDK only supports .NET 8.0+ -->
|
||||
<TargetFrameworks>$(TargetFrameworksCore)</TargetFrameworks>
|
||||
<NoWarn>$(NoWarn);GHCP001</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Agents.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
|
||||
namespace Microsoft.Extensions.AI;
|
||||
@@ -14,29 +16,28 @@ public static class ChatClientHarnessExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="HarnessAgent"/> that wraps this <see cref="IChatClient"/> with a pre-configured
|
||||
/// pipeline including function invocation, per-service-call chat history persistence, and in-loop compaction.
|
||||
/// pipeline including function invocation, per-service-call chat history persistence, optional in-loop compaction, and a rich set
|
||||
/// of default context providers and agent decorators.
|
||||
/// </summary>
|
||||
/// <param name="chatClient">
|
||||
/// The <see cref="IChatClient"/> that provides access to the underlying AI model.
|
||||
/// </param>
|
||||
/// <param name="maxContextWindowTokens">
|
||||
/// The maximum number of tokens the model's context window supports (e.g., 1,050,000 for gpt-5.4).
|
||||
/// Used to configure the compaction strategy.
|
||||
/// </param>
|
||||
/// <param name="maxOutputTokens">
|
||||
/// The maximum number of output tokens the model can generate per response (e.g., 128,000 for gpt-5.4).
|
||||
/// Used to configure the compaction strategy.
|
||||
/// </param>
|
||||
/// <param name="options">
|
||||
/// Optional configuration options for the agent, including instructions override, tools,
|
||||
/// additional context providers, and chat history provider.
|
||||
/// When <see langword="null"/>, the agent uses built-in default settings.
|
||||
/// additional context providers, chat history provider, and compaction settings.
|
||||
/// When <see langword="null"/>, the agent uses built-in default settings with compaction disabled.
|
||||
/// </param>
|
||||
/// <param name="loggerFactory">
|
||||
/// Optional logger factory for creating loggers used by the agent and its components.
|
||||
/// </param>
|
||||
/// <param name="services">
|
||||
/// Optional service provider for resolving dependencies required by AI functions and other agent components.
|
||||
/// </param>
|
||||
/// <returns>A new <see cref="HarnessAgent"/> instance.</returns>
|
||||
public static HarnessAgent AsHarnessAgent(
|
||||
this IChatClient chatClient,
|
||||
int maxContextWindowTokens,
|
||||
int maxOutputTokens,
|
||||
HarnessAgentOptions? options = null) =>
|
||||
new(chatClient, maxContextWindowTokens, maxOutputTokens, options);
|
||||
HarnessAgentOptions? options = null,
|
||||
ILoggerFactory? loggerFactory = null,
|
||||
IServiceProvider? services = null) =>
|
||||
new(chatClient, options, loggerFactory, services);
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using Microsoft.Agents.AI.Compaction;
|
||||
using Microsoft.Agents.AI.Tools.Shell;
|
||||
#endif
|
||||
using Microsoft.Extensions.AI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Shared.DiagnosticIds;
|
||||
using Microsoft.Shared.Diagnostics;
|
||||
|
||||
@@ -17,50 +18,65 @@ namespace Microsoft.Agents.AI;
|
||||
|
||||
/// <summary>
|
||||
/// A pre-configured <see cref="DelegatingAIAgent"/> that wraps a <see cref="ChatClientAgent"/> with
|
||||
/// function invocation, per-service-call chat history persistence, in-loop compaction, and a rich set
|
||||
/// function invocation, per-service-call chat history persistence, optional in-loop compaction, and a rich set
|
||||
/// of default context providers and agent decorators.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="HarnessAgent"/> assembles the following pipeline from a caller-supplied <see cref="IChatClient"/>:
|
||||
/// <see cref="HarnessAgent"/> provides an opinionated, batteries-included agent suitable for
|
||||
/// interactive agentic scenarios such as research, coding, data analysis, and general task automation.
|
||||
/// It assembles a full pipeline from a caller-supplied <see cref="IChatClient"/> so that callers
|
||||
/// only need to configure the parts they want to customize.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <strong>Chat client pipeline (inner to outer):</strong>
|
||||
/// <list type="number">
|
||||
/// <item><description><see cref="FunctionInvokingChatClient"/> — automatic function/tool invocation.</description></item>
|
||||
/// <item><description><see cref="MessageInjectingChatClient"/> — allows external code to inject messages into the conversation mid-stream.</description></item>
|
||||
/// <item><description><see cref="PerServiceCallChatHistoryPersistingChatClient"/> — persists chat history after every individual service call within a function-invocation loop.</description></item>
|
||||
/// <item><description><see cref="AIContextProviderChatClient"/> with a <see cref="CompactionProvider"/> — applies context-window compaction before each call so long function-invocation loops do not overflow the context window.</description></item>
|
||||
/// <item><description><see cref="FunctionInvokingChatClient"/> — automatic function/tool invocation with configurable iteration limits.</description></item>
|
||||
/// <item><description><see cref="MessageInjectingChatClient"/> — allows external code to inject messages into the conversation mid-stream (e.g., for user interrupts).</description></item>
|
||||
/// <item><description><see cref="PerServiceCallChatHistoryPersistingChatClient"/> — persists chat history after every individual service call within a function-invocation loop, enabling crash recovery and history inspection.</description></item>
|
||||
/// <item><description><see cref="AIContextProviderChatClient"/> with a <see cref="CompactionProvider"/> — applies context-window compaction before each call so long function-invocation loops do not overflow the context window. Only included when <see cref="HarnessAgentOptions.MaxContextWindowTokens"/> and <see cref="HarnessAgentOptions.MaxOutputTokens"/> are both provided.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// By default, the following context providers are included (each can be disabled via <see cref="HarnessAgentOptions"/>):
|
||||
/// <strong>Context providers (each enabled by default, individually disableable via <see cref="HarnessAgentOptions"/>):</strong>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="TodoProvider"/> — todo list management.</description></item>
|
||||
/// <item><description><see cref="AgentModeProvider"/> — agent mode tracking (plan/execute).</description></item>
|
||||
/// <item><description><see cref="FileMemoryProvider"/> — file-based session memory.</description></item>
|
||||
/// <item><description><see cref="FileAccessProvider"/> — shared file access.</description></item>
|
||||
/// <item><description><see cref="AgentSkillsProvider"/> — skill discovery and loading.</description></item>
|
||||
/// <item><description><see cref="TodoProvider"/> — persistent todo list that the agent uses to track multi-step plans. Disable with <see cref="HarnessAgentOptions.DisableTodoProvider"/>.</description></item>
|
||||
/// <item><description><see cref="AgentModeProvider"/> — mode tracking (e.g., "plan" vs "execute") that the agent uses to structure its work. Disable with <see cref="HarnessAgentOptions.DisableAgentModeProvider"/>.</description></item>
|
||||
/// <item><description><see cref="FileMemoryProvider"/> — file-based session memory allowing the agent to persist notes and artifacts across turns. Disable with <see cref="HarnessAgentOptions.DisableFileMemory"/>.</description></item>
|
||||
/// <item><description><see cref="FileAccessProvider"/> — shared file access providing read/write tools for a working directory. Disable with <see cref="HarnessAgentOptions.DisableFileAccess"/>.</description></item>
|
||||
/// <item><description><see cref="AgentSkillsProvider"/> — discovers and loads skill definitions from the file system, enabling dynamic tool sets. Disable with <see cref="HarnessAgentOptions.DisableAgentSkillsProvider"/>.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The agent is also wrapped with the following decorators by default (each can be disabled):
|
||||
/// <strong>Optional context providers (enabled via <see cref="HarnessAgentOptions"/>):</strong>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="ToolApprovalAgent"/> — "don't ask again" tool approval rules.</description></item>
|
||||
/// <item><description><see cref="OpenTelemetryAgent"/> — OpenTelemetry instrumentation.</description></item>
|
||||
/// <item><description><see cref="BackgroundAgentsProvider"/> — enables delegation to background agents for parallel work. Enable by setting <see cref="HarnessAgentOptions.BackgroundAgents"/>.</description></item>
|
||||
/// <item><description><c>ShellEnvironmentProvider</c> — injects OS/shell/CWD information and a shell execution tool. Enable by setting <c>HarnessAgentOptions.ShellExecutor</c> (.NET only).</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// A <see cref="HostedWebSearchTool"/> is added to the chat options by default (can be disabled via
|
||||
/// <see cref="HarnessAgentOptions.DisableWebSearch"/>).
|
||||
/// <strong>Agent decorators (each enabled by default, individually disableable):</strong>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="ToolApprovalAgent"/> — "don't ask again" tool approval rules enabling safe unattended execution. Disable with <see cref="HarnessAgentOptions.DisableToolApproval"/>.</description></item>
|
||||
/// <item><description><see cref="OpenTelemetryAgent"/> — OpenTelemetry instrumentation following semantic conventions for generative AI. Disable with <see cref="HarnessAgentOptions.DisableOpenTelemetry"/>.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// The underlying <see cref="ChatClientAgent"/> is configured with
|
||||
/// <see cref="ChatClientAgentOptions.UseProvidedChatClientAsIs"/> and
|
||||
/// <see cref="ChatClientAgentOptions.RequirePerServiceCallChatHistoryPersistence"/> set to <see langword="true"/>
|
||||
/// to match the manually-assembled pipeline.
|
||||
/// <strong>Default tools:</strong>
|
||||
/// <list type="bullet">
|
||||
/// <item><description><see cref="HostedWebSearchTool"/> — a hosted web search tool added to chat options by default. Disable with <see cref="HarnessAgentOptions.DisableWebSearch"/>.</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When no <see cref="HarnessAgentOptions.ChatHistoryProvider"/> is supplied, the agent defaults to an
|
||||
/// <see cref="InMemoryChatHistoryProvider"/> whose chat reducer applies the same compaction strategy,
|
||||
/// keeping in-memory history from growing unboundedly across sessions.
|
||||
/// <strong>Chat history:</strong> When no <see cref="HarnessAgentOptions.ChatHistoryProvider"/> is supplied,
|
||||
/// the agent defaults to an <see cref="InMemoryChatHistoryProvider"/>. If compaction is enabled, the provider
|
||||
/// is configured with a compaction-based chat reducer to keep in-memory history bounded. Otherwise, no reducer
|
||||
/// is applied.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <strong>Default instructions:</strong> The agent includes built-in system instructions (<see cref="DefaultInstructions"/>)
|
||||
/// that guide general tool usage and reasoning patterns. These can be overridden via <see cref="HarnessAgentOptions.HarnessInstructions"/>
|
||||
/// and combined with agent-specific instructions via <see cref="ChatOptions.Instructions"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
[Experimental(DiagnosticIds.Experiments.AgentsAIExperiments)]
|
||||
@@ -89,47 +105,46 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
/// </summary>
|
||||
/// <param name="chatClient">
|
||||
/// The <see cref="IChatClient"/> that provides access to the underlying AI model.
|
||||
/// The agent wraps this client in a function-invocation, per-service-call persistence,
|
||||
/// and compaction pipeline automatically.
|
||||
/// </param>
|
||||
/// <param name="maxContextWindowTokens">
|
||||
/// The maximum number of tokens the model's context window supports (e.g., 1,050,000 for gpt-5.4).
|
||||
/// Used to configure the compaction strategy.
|
||||
/// </param>
|
||||
/// <param name="maxOutputTokens">
|
||||
/// The maximum number of output tokens the model can generate per response (e.g., 128,000 for gpt-5.4).
|
||||
/// Used to configure the compaction strategy and to limit the model's output.
|
||||
/// The agent wraps this client in a function-invocation and per-service-call persistence pipeline.
|
||||
/// When compaction is enabled via <paramref name="options"/>, a compaction decorator is also added.
|
||||
/// </param>
|
||||
/// <param name="options">
|
||||
/// Optional configuration options for the agent, including instructions override, tools,
|
||||
/// additional context providers, and chat history provider.
|
||||
/// When <see langword="null"/>, the agent uses built-in default settings.
|
||||
/// additional context providers, chat history provider, and compaction settings.
|
||||
/// When <see langword="null"/>, the agent uses built-in default settings with compaction disabled.
|
||||
/// </param>
|
||||
/// <param name="loggerFactory">
|
||||
/// Optional logger factory for creating loggers used by the agent and its components.
|
||||
/// </param>
|
||||
/// <param name="services">
|
||||
/// Optional service provider for resolving dependencies required by AI functions and other agent components.
|
||||
/// </param>
|
||||
/// <exception cref="ArgumentNullException">
|
||||
/// <paramref name="chatClient"/> is <see langword="null"/>.
|
||||
/// </exception>
|
||||
/// <exception cref="ArgumentOutOfRangeException">
|
||||
/// <paramref name="maxContextWindowTokens"/> is not positive, or
|
||||
/// <paramref name="maxOutputTokens"/> is negative or greater than or equal to <paramref name="maxContextWindowTokens"/>.
|
||||
/// <see cref="HarnessAgentOptions.MaxContextWindowTokens"/> is not positive, or
|
||||
/// <see cref="HarnessAgentOptions.MaxOutputTokens"/> is negative or greater than or equal to
|
||||
/// <see cref="HarnessAgentOptions.MaxContextWindowTokens"/> (when both are provided).
|
||||
/// </exception>
|
||||
public HarnessAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options = null)
|
||||
public HarnessAgent(IChatClient chatClient, HarnessAgentOptions? options = null, ILoggerFactory? loggerFactory = null, IServiceProvider? services = null)
|
||||
: base(BuildAgent(
|
||||
Throw.IfNull(chatClient),
|
||||
maxContextWindowTokens,
|
||||
maxOutputTokens,
|
||||
options))
|
||||
options,
|
||||
loggerFactory,
|
||||
services))
|
||||
{
|
||||
}
|
||||
|
||||
private static AIAgent BuildAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options)
|
||||
private static AIAgent BuildAgent(IChatClient chatClient, HarnessAgentOptions? options, ILoggerFactory? loggerFactory, IServiceProvider? services)
|
||||
{
|
||||
ChatClientAgent innerAgent = BuildInnerAgent(chatClient, maxContextWindowTokens, maxOutputTokens, options);
|
||||
ChatClientAgent innerAgent = BuildInnerAgent(chatClient, options, loggerFactory, services);
|
||||
|
||||
AIAgentBuilder builder = innerAgent.AsBuilder();
|
||||
|
||||
if (options?.DisableToolApproval is not true)
|
||||
{
|
||||
builder.UseToolApproval();
|
||||
builder.UseToolApproval(options?.ToolApprovalAgentOptions);
|
||||
}
|
||||
|
||||
if (options?.DisableOpenTelemetry is not true)
|
||||
@@ -137,20 +152,38 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
builder.UseOpenTelemetry(sourceName: options?.OpenTelemetrySourceName);
|
||||
}
|
||||
|
||||
return builder.Build();
|
||||
return builder.Build(services);
|
||||
}
|
||||
|
||||
private static ChatClientAgent BuildInnerAgent(IChatClient chatClient, int maxContextWindowTokens, int maxOutputTokens, HarnessAgentOptions? options)
|
||||
private static ChatClientAgent BuildInnerAgent(IChatClient chatClient, HarnessAgentOptions? options, ILoggerFactory? loggerFactory, IServiceProvider? services)
|
||||
{
|
||||
var compactionStrategy = new ContextWindowCompactionStrategy(
|
||||
maxContextWindowTokens: maxContextWindowTokens,
|
||||
maxOutputTokens: maxOutputTokens);
|
||||
// Determine compaction strategy:
|
||||
// 1. DisableCompaction = true → no compaction
|
||||
// 2. Custom CompactionStrategy provided → use it (ignore token params)
|
||||
// 3. Both token params provided → build default ContextWindowCompactionStrategy
|
||||
// 4. Otherwise → no compaction
|
||||
CompactionStrategy? compactionStrategy = null;
|
||||
if (options?.DisableCompaction is not true)
|
||||
{
|
||||
if (options?.CompactionStrategy is CompactionStrategy customStrategy)
|
||||
{
|
||||
compactionStrategy = customStrategy;
|
||||
}
|
||||
else if (options?.MaxContextWindowTokens is int maxCtx && options?.MaxOutputTokens is int maxOut)
|
||||
{
|
||||
compactionStrategy = new ContextWindowCompactionStrategy(
|
||||
maxContextWindowTokens: maxCtx,
|
||||
maxOutputTokens: maxOut);
|
||||
}
|
||||
}
|
||||
|
||||
ChatHistoryProvider chatHistoryProvider = options?.ChatHistoryProvider
|
||||
?? new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
|
||||
{
|
||||
ChatReducer = compactionStrategy.AsChatReducer(),
|
||||
});
|
||||
?? (compactionStrategy is not null
|
||||
? new InMemoryChatHistoryProvider(new InMemoryChatHistoryProviderOptions
|
||||
{
|
||||
ChatReducer = compactionStrategy.AsChatReducer(),
|
||||
})
|
||||
: new InMemoryChatHistoryProvider());
|
||||
|
||||
string harnessInstructions = options?.HarnessInstructions ?? DefaultInstructions;
|
||||
string? agentInstructions = options?.ChatOptions?.Instructions;
|
||||
@@ -163,20 +196,34 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
(false, false) => $"{harnessInstructions}\n\n{agentInstructions}",
|
||||
};
|
||||
|
||||
ChatOptions chatOptions = BuildChatOptions(options, instructions, maxOutputTokens);
|
||||
ChatOptions chatOptions = BuildChatOptions(options, instructions, options?.MaxOutputTokens);
|
||||
|
||||
var compactionProvider = new CompactionProvider(compactionStrategy);
|
||||
CompactionProvider? compactionProvider = compactionStrategy is not null
|
||||
? new CompactionProvider(compactionStrategy, loggerFactory: loggerFactory)
|
||||
: null;
|
||||
|
||||
IEnumerable<AIContextProvider> contextProviders = BuildContextProviders(options);
|
||||
IEnumerable<AIContextProvider> contextProviders = BuildContextProviders(options, loggerFactory);
|
||||
|
||||
return chatClient
|
||||
.AsBuilder()
|
||||
.UseFunctionInvocation(configure: options?.MaximumIterationsPerRequest is int maxIterations
|
||||
ChatClientBuilder chatClientBuilder = chatClient.AsBuilder();
|
||||
|
||||
if (options?.DisableNonApprovalRequiredFunctionBypassing is not true)
|
||||
{
|
||||
chatClientBuilder.UseNonApprovalRequiredFunctionBypassing();
|
||||
}
|
||||
|
||||
ChatClientBuilder pipeline = chatClientBuilder
|
||||
.UseFunctionInvocation(loggerFactory, configure: options?.MaximumIterationsPerRequest is int maxIterations
|
||||
? ficc => ficc.MaximumIterationsPerRequest = maxIterations
|
||||
: null)
|
||||
.UseMessageInjection()
|
||||
.UsePerServiceCallChatHistoryPersistence()
|
||||
.UseAIContextProviders(compactionProvider)
|
||||
.UsePerServiceCallChatHistoryPersistence();
|
||||
|
||||
if (compactionProvider is not null)
|
||||
{
|
||||
pipeline = pipeline.UseAIContextProviders(compactionProvider);
|
||||
}
|
||||
|
||||
return pipeline
|
||||
.BuildAIAgent(new ChatClientAgentOptions
|
||||
{
|
||||
Id = options?.Id,
|
||||
@@ -189,14 +236,20 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
RequirePerServiceCallChatHistoryPersistence = true,
|
||||
WarnOnChatHistoryProviderConflict = false,
|
||||
ThrowOnChatHistoryProviderConflict = false,
|
||||
});
|
||||
},
|
||||
loggerFactory,
|
||||
services);
|
||||
}
|
||||
|
||||
private static ChatOptions BuildChatOptions(HarnessAgentOptions? options, string instructions, int maxOutputTokens)
|
||||
private static ChatOptions BuildChatOptions(HarnessAgentOptions? options, string instructions, int? maxOutputTokens)
|
||||
{
|
||||
ChatOptions result = options?.ChatOptions?.Clone() ?? new ChatOptions();
|
||||
result.Instructions = instructions;
|
||||
result.MaxOutputTokens ??= maxOutputTokens;
|
||||
|
||||
if (maxOutputTokens.HasValue)
|
||||
{
|
||||
result.MaxOutputTokens ??= maxOutputTokens.Value;
|
||||
}
|
||||
|
||||
if (options?.DisableWebSearch is not true)
|
||||
{
|
||||
@@ -215,7 +268,7 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<AIContextProvider> BuildContextProviders(HarnessAgentOptions? options)
|
||||
private static List<AIContextProvider> BuildContextProviders(HarnessAgentOptions? options, ILoggerFactory? loggerFactory)
|
||||
{
|
||||
var providers = new List<AIContextProvider>();
|
||||
|
||||
@@ -255,8 +308,8 @@ public sealed class HarnessAgent : DelegatingAIAgent
|
||||
if (options?.DisableAgentSkillsProvider is not true)
|
||||
{
|
||||
AgentSkillsProvider skillsProvider = options?.AgentSkillsSource is AgentSkillsSource source
|
||||
? new AgentSkillsProvider(source)
|
||||
: new AgentSkillsProvider(Directory.GetCurrentDirectory());
|
||||
? new AgentSkillsProvider(source, loggerFactory: loggerFactory)
|
||||
: new AgentSkillsProvider(Directory.GetCurrentDirectory(), loggerFactory: loggerFactory);
|
||||
|
||||
providers.Add(skillsProvider);
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using Microsoft.Agents.AI.Compaction;
|
||||
#if NET
|
||||
using Microsoft.Agents.AI.Tools.Shell;
|
||||
#endif
|
||||
@@ -31,6 +32,68 @@ public sealed class HarnessAgentOptions
|
||||
/// </summary>
|
||||
public string? Description { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of tokens the model's context window supports (e.g., 1,050,000 for gpt-5.4).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When both <see cref="MaxContextWindowTokens"/> and <see cref="MaxOutputTokens"/> are provided (and no
|
||||
/// custom <see cref="CompactionStrategy"/> is set), a default <see cref="ContextWindowCompactionStrategy"/>
|
||||
/// is constructed from these values to prevent function-invocation loops from overflowing the context window.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Ignored when <see cref="CompactionStrategy"/> is provided or when <see cref="DisableCompaction"/> is
|
||||
/// <see langword="true"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int? MaxContextWindowTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the maximum number of output tokens the model can generate per response (e.g., 128,000 for gpt-5.4).
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When set, this value is used as the default for <see cref="ChatOptions"/>.<see cref="ChatOptions.MaxOutputTokens"/>
|
||||
/// when not explicitly configured.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// For compaction purposes, this value is used together with <see cref="MaxContextWindowTokens"/> to construct a
|
||||
/// default <see cref="ContextWindowCompactionStrategy"/> — but only when no custom <see cref="CompactionStrategy"/>
|
||||
/// is provided and <see cref="DisableCompaction"/> is <see langword="false"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public int? MaxOutputTokens { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a custom <see cref="Compaction.CompactionStrategy"/> to use for in-loop context-window compaction.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// When provided, this strategy is used directly and <see cref="MaxContextWindowTokens"/> and
|
||||
/// <see cref="MaxOutputTokens"/> are ignored for compaction purposes (<see cref="MaxOutputTokens"/> is still
|
||||
/// used as the default for <see cref="ChatOptions"/>.<see cref="ChatOptions.MaxOutputTokens"/> if set).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When <see langword="null"/> and both <see cref="MaxContextWindowTokens"/> and <see cref="MaxOutputTokens"/>
|
||||
/// are provided, a default <see cref="ContextWindowCompactionStrategy"/> is constructed from those values.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// This property is ignored when <see cref="DisableCompaction"/> is <see langword="true"/>.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public CompactionStrategy? CompactionStrategy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether in-loop compaction is disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When <see langword="true"/>, compaction is disabled regardless of <see cref="CompactionStrategy"/>,
|
||||
/// <see cref="MaxContextWindowTokens"/>, or <see cref="MaxOutputTokens"/> settings. No
|
||||
/// <see cref="CompactionProvider"/> is added to the chat client pipeline, and the default
|
||||
/// <see cref="InMemoryChatHistoryProvider"/> is configured without a chat reducer.
|
||||
/// </remarks>
|
||||
public bool DisableCompaction { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets additional chat options such as tools for the agent to use.
|
||||
/// </summary>
|
||||
@@ -68,9 +131,9 @@ public sealed class HarnessAgentOptions
|
||||
/// Gets or sets the <see cref="ChatHistoryProvider"/> to use for storing chat history.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When <see langword="null"/>, the agent defaults to an <see cref="InMemoryChatHistoryProvider"/>
|
||||
/// configured with a compaction-based chat reducer derived from the <c>maxContextWindowTokens</c>
|
||||
/// and <c>maxOutputTokens</c> constructor parameters of <see cref="HarnessAgent"/>.
|
||||
/// When <see langword="null"/>, the agent defaults to an <see cref="InMemoryChatHistoryProvider"/>.
|
||||
/// If <see cref="MaxContextWindowTokens"/> and <see cref="MaxOutputTokens"/> are both provided,
|
||||
/// the default provider is configured with a compaction-based chat reducer; otherwise, no reducer is applied.
|
||||
/// </remarks>
|
||||
public ChatHistoryProvider? ChatHistoryProvider { get; set; }
|
||||
|
||||
@@ -101,6 +164,29 @@ public sealed class HarnessAgentOptions
|
||||
/// </remarks>
|
||||
public bool DisableToolApproval { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the options for the <see cref="ToolApprovalAgent"/> middleware.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When <see langword="null"/>, the <see cref="ToolApprovalAgent"/> uses default settings.
|
||||
/// This property has no effect when <see cref="DisableToolApproval"/> is <see langword="true"/>.
|
||||
/// </remarks>
|
||||
public ToolApprovalAgentOptions? ToolApprovalAgentOptions { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether bypassing of approval requests for tools that do not
|
||||
/// require approval is disabled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// When <see langword="false"/> (the default), the underlying chat client pipeline includes the decorator
|
||||
/// added by <see cref="ChatClientBuilderExtensions.UseNonApprovalRequiredFunctionBypassing"/> above the
|
||||
/// function invocation middleware.
|
||||
/// This stores automatically approved function calls for tools that do not require approval in the session
|
||||
/// state when they are returned alongside tools that do, so that only tools that truly require human
|
||||
/// approval are surfaced to the caller.
|
||||
/// </remarks>
|
||||
public bool DisableNonApprovalRequiredFunctionBypassing { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the <see cref="FileMemoryProvider"/> is disabled.
|
||||
/// </summary>
|
||||
|
||||
@@ -103,7 +103,16 @@ public static class AGUIEndpointRouteBuilderExtensions
|
||||
ArgumentNullException.ThrowIfNull(aiAgent);
|
||||
|
||||
var agentSessionStore = endpoints.ServiceProvider.GetKeyedService<AgentSessionStore>(aiAgent.Name);
|
||||
var hostAgent = new AIHostAgent(aiAgent, agentSessionStore ?? new NoopAgentSessionStore());
|
||||
|
||||
// Ensure that we have an IsolationKeyScopedAgentSessionStore registered.
|
||||
var isolationKeyProvider = endpoints.ServiceProvider.GetService<SessionIsolationKeyProvider>();
|
||||
if (agentSessionStore?.GetService<IsolationKeyScopedAgentSessionStore>() is null)
|
||||
{
|
||||
agentSessionStore ??= new NoopAgentSessionStore();
|
||||
agentSessionStore = new IsolationKeyScopedAgentSessionStore(agentSessionStore, isolationKeyProvider, new() { Strict = isolationKeyProvider != null });
|
||||
}
|
||||
|
||||
var hostAgent = new AIHostAgent(aiAgent, agentSessionStore);
|
||||
|
||||
return endpoints.MapPost(pattern, async ([FromBody] RunAgentInput? input, HttpContext context, CancellationToken cancellationToken) =>
|
||||
{
|
||||
|
||||
@@ -21,6 +21,18 @@ namespace Microsoft.Agents.AI.Hosting;
|
||||
/// from the ambient <see cref="HttpContext"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <strong>Security warning:</strong> The configured <see cref="ClaimsIdentitySessionIsolationKeyProviderOptions.ClaimType"/>
|
||||
/// must uniquely identify the principal within the served population. Display names, usernames, email
|
||||
/// aliases, and other mutable or non-unique claims are <strong>unsafe</strong> isolation keys unless the
|
||||
/// host can prove their uniqueness across all callers: two distinct principals that share the same value
|
||||
/// would receive the same isolation key and could read or overwrite one another's persisted sessions.
|
||||
/// The default claim type is <see cref="ClaimTypes.NameIdentifier"/>, a stable unique subject identifier
|
||||
/// that is typically populated from the OpenID Connect <c>sub</c> claim via the default JWT inbound claim
|
||||
/// mapping (note that this differs from Entra's object identifier <c>oid</c> claim; override
|
||||
/// <see cref="ClaimsIdentitySessionIsolationKeyProviderOptions.ClaimType"/> if you need <c>oid</c> or your
|
||||
/// provider maps a different claim).
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// If the <see cref="HttpContext"/> is unavailable, the user is not authenticated, or the specified claim
|
||||
/// is missing, the provider returns <see langword="null"/>. The consuming <see cref="IsolationKeyScopedAgentSessionStore"/>
|
||||
/// will then enforce strict or pass-through behavior based on its configuration.
|
||||
@@ -60,18 +72,24 @@ public class ClaimsIdentitySessionIsolationKeyProvider : SessionIsolationKeyProv
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.</param>
|
||||
/// <returns>
|
||||
/// A task that represents the asynchronous operation. The task result contains the value of the
|
||||
/// configured claim type from the current user's identity, or <see langword="null"/> if the claim
|
||||
/// is not present or the HTTP context is unavailable.
|
||||
/// configured claim type from the current user's identity, or <see langword="null"/> if the HTTP
|
||||
/// context is unavailable, the user is not authenticated, or the claim is not present.
|
||||
/// </returns>
|
||||
/// <remarks>
|
||||
/// This method retrieves the claim value from <c>HttpContext.User.Claims</c>. If multiple claims
|
||||
/// of the specified type exist, the first match is returned.
|
||||
/// This method only reads claims from an authenticated principal: if the current request has no
|
||||
/// authenticated user, it returns <see langword="null"/> rather than trusting claims on an
|
||||
/// unauthenticated identity. The claim value is retrieved from <c>HttpContext.User.Claims</c>; if
|
||||
/// multiple claims of the specified type exist, the first match is returned.
|
||||
/// </remarks>
|
||||
public override ValueTask<string?> GetSessionIsolationKeyAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
Claim? claim = this._httpContextAccessor?
|
||||
.HttpContext?
|
||||
.User?.Claims.FirstOrDefault(c => c.Type == this._claimType);
|
||||
ClaimsPrincipal? user = this._httpContextAccessor?.HttpContext?.User;
|
||||
if (user?.Identity?.IsAuthenticated != true)
|
||||
{
|
||||
return new ValueTask<string?>((string?)null);
|
||||
}
|
||||
|
||||
Claim? claim = user?.Claims.FirstOrDefault(c => c.Type == this._claimType);
|
||||
|
||||
return new ValueTask<string?>(claim?.Value);
|
||||
}
|
||||
|
||||
@@ -14,17 +14,30 @@ public class ClaimsIdentitySessionIsolationKeyProviderOptions
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// Defaults to <see cref="ClaimsIdentity.DefaultNameClaimType"/>, which typically corresponds to
|
||||
/// the user's name or unique identifier claim.
|
||||
/// Defaults to <see cref="ClaimTypes.NameIdentifier"/>, which corresponds to a stable, unique
|
||||
/// subject identifier for the authenticated principal. For OpenID Connect tokens (including those
|
||||
/// issued by Microsoft Entra ID), this is typically populated from the <c>sub</c> claim via the
|
||||
/// default JWT inbound claim mapping. Note that <c>sub</c> is distinct from Entra's object
|
||||
/// identifier (<c>oid</c>) claim; if you require the <c>oid</c> claim, or your provider does not map
|
||||
/// a unique identifier onto <see cref="ClaimTypes.NameIdentifier"/>, override <see cref="ClaimType"/>
|
||||
/// with the appropriate claim type.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <strong>Security warning:</strong> The configured claim must uniquely identify the principal
|
||||
/// within the served population. Display names (<see cref="ClaimsIdentity.DefaultNameClaimType"/>
|
||||
/// / <see cref="ClaimTypes.Name"/>), usernames, email aliases, and other mutable or non-unique
|
||||
/// claims are <strong>unsafe</strong> isolation keys unless the host can prove their uniqueness
|
||||
/// across all callers. Two distinct principals that share the same value for a non-unique claim
|
||||
/// would receive the same session-isolation key and could read or overwrite one another's
|
||||
/// persisted sessions. Only override this value with a claim that is guaranteed unique and stable.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// Common alternatives include:
|
||||
/// <list type="bullet">
|
||||
/// <item><description><c>ClaimTypes.NameIdentifier</c> — Stable user identifier</description></item>
|
||||
/// <item><description><c>ClaimTypes.Email</c> — Email address</description></item>
|
||||
/// <item><description>Custom claim types specific to your authentication provider</description></item>
|
||||
/// <item><description>A composite of tenant and subject identifiers — required for multi-tenant hosts where the subject is only unique per tenant</description></item>
|
||||
/// <item><description>Custom claim types specific to your authentication provider, provided they are unique and stable</description></item>
|
||||
/// </list>
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public string ClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType;
|
||||
public string ClaimType { get; set; } = ClaimTypes.NameIdentifier;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
@@ -19,8 +20,28 @@ public static class ServiceCollectionExtensions
|
||||
/// <param name="options"> Optional configuration for the claims-based session isolation key provider.</param>
|
||||
/// <returns>The <see cref="IServiceCollection"/> so that additional calls can be chained.</returns>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// This method requires <see cref="IHttpContextAccessor"/> to be registered in the service collection.
|
||||
/// Ensure that <c>services.AddHttpContextAccessor()</c> has been called before using this method.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// When <paramref name="options"/> is not supplied, the isolation key is derived from the
|
||||
/// <see cref="ClaimTypes.NameIdentifier"/> claim, a stable unique subject identifier. For OpenID
|
||||
/// Connect tokens (including Microsoft Entra ID), this is typically mapped from the <c>sub</c> claim
|
||||
/// by the default JWT inbound claim mapping. Authentication schemes that do not project a unique
|
||||
/// identifier onto <see cref="ClaimTypes.NameIdentifier"/> (or hosts that require a different claim
|
||||
/// such as Entra's <c>oid</c>) should override
|
||||
/// <see cref="ClaimsIdentitySessionIsolationKeyProviderOptions.ClaimType"/>; otherwise the key may be
|
||||
/// absent, which causes strict-mode session stores to fail.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <strong>Security warning:</strong> If you override
|
||||
/// <see cref="ClaimsIdentitySessionIsolationKeyProviderOptions.ClaimType"/>, the chosen claim must
|
||||
/// uniquely identify the principal within the served population. Display names, usernames, email
|
||||
/// aliases, and other mutable or non-unique claims are <strong>unsafe</strong> isolation keys unless
|
||||
/// the host can prove their uniqueness across all callers, because distinct principals that share the
|
||||
/// same claim value would receive the same isolation key and could access one another's sessions.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public static IServiceCollection UseClaimsBasedSessionIsolation(
|
||||
this IServiceCollection services,
|
||||
|
||||
@@ -1,10 +1,14 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Channels;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.Agents.AI.Purview.Models.Common;
|
||||
using Microsoft.Agents.AI.Purview.Models.Jobs;
|
||||
using Microsoft.Agents.AI.Purview.Models.Requests;
|
||||
using Microsoft.Agents.AI.Purview.Models.Responses;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.Agents.AI.Purview;
|
||||
@@ -16,6 +20,7 @@ internal sealed class BackgroundJobRunner : IBackgroundJobRunner
|
||||
{
|
||||
private readonly IChannelHandler _channelHandler;
|
||||
private readonly IPurviewClient _purviewClient;
|
||||
private readonly ICacheProvider _cacheProvider;
|
||||
private readonly ILogger _logger;
|
||||
|
||||
/// <summary>
|
||||
@@ -23,12 +28,14 @@ internal sealed class BackgroundJobRunner : IBackgroundJobRunner
|
||||
/// </summary>
|
||||
/// <param name="channelHandler">The channel handler used to manage job channels.</param>
|
||||
/// <param name="purviewClient">The Purview client used to send requests to Purview.</param>
|
||||
/// <param name="cacheProvider">The cache provider used to store protection scopes results.</param>
|
||||
/// <param name="logger">The logger used to log information about background jobs.</param>
|
||||
/// <param name="purviewSettings">The settings used to configure Purview client behavior.</param>
|
||||
public BackgroundJobRunner(IChannelHandler channelHandler, IPurviewClient purviewClient, ILogger logger, PurviewSettings purviewSettings)
|
||||
public BackgroundJobRunner(IChannelHandler channelHandler, IPurviewClient purviewClient, ICacheProvider cacheProvider, ILogger logger, PurviewSettings purviewSettings)
|
||||
{
|
||||
this._channelHandler = channelHandler;
|
||||
this._purviewClient = purviewClient;
|
||||
this._cacheProvider = cacheProvider;
|
||||
this._logger = logger;
|
||||
|
||||
for (int i = 0; i < purviewSettings.MaxConcurrentJobConsumers; i++)
|
||||
@@ -67,6 +74,28 @@ internal sealed class BackgroundJobRunner : IBackgroundJobRunner
|
||||
break;
|
||||
case ContentActivityJob contentActivityJob:
|
||||
_ = await this._purviewClient.SendContentActivitiesAsync(contentActivityJob.Request, CancellationToken.None).ConfigureAwait(false);
|
||||
break;
|
||||
case ScopeRetrievalJob scopeRetrievalJob:
|
||||
try
|
||||
{
|
||||
ProtectionScopesResponse response = await this._purviewClient.GetProtectionScopesAsync(scopeRetrievalJob.Request, CancellationToken.None).ConfigureAwait(false);
|
||||
await this._cacheProvider.SetAsync(scopeRetrievalJob.CacheKey, response, CancellationToken.None).ConfigureAwait(false);
|
||||
(bool shouldProcess, List<DlpActionInfo> _, ExecutionMode _) = ScopedContentProcessor.CheckApplicableScopes(scopeRetrievalJob.ProcessContentRequest, response);
|
||||
if (!shouldProcess)
|
||||
{
|
||||
ProcessContentRequest pcRequest = scopeRetrievalJob.ProcessContentRequest;
|
||||
ContentActivitiesRequest caRequest = new(pcRequest.UserId, pcRequest.TenantId, pcRequest.ContentToProcess, pcRequest.CorrelationId);
|
||||
this._channelHandler.QueueJob(new ContentActivityJob(caRequest));
|
||||
}
|
||||
}
|
||||
catch (PurviewPaymentRequiredException ex)
|
||||
{
|
||||
await this._cacheProvider.SetAsync(
|
||||
new PaymentRequiredCacheKey(scopeRetrievalJob.Request.TenantId),
|
||||
new PaymentRequiredCacheEntry(ex.Message),
|
||||
CancellationToken.None).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Purview.Models.Common;
|
||||
|
||||
/// <summary>
|
||||
/// Cached tenant-level payment required state.
|
||||
/// </summary>
|
||||
internal sealed class PaymentRequiredCacheEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="PaymentRequiredCacheEntry"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The payment required error message.</param>
|
||||
public PaymentRequiredCacheEntry(string? message)
|
||||
{
|
||||
this.Message = message;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The payment required error message.
|
||||
/// </summary>
|
||||
public string? Message { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
namespace Microsoft.Agents.AI.Purview.Models.Common;
|
||||
|
||||
/// <summary>
|
||||
/// A cache key for tenant-level payment required state.
|
||||
/// </summary>
|
||||
internal sealed class PaymentRequiredCacheKey
|
||||
{
|
||||
/// <summary>
|
||||
/// Creates a new instance of <see cref="PaymentRequiredCacheKey"/>.
|
||||
/// </summary>
|
||||
/// <param name="tenantId">The id of the tenant.</param>
|
||||
public PaymentRequiredCacheKey(string tenantId)
|
||||
{
|
||||
this.TenantId = tenantId;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The id of the tenant.
|
||||
/// </summary>
|
||||
public string TenantId { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
// Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
using Microsoft.Agents.AI.Purview.Models.Common;
|
||||
using Microsoft.Agents.AI.Purview.Models.Requests;
|
||||
|
||||
namespace Microsoft.Agents.AI.Purview.Models.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// Class representing a job that refreshes the protection scopes cache in the background.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Used by the parallel protection scopes retrieval path to warm the cache without blocking the
|
||||
/// foreground ProcessContent call.
|
||||
/// </remarks>
|
||||
internal sealed class ScopeRetrievalJob : BackgroundJobBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ScopeRetrievalJob"/> class.
|
||||
/// </summary>
|
||||
/// <param name="request">The protection scopes request to send to Purview.</param>
|
||||
/// <param name="cacheKey">The cache key used to store the response.</param>
|
||||
/// <param name="processContentRequest">The original process content request that triggered scope retrieval.</param>
|
||||
public ScopeRetrievalJob(ProtectionScopesRequest request, ProtectionScopesCacheKey cacheKey, ProcessContentRequest processContentRequest)
|
||||
{
|
||||
this.Request = request;
|
||||
this.CacheKey = cacheKey;
|
||||
this.ProcessContentRequest = processContentRequest;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the protection scopes request.
|
||||
/// </summary>
|
||||
public ProtectionScopesRequest Request { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the cache key used to store the response.
|
||||
/// </summary>
|
||||
public ProtectionScopesCacheKey CacheKey { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the original process content request that triggered scope retrieval.
|
||||
/// </summary>
|
||||
public ProcessContentRequest ProcessContentRequest { get; }
|
||||
}
|
||||
@@ -53,4 +53,10 @@ internal sealed class ProcessContentRequest
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
internal string? ScopeIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the ProcessContent request should ask the service for inline evaluation.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
internal bool ProcessInline { get; set; }
|
||||
}
|
||||
|
||||