diff --git a/.devcontainer/dotnet/devcontainer.json b/.devcontainer/dotnet/devcontainer.json
index a10557f9819..59b56a44383 100644
--- a/.devcontainer/dotnet/devcontainer.json
+++ b/.devcontainer/dotnet/devcontainer.json
@@ -1,10 +1,11 @@
{
"name": "C# (.NET)",
- "image": "mcr.microsoft.com/devcontainers/dotnet:9.0",
+ "image": "mcr.microsoft.com/devcontainers/dotnet:10.0",
"features": {
"ghcr.io/devcontainers/features/dotnet:2.4.0": {},
"ghcr.io/devcontainers/features/powershell:1.5.1": {},
- "ghcr.io/devcontainers/features/azure-cli:1.2.8": {}
+ "ghcr.io/devcontainers/features/azure-cli:1.2.8": {},
+ "ghcr.io/devcontainers/features/docker-in-docker:2.12.4": {}
},
"workspaceFolder": "/workspaces/agent-framework/dotnet/",
"customizations": {
diff --git a/.github/upgrades/prompts/SemanticKernelToAgentFramework.md b/.github/upgrades/prompts/SemanticKernelToAgentFramework.md
index a121a5f4469..1b28626ea85 100644
--- a/.github/upgrades/prompts/SemanticKernelToAgentFramework.md
+++ b/.github/upgrades/prompts/SemanticKernelToAgentFramework.md
@@ -1636,4 +1636,4 @@ The property mapping guide from a `AutoFunctionInvocationContext` to a `Function
| Result | Use `return` from the delegate |
| Terminate | Terminate |
| CancellationToken | provided via argument to middleware delegate |
-| Arguments | Arguments |
\ No newline at end of file
+| Arguments | Arguments |
diff --git a/.github/workflows/dotnet-build-and-test.yml b/.github/workflows/dotnet-build-and-test.yml
index ecf093a3e91..109f5c11aab 100644
--- a/.github/workflows/dotnet-build-and-test.yml
+++ b/.github/workflows/dotnet-build-and-test.yml
@@ -18,6 +18,7 @@ on:
env:
COVERAGE_THRESHOLD: 80
+ COVERAGE_FRAMEWORK: net10.0 # framework target for which we run/report code coverage
concurrency:
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
@@ -59,9 +60,9 @@ jobs:
fail-fast: false
matrix:
include:
- - { targetFramework: "net9.0", os: "ubuntu-latest", configuration: Release, integration-tests: true, environment: "integration" }
- - { targetFramework: "net9.0", os: "ubuntu-latest", configuration: Debug }
- - { targetFramework: "net9.0", os: "windows-latest", configuration: Release }
+ - { targetFramework: "net10.0", os: "ubuntu-latest", configuration: Release, integration-tests: true, environment: "integration" }
+ - { targetFramework: "net9.0", os: "windows-latest", configuration: Debug }
+ - { targetFramework: "net8.0", os: "ubuntu-latest", configuration: Release }
- { targetFramework: "net472", os: "windows-latest", configuration: Release, integration-tests: true, environment: "integration" }
runs-on: ${{ matrix.os }}
@@ -69,16 +70,16 @@ jobs:
steps:
- uses: actions/checkout@v5
with:
- persist-credentials: false
- sparse-checkout: |
- .
- .github
- dotnet
- python
- workflow-samples
+ persist-credentials: false
+ sparse-checkout: |
+ .
+ .github
+ dotnet
+ python
+ workflow-samples
- name: Setup dotnet
- uses: actions/setup-dotnet@v5.0.0
+ uses: actions/setup-dotnet@v5.0.1
with:
global-json-file: ${{ github.workspace }}/dotnet/global.json
- name: Build dotnet solutions
@@ -123,7 +124,17 @@ jobs:
popd
rm -rf "$TEMP_DIR"
- - name: Run Unit Tests Windows
+ # Start Cosmos DB Emulator for Cosmos-based unit tests (only on Windows)
+ - name: Start Azure Cosmos DB Emulator
+ if: runner.os == 'Windows'
+ shell: pwsh
+ run: |
+ Write-Host "Launching Azure Cosmos DB Emulator"
+ Import-Module "$env:ProgramFiles\Azure Cosmos DB Emulator\PSModules\Microsoft.Azure.CosmosDB.Emulator"
+ Start-CosmosDbEmulator -NoUI -Key "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="
+ echo "COSMOS_EMULATOR_AVAILABLE=true" >> $env:GITHUB_ENV
+
+ - name: Run Unit Tests
shell: bash
run: |
export UT_PROJECTS=$(find ./dotnet -type f -name "*.UnitTests.csproj" | tr '\n' ' ')
@@ -133,12 +144,20 @@ jobs:
# Check if the project supports the target framework
if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
- dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --collect:"XPlat Code Coverage" --results-directory:"TestResults/Coverage/" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttribute=GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute
+ if [[ "${{ matrix.targetFramework }}" == "${{ env.COVERAGE_FRAMEWORK }}" ]]; then
+ dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx --collect:"XPlat Code Coverage" --results-directory:"TestResults/Coverage/" -- DataCollectionRunSettings.DataCollectors.DataCollector.Configuration.ExcludeByAttribute=GeneratedCodeAttribute,CompilerGeneratedAttribute,ExcludeFromCodeCoverageAttribute
+ else
+ dotnet test -f ${{ matrix.targetFramework }} -c ${{ matrix.configuration }} $project --no-build -v Normal --logger trx
+ fi
else
echo "Skipping $project - does not support target framework ${{ matrix.targetFramework }} (supports: $target_frameworks)"
fi
done
-
+ env:
+ # Cosmos DB Emulator connection settings
+ COSMOSDB_ENDPOINT: https://localhost:8081
+ COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
+
- name: Log event name and matrix integration-tests
shell: bash
run: echo "github.event_name:${{ github.event_name }} matrix.integration-tests:${{ matrix.integration-tests }} github.event.action:${{ github.event.action }} github.event.pull_request.merged:${{ github.event.pull_request.merged }}"
@@ -176,6 +195,9 @@ jobs:
fi
done
env:
+ # Cosmos DB Emulator connection settings
+ COSMOSDB_ENDPOINT: https://localhost:8081
+ COSMOSDB_KEY: C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==
# OpenAI Models
OpenAI__ApiKey: ${{ secrets.OPENAI__APIKEY }}
OpenAI__ChatModelId: ${{ vars.OPENAI__CHATMODELID }}
@@ -194,19 +216,22 @@ jobs:
# Generate test reports and check coverage
- name: Generate test reports
- uses: danielpalme/ReportGenerator-GitHub-Action@5.4.18
+ if: matrix.targetFramework == env.COVERAGE_FRAMEWORK
+ uses: danielpalme/ReportGenerator-GitHub-Action@5.5.0
with:
reports: "./TestResults/Coverage/**/coverage.cobertura.xml"
targetdir: "./TestResults/Reports"
reporttypes: "HtmlInline;JsonSummary"
- name: Upload coverage report artifact
+ if: matrix.targetFramework == env.COVERAGE_FRAMEWORK
uses: actions/upload-artifact@v5
with:
name: CoverageReport-${{ matrix.os }}-${{ matrix.targetFramework }}-${{ matrix.configuration }} # Artifact name
path: ./TestResults/Reports # Directory containing files to upload
- name: Check coverage
+ if: matrix.targetFramework == env.COVERAGE_FRAMEWORK
shell: pwsh
run: .github/workflows/dotnet-check-coverage.ps1 -JsonReportPath "TestResults/Reports/Summary.json" -CoverageThreshold $env:COVERAGE_THRESHOLD
diff --git a/.github/workflows/dotnet-format.yml b/.github/workflows/dotnet-format.yml
index a9fe0900135..757d877028f 100644
--- a/.github/workflows/dotnet-format.yml
+++ b/.github/workflows/dotnet-format.yml
@@ -22,7 +22,7 @@ jobs:
fail-fast: false
matrix:
include:
- - { dotnet: "9.0", configuration: Release, os: ubuntu-latest }
+ - { dotnet: "10.0", configuration: Release, os: ubuntu-latest }
runs-on: ${{ matrix.os }}
env:
diff --git a/README.md b/README.md
index 30d9ab2bdd6..64b0dbd821d 100644
--- a/README.md
+++ b/README.md
@@ -48,6 +48,8 @@ dotnet add package Microsoft.Agents.AI
- **[Migration from Semantic Kernel](https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-semantic-kernel)** - Guide to migrate from Semantic Kernel
- **[Migration from AutoGen](https://learn.microsoft.com/en-us/agent-framework/migration-guide/from-autogen)** - Guide to migrate from AutoGen
+Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-community-office-hours) or ask questions in our [Discord channel](https://discord.gg/b5zjErwbQM) to get help from the team and other users.
+
### ✨ **Highlights**
- **Graph-based Workflows**: Connect agents and deterministic functions using data flows with streaming, checkpointing, human-in-the-loop, and time-travel capabilities
diff --git a/TRANSPARENCY_FAQ.md b/TRANSPARENCY_FAQ.md
index cd850ff7969..3a09f191eb2 100644
--- a/TRANSPARENCY_FAQ.md
+++ b/TRANSPARENCY_FAQ.md
@@ -42,9 +42,9 @@ Microsoft Agent Framework relies on existing LLMs. Using the framework retains c
**Framework-Specific Limitations**:
-- **Platform Requirements**: Python 3.10+ required, specific .NET versions (.NET 8.0, 9.0, netstandard2.0, net472)
+- **Platform Requirements**: Python 3.10+ required, specific .NET versions (.NET 8.0, 9.0, 10.0, netstandard2.0, net472)
- **API Dependencies**: Requires proper configuration of LLM provider keys and endpoints
-- **Orchestration Features**: Advanced orchestration patterns like GroupChat, Sequential, and Concurrent orchestrations are "coming soon" for Python implementation
+- **Orchestration Features**: Advanced orchestration patterns including GroupChat, Sequential, and Concurrent workflows are now available in both Python and .NET implementations. See the respective language documentation for examples.
- **Privacy and Data Protection**: The framework allows for human participation in conversations between agents. It is important to ensure that user data and conversations are protected and that developers use appropriate measures to safeguard privacy.
- **Accountability and Transparency**: The framework involves multiple agents conversing and collaborating, it is important to establish clear accountability and transparency mechanisms. Users should be able to understand and trace the decision-making process of the agents involved in order to ensure accountability and address any potential issues or biases.
- **Security & unintended consequences**: The use of multi-agent conversations and automation in complex tasks may have unintended consequences. Especially, allowing agents to make changes in external environments through tool calls or function execution could pose significant risks. Developers should carefully consider the potential risks and ensure that appropriate safeguards are in place to prevent harm or negative outcomes, including keeping a human in the loop for decision making.
diff --git a/agent-samples/azure/AzureOpenAIAssistants.yaml b/agent-samples/azure/AzureOpenAIAssistants.yaml
index 8c0d889598a..f973d05acc7 100644
--- a/agent-samples/azure/AzureOpenAIAssistants.yaml
+++ b/agent-samples/azure/AzureOpenAIAssistants.yaml
@@ -1,9 +1,9 @@
kind: Prompt
name: Assistant
description: Helpful assistant
-instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
model:
- id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
+ id: gpt-4o-mini
provider: AzureOpenAI
apiType: Assistants
options:
@@ -12,14 +12,14 @@ model:
outputSchema:
properties:
language:
- kind: string
+ type: string
required: true
description: The language of the answer.
answer:
- kind: string
+ type: string
required: true
description: The answer text.
type:
- kind: string
+ type: string
required: true
description: The type of the response.
diff --git a/agent-samples/azure/AzureOpenAIChat.yaml b/agent-samples/azure/AzureOpenAIChat.yaml
new file mode 100644
index 00000000000..d02e0c60398
--- /dev/null
+++ b/agent-samples/azure/AzureOpenAIChat.yaml
@@ -0,0 +1,25 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Chat as the type in your response.
+model:
+ id: gpt-4o-mini
+ provider: AzureOpenAI
+ apiType: Chat
+ options:
+ temperature: 0.9
+ topP: 0.95
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/azure/AzureOpenAIResponses.yaml b/agent-samples/azure/AzureOpenAIResponses.yaml
index 5db218ade35..006c1476f42 100644
--- a/agent-samples/azure/AzureOpenAIResponses.yaml
+++ b/agent-samples/azure/AzureOpenAIResponses.yaml
@@ -1,28 +1,25 @@
kind: Prompt
name: Assistant
description: Helpful assistant
-instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
model:
- id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
+ id: gpt-4o-mini
provider: AzureOpenAI
apiType: Responses
options:
- text:
- verbosity: medium
- connection:
- kind: remote
- endpoint: =Env.AZURE_OPENAI_ENDPOINT
+ temperature: 0.9
+ topP: 0.95
outputSchema:
properties:
language:
- kind: string
+ type: string
required: true
description: The language of the answer.
answer:
- kind: string
+ type: string
required: true
description: The answer text.
type:
- kind: string
+ type: string
required: true
description: The type of the response.
diff --git a/agent-samples/chatclient/Assistant.yaml b/agent-samples/chatclient/Assistant.yaml
index b34add2d232..3332d545405 100644
--- a/agent-samples/chatclient/Assistant.yaml
+++ b/agent-samples/chatclient/Assistant.yaml
@@ -1,7 +1,7 @@
kind: Prompt
name: Assistant
description: Helpful assistant
-instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format.
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
model:
options:
temperature: 0.9
@@ -9,10 +9,10 @@ model:
outputSchema:
properties:
language:
- kind: string
+ type: string
required: true
description: The language of the answer.
answer:
- kind: string
+ type: string
required: true
description: The answer text.
diff --git a/agent-samples/chatclient/GetWeather.yaml b/agent-samples/chatclient/GetWeather.yaml
index 9ed637894dd..f32411be982 100644
--- a/agent-samples/chatclient/GetWeather.yaml
+++ b/agent-samples/chatclient/GetWeather.yaml
@@ -4,6 +4,8 @@ description: Helpful assistant
instructions: You are a helpful assistant. You answer questions using the tools provided.
model:
options:
+ temperature: 0.9
+ topP: 0.95
allowMultipleToolCalls: true
chatToolMode: auto
tools:
diff --git a/agent-samples/foundry/FoundryAgent.yaml b/agent-samples/foundry/FoundryAgent.yaml
new file mode 100644
index 00000000000..2de2ea069ef
--- /dev/null
+++ b/agent-samples/foundry/FoundryAgent.yaml
@@ -0,0 +1,22 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+model:
+ id: gpt-4.1-mini
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: Remote
+ endpoint: =Env.AZURE_FOUNDRY_PROJECT_ENDPOINT
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
diff --git a/agent-samples/openai/OpenAIAssistants.yaml b/agent-samples/openai/OpenAIAssistants.yaml
index 78bd48d7015..c1f20beb381 100644
--- a/agent-samples/openai/OpenAIAssistants.yaml
+++ b/agent-samples/openai/OpenAIAssistants.yaml
@@ -1,30 +1,28 @@
kind: Prompt
name: Assistant
description: Helpful assistant
-instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Assistants as the type in your response.
model:
- id: =Env.OPENAI_MODEL
+ id: gpt-4.1-mini
provider: OpenAI
apiType: Assistants
options:
temperature: 0.9
topP: 0.95
connection:
- kind: key
+ kind: ApiKey
key: =Env.OPENAI_APIKEY
outputSchema:
- name: AssistantResponse
- description: The response from the assistant.
properties:
language:
- kind: string
+ type: string
required: true
description: The language of the answer.
answer:
- kind: string
+ type: string
required: true
description: The answer text.
type:
- kind: string
+ type: string
required: true
description: The type of the response.
diff --git a/agent-samples/openai/OpenAIChat.yaml b/agent-samples/openai/OpenAIChat.yaml
new file mode 100644
index 00000000000..832ef4eb151
--- /dev/null
+++ b/agent-samples/openai/OpenAIChat.yaml
@@ -0,0 +1,28 @@
+kind: Prompt
+name: Assistant
+description: Helpful assistant
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Chat as the type in your response.
+model:
+ id: gpt-4.1-mini
+ provider: OpenAI
+ apiType: Chat
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: ApiKey
+ key: =Env.OPENAI_APIKEY
+outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ type:
+ type: string
+ required: true
+ description: The type of the response.
diff --git a/agent-samples/openai/OpenAIResponses.yaml b/agent-samples/openai/OpenAIResponses.yaml
index 0fcda30c9cd..efe822233ec 100644
--- a/agent-samples/openai/OpenAIResponses.yaml
+++ b/agent-samples/openai/OpenAIResponses.yaml
@@ -1,28 +1,28 @@
kind: Prompt
name: Assistant
description: Helpful assistant
-instructions: You are a helpful assistant. You answer questions is the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
+instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format. You must include Responses as the type in your response.
model:
- id: =Env.OPENAI_MODEL
+ id: gpt-4.1-mini
provider: OpenAI
apiType: Responses
options:
- text:
- verbosity: medium
+ temperature: 0.9
+ topP: 0.95
connection:
- kind: key
+ kind: ApiKey
key: =Env.OPENAI_APIKEY
outputSchema:
properties:
language:
- kind: string
+ type: string
required: true
description: The language of the answer.
answer:
- kind: string
+ type: string
required: true
description: The answer text.
type:
- kind: string
+ type: string
required: true
description: The type of the response.
diff --git a/dotnet/Directory.Build.props b/dotnet/Directory.Build.props
index 6b61196bbd9..54a125a13b4 100644
--- a/dotnet/Directory.Build.props
+++ b/dotnet/Directory.Build.props
@@ -6,14 +6,12 @@
AllEnabledByDefault
latest
true
- 13
+ latest
enable
- $(NoWarn);NU5128
+ $(NoWarn);NU5128;CS8002
true
- net9.0;net8.0
- net9.0
- net9.0;net8.0;netstandard2.0;net472
- net9.0;net472
+ net10.0;net9.0;net8.0
+ $(TargetFrameworksCore);netstandard2.0;net472
true
Debug;Release;Publish
diff --git a/dotnet/Directory.Build.targets b/dotnet/Directory.Build.targets
index 75033d16e31..5e62f1cef75 100644
--- a/dotnet/Directory.Build.targets
+++ b/dotnet/Directory.Build.targets
@@ -5,7 +5,7 @@
-
+
diff --git a/dotnet/Directory.Packages.props b/dotnet/Directory.Packages.props
index 00f10230a5b..b9338fc9bb7 100644
--- a/dotnet/Directory.Packages.props
+++ b/dotnet/Directory.Packages.props
@@ -7,10 +7,12 @@
- 13.0.0
+ 13.0.1
+
+
@@ -18,15 +20,22 @@
-
-
-
+
+
+
+
+
+
+
+
+
+
-
+
@@ -37,6 +46,7 @@
+
@@ -48,13 +58,13 @@
-
+
-
-
+
+
-
+
@@ -80,22 +90,21 @@
-
+
-
-
+
+
-
-
+
@@ -117,16 +126,17 @@
-
-
-
+
+
+
+
@@ -153,20 +163,20 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
\ No newline at end of file
diff --git a/dotnet/agent-framework-dotnet.slnx b/dotnet/agent-framework-dotnet.slnx
index 9c5f1a81f33..816ffdb678f 100644
--- a/dotnet/agent-framework-dotnet.slnx
+++ b/dotnet/agent-framework-dotnet.slnx
@@ -40,16 +40,19 @@
+
+
+
@@ -76,11 +79,21 @@
+
+
+
+
+
+
+
+
+
+
@@ -311,6 +324,7 @@
+
@@ -335,9 +349,12 @@
+
+
+
@@ -357,6 +374,7 @@
+
@@ -373,8 +391,11 @@
+
+
+
@@ -389,4 +410,4 @@
-
+
\ No newline at end of file
diff --git a/dotnet/global.json b/dotnet/global.json
index 402d97f6652..54533bf771b 100644
--- a/dotnet/global.json
+++ b/dotnet/global.json
@@ -1,7 +1,7 @@
{
"sdk": {
- "version": "9.0.300",
- "rollForward": "latestMajor",
+ "version": "10.0.100",
+ "rollForward": "minor",
"allowPrerelease": false
}
}
\ No newline at end of file
diff --git a/dotnet/nuget/nuget-package.props b/dotnet/nuget/nuget-package.props
index 2282c9ce133..ca719be243f 100644
--- a/dotnet/nuget/nuget-package.props
+++ b/dotnet/nuget/nuget-package.props
@@ -2,9 +2,9 @@
1.0.0
- $(VersionPrefix)-$(VersionSuffix).251114.1
- $(VersionPrefix)-preview.251114.1
- 1.0.0-preview.251114.1
+ $(VersionPrefix)-$(VersionSuffix).251125.1
+ $(VersionPrefix)-preview.251125.1
+ 1.0.0-preview.251125.1
Debug;Release;Publish
true
diff --git a/dotnet/samples/A2AClientServer/A2AClient/A2AClient.csproj b/dotnet/samples/A2AClientServer/A2AClient/A2AClient.csproj
index 77a05882319..6b88c5c697f 100644
--- a/dotnet/samples/A2AClientServer/A2AClient/A2AClient.csproj
+++ b/dotnet/samples/A2AClientServer/A2AClient/A2AClient.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
@@ -12,8 +12,6 @@
-
-
diff --git a/dotnet/samples/A2AClientServer/A2AServer/A2AServer.csproj b/dotnet/samples/A2AClientServer/A2AServer/A2AServer.csproj
index 8d67180f64f..0a3b170a0bd 100644
--- a/dotnet/samples/A2AClientServer/A2AServer/A2AServer.csproj
+++ b/dotnet/samples/A2AClientServer/A2AServer/A2AServer.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
@@ -11,8 +11,11 @@
-
-
+
+
+
+
+
diff --git a/dotnet/samples/A2AClientServer/README.md b/dotnet/samples/A2AClientServer/README.md
index 8bf5fc5816f..04b9968e760 100644
--- a/dotnet/samples/A2AClientServer/README.md
+++ b/dotnet/samples/A2AClientServer/README.md
@@ -103,7 +103,7 @@ dotnet run --urls "http://localhost:5002;https://localhost:5012" --agentId "
Exe
- net9.0
+ net10.0
enable
enable
a8b2e9f0-1ea3-4f18-9d41-42d1a6f8fe10
@@ -11,8 +11,6 @@
-
-
diff --git a/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs b/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs
index 0cbf15d6e45..3079bf14517 100644
--- a/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs
+++ b/dotnet/samples/AGUIClientServer/AGUIClient/Program.cs
@@ -201,12 +201,12 @@ private static string PrintArguments(IDictionary? arguments)
{
return "";
}
- var builder = new StringBuilder();
- builder.AppendLine();
+ var builder = new StringBuilder().AppendLine();
foreach (var kvp in arguments)
{
- builder.AppendLine($" Name: {kvp.Key}");
- builder.AppendLine($" Value: {kvp.Value}");
+ builder
+ .AppendLine($" Name: {kvp.Key}")
+ .AppendLine($" Value: {kvp.Value}");
}
return builder.ToString();
}
diff --git a/dotnet/samples/AGUIClientServer/AGUIDojoServer/AGUIDojoServer.csproj b/dotnet/samples/AGUIClientServer/AGUIDojoServer/AGUIDojoServer.csproj
index 0513374a935..cea8efff76e 100644
--- a/dotnet/samples/AGUIClientServer/AGUIDojoServer/AGUIDojoServer.csproj
+++ b/dotnet/samples/AGUIClientServer/AGUIDojoServer/AGUIDojoServer.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
b9c3f1e1-2fb4-5g29-0e52-53e2b7g9gf21
@@ -11,8 +11,6 @@
-
-
diff --git a/dotnet/samples/AGUIClientServer/AGUIDojoServer/Program.cs b/dotnet/samples/AGUIClientServer/AGUIDojoServer/Program.cs
index 57cc409c589..7e9ccca9b90 100644
--- a/dotnet/samples/AGUIClientServer/AGUIDojoServer/Program.cs
+++ b/dotnet/samples/AGUIClientServer/AGUIDojoServer/Program.cs
@@ -42,4 +42,4 @@
await app.RunAsync();
-public partial class Program { }
+public partial class Program;
diff --git a/dotnet/samples/AGUIClientServer/AGUIServer/AGUIServer.csproj b/dotnet/samples/AGUIClientServer/AGUIServer/AGUIServer.csproj
index c1bcd511da2..ccfe22923a8 100644
--- a/dotnet/samples/AGUIClientServer/AGUIServer/AGUIServer.csproj
+++ b/dotnet/samples/AGUIClientServer/AGUIServer/AGUIServer.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
a8b2e9f0-1ea3-4f18-9d41-42d1a6f8fe10
@@ -11,8 +11,6 @@
-
-
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj
index b4141ba166a..3f2a832a690 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/AgentWebChat.AgentHost.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
enable
enable
true
@@ -31,11 +31,4 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Custom/CustomAITools.cs b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Custom/CustomAITools.cs
index d3deb9162cb..14f0bcee41a 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Custom/CustomAITools.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Custom/CustomAITools.cs
@@ -4,9 +4,7 @@
namespace AgentWebChat.AgentHost.Custom;
-public class CustomAITool : AITool
-{
-}
+public class CustomAITool : AITool;
public class CustomFunctionTool : AIFunction
{
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.AppHost/AgentWebChat.AppHost.csproj b/dotnet/samples/AgentWebChat/AgentWebChat.AppHost/AgentWebChat.AppHost.csproj
index 464ba54db84..de87c119ecf 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.AppHost/AgentWebChat.AppHost.csproj
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.AppHost/AgentWebChat.AppHost.csproj
@@ -4,7 +4,7 @@
Exe
- net9.0
+ net10.0
enable
enable
true
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.ServiceDefaults/AgentWebChat.ServiceDefaults.csproj b/dotnet/samples/AgentWebChat/AgentWebChat.ServiceDefaults/AgentWebChat.ServiceDefaults.csproj
index 09110f11ad2..0c5573beac6 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.ServiceDefaults/AgentWebChat.ServiceDefaults.csproj
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.ServiceDefaults/AgentWebChat.ServiceDefaults.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
enable
enable
true
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs b/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs
index db690950da9..08dafea1299 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/A2AAgentClient.cs
@@ -25,7 +25,7 @@ public A2AAgentClient(ILogger logger, Uri baseUri)
this._uri = baseUri;
}
- public async override IAsyncEnumerable RunStreamingAsync(
+ public override async IAsyncEnumerable RunStreamingAsync(
string agentName,
IList messages,
string? threadId = null,
@@ -122,7 +122,7 @@ public async override IAsyncEnumerable RunStreamingAsync
}
}
- public async override Task GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
+ public override async Task GetAgentCardAsync(string agentName, CancellationToken cancellationToken = default)
{
this._logger.LogInformation("Retrieving agent card for {Agent}", agentName);
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/AgentWebChat.Web.csproj b/dotnet/samples/AgentWebChat/AgentWebChat.Web/AgentWebChat.Web.csproj
index 72541f046f1..fd26f561912 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.Web/AgentWebChat.Web.csproj
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/AgentWebChat.Web.csproj
@@ -1,7 +1,7 @@
- net9.0
+ net10.0
enable
enable
$(NoWarn);CA1812
@@ -15,11 +15,4 @@
-
-
-
-
-
-
-
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIChatCompletionsAgentClient.cs b/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIChatCompletionsAgentClient.cs
index ae71a876786..95e3d16fd4c 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIChatCompletionsAgentClient.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIChatCompletionsAgentClient.cs
@@ -16,7 +16,7 @@ namespace AgentWebChat.Web;
///
internal sealed class OpenAIChatCompletionsAgentClient(HttpClient httpClient) : AgentClientBase
{
- public async override IAsyncEnumerable RunStreamingAsync(
+ public override async IAsyncEnumerable RunStreamingAsync(
string agentName,
IList messages,
string? threadId = null,
diff --git a/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs b/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs
index bb7f6c151c3..7cc85b97c3b 100644
--- a/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs
+++ b/dotnet/samples/AgentWebChat/AgentWebChat.Web/OpenAIResponsesAgentClient.cs
@@ -15,7 +15,7 @@ namespace AgentWebChat.Web;
///
internal sealed class OpenAIResponsesAgentClient(HttpClient httpClient) : AgentClientBase
{
- public async override IAsyncEnumerable RunStreamingAsync(
+ public override async IAsyncEnumerable RunStreamingAsync(
string agentName,
IList messages,
string? threadId = null,
diff --git a/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj b/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj
index bce1b96f7b6..99f78cc1abb 100644
--- a/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj
+++ b/dotnet/samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj
index 4ec460450ab..af6fe8bcded 100644
--- a/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj
+++ b/dotnet/samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj
index 8698b0a7b87..394bf9cc35b 100644
--- a/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj
+++ b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs
index d4d5750df7f..5a6fbaf2034 100644
--- a/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs
+++ b/dotnet/samples/AzureFunctions/03_AgentOrchestration_Concurrency/Program.cs
@@ -36,8 +36,9 @@
.ConfigureFunctionsWebApplication()
.ConfigureDurableAgents(options =>
{
- options.AddAIAgent(physicistAgent);
- options.AddAIAgent(chemistAgent);
+ options
+ .AddAIAgent(physicistAgent)
+ .AddAIAgent(chemistAgent);
})
.Build();
diff --git a/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj b/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj
index 1971fb164a4..8dc1832227d 100644
--- a/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj
+++ b/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs b/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs
index e63d1a96677..971f862f21c 100644
--- a/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs
+++ b/dotnet/samples/AzureFunctions/04_AgentOrchestration_Conditionals/Program.cs
@@ -39,8 +39,9 @@
.ConfigureFunctionsWebApplication()
.ConfigureDurableAgents(options =>
{
- options.AddAIAgent(spamDetectionAgent);
- options.AddAIAgent(emailAssistantAgent);
+ options
+ .AddAIAgent(spamDetectionAgent)
+ .AddAIAgent(emailAssistantAgent);
})
.Build();
diff --git a/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj b/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj
index b7d211605fd..a240ea03946 100644
--- a/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj
+++ b/dotnet/samples/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj b/dotnet/samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj
index f6e6b7bbfc1..8711331aa24 100644
--- a/dotnet/samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj
+++ b/dotnet/samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj b/dotnet/samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj
index 8fa1f5f2e77..12795b2efbd 100644
--- a/dotnet/samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj
+++ b/dotnet/samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj
@@ -1,6 +1,6 @@
- net9.0
+ net10.0
v4
Exe
enable
diff --git a/dotnet/samples/AzureFunctions/README.md b/dotnet/samples/AzureFunctions/README.md
index 83d26a53b7f..e60b0f662e3 100644
--- a/dotnet/samples/AzureFunctions/README.md
+++ b/dotnet/samples/AzureFunctions/README.md
@@ -18,7 +18,7 @@ These samples are designed to be run locally in a cloned repository.
The following prerequisites are required to run the samples:
-- [.NET 9.0 SDK or later](https://dotnet.microsoft.com/download/dotnet)
+- [.NET 10.0 SDK or later](https://dotnet.microsoft.com/download/dotnet)
- [Azure Functions Core Tools](https://learn.microsoft.com/azure/azure-functions/functions-run-local) (version 4.x or later)
- [Azure CLI](https://learn.microsoft.com/cli/azure/install-azure-cli) installed and authenticated (`az login`) or an API key for the Azure OpenAI service
- [Azure OpenAI Service](https://learn.microsoft.com/azure/ai-services/openai/how-to/create-resource) with a deployed model (gpt-4o-mini or better is recommended)
diff --git a/dotnet/samples/Directory.Build.props b/dotnet/samples/Directory.Build.props
index dd86677c3e5..15880d4a8e5 100644
--- a/dotnet/samples/Directory.Build.props
+++ b/dotnet/samples/Directory.Build.props
@@ -5,7 +5,7 @@
false
false
- net472;net9.0
+ net10.0;net472
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj
index 2b89b20fbf1..d91b20e34bc 100644
--- a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -13,8 +13,6 @@
-
-
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md
index 6cbd56dca4c..c050ad08302 100644
--- a/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/README.md
@@ -7,7 +7,7 @@ and register these function tools with another AI agent so it can leverage the A
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Access to the A2A agent host service
**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/A2AAgent_PollingForTaskCompletion.csproj b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/A2AAgent_PollingForTaskCompletion.csproj
new file mode 100644
index 00000000000..1f36cef5766
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/A2AAgent_PollingForTaskCompletion.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs
new file mode 100644
index 00000000000..7b5934575cd
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/Program.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A AI agent.
+
+using A2A;
+using Microsoft.Agents.AI;
+
+var a2aAgentHost = Environment.GetEnvironmentVariable("A2A_AGENT_HOST") ?? throw new InvalidOperationException("A2A_AGENT_HOST is not set.");
+
+// Initialize an A2ACardResolver to get an A2A agent card.
+A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
+
+// Get the agent card
+AgentCard agentCard = await agentCardResolver.GetAgentCardAsync();
+
+// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
+AIAgent agent = agentCard.GetAIAgent();
+
+AgentThread thread = agent.GetNewThread();
+
+// Start the initial run with a long-running task.
+AgentRunResponse response = await agent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.", thread);
+
+// Poll until the response is complete.
+while (response.ContinuationToken is { } token)
+{
+ // Wait before polling again.
+ await Task.Delay(TimeSpan.FromSeconds(2));
+
+ // Continue with the token.
+ response = await agent.RunAsync(thread, options: new AgentRunOptions { ContinuationToken = token });
+}
+
+// Display the result
+Console.WriteLine(response);
diff --git a/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/README.md b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/README.md
new file mode 100644
index 00000000000..3e1160b5109
--- /dev/null
+++ b/dotnet/samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/README.md
@@ -0,0 +1,25 @@
+# Polling for A2A Agent Task Completion
+
+This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A AI agent, following the background responses pattern.
+
+The sample:
+
+- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
+- Sends a request to the agent that may take time to complete
+- Polls the agent at regular intervals using continuation tokens until a final response is received
+- Displays the final result
+
+This pattern is useful when an AI model cannot complete a complex task in a single response and needs multiple rounds of processing.
+
+# Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 10.0 SDK or later
+- An A2A agent server running and accessible via HTTP
+
+Set the following environment variable:
+
+```powershell
+$env:A2A_AGENT_HOST="http://localhost:5000" # Replace with your A2A agent server host
+```
diff --git a/dotnet/samples/GettingStarted/A2A/README.md b/dotnet/samples/GettingStarted/A2A/README.md
index 3ddac959967..b513ffa9293 100644
--- a/dotnet/samples/GettingStarted/A2A/README.md
+++ b/dotnet/samples/GettingStarted/A2A/README.md
@@ -14,6 +14,7 @@ See the README.md for each sample for the prerequisites for that sample.
|Sample|Description|
|---|---|
|[A2A Agent As Function Tools](./A2AAgent_AsFunctionTools/)|This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent, and register these function tools with another AI agent so it can leverage the A2A agent's skills.|
+|[A2A Agent Polling For Task Completion](./A2AAgent_PollingForTaskCompletion/)|This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A agent.|
## Running the samples from the console
diff --git a/dotnet/samples/GettingStarted/AgentOpenTelemetry/AgentOpenTelemetry.csproj b/dotnet/samples/GettingStarted/AgentOpenTelemetry/AgentOpenTelemetry.csproj
index f9b7b3da2a6..e194fec9c25 100644
--- a/dotnet/samples/GettingStarted/AgentOpenTelemetry/AgentOpenTelemetry.csproj
+++ b/dotnet/samples/GettingStarted/AgentOpenTelemetry/AgentOpenTelemetry.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -22,7 +22,6 @@
-
diff --git a/dotnet/samples/GettingStarted/AgentOpenTelemetry/README.md b/dotnet/samples/GettingStarted/AgentOpenTelemetry/README.md
index 8f675a20d1e..229d37dca6a 100644
--- a/dotnet/samples/GettingStarted/AgentOpenTelemetry/README.md
+++ b/dotnet/samples/GettingStarted/AgentOpenTelemetry/README.md
@@ -22,7 +22,7 @@ graph TD
## Prerequisites
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- Docker installed (for running Aspire Dashboard)
@@ -71,7 +71,7 @@ If you prefer to run the components manually:
#### Step 1: Start the Aspire Dashboard via Docker
```powershell
-docker run -d --name aspire-dashboard -p 4318:18888 -p 4317:18889 -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true mcr.microsoft.com/dotnet/aspire-dashboard:9.0
+docker run -d --name aspire-dashboard -p 4318:18888 -p 4317:18889 -e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true mcr.microsoft.com/dotnet/aspire-dashboard:latest
```
#### Step 2: Access the Dashboard
@@ -207,7 +207,7 @@ If you encounter port binding errors, try:
- Ensure the Azure OpenAI deployment name matches your actual deployment
### Build Issues
-- Ensure you're using .NET 9.0 SDK
+- Ensure you're using .NET 10.0 SDK
- Run `dotnet restore` if you encounter package restore issues
- Check that all project references are correctly resolved
diff --git a/dotnet/samples/GettingStarted/AgentOpenTelemetry/start-demo.ps1 b/dotnet/samples/GettingStarted/AgentOpenTelemetry/start-demo.ps1
index 8445d1e7e32..7af1c9d8aee 100644
--- a/dotnet/samples/GettingStarted/AgentOpenTelemetry/start-demo.ps1
+++ b/dotnet/samples/GettingStarted/AgentOpenTelemetry/start-demo.ps1
@@ -65,7 +65,7 @@ $dockerResult = docker run -d `
-p 4317:18889 `
-e DOTNET_DASHBOARD_UNSECURED_ALLOW_ANONYMOUS=true `
--restart unless-stopped `
- mcr.microsoft.com/dotnet/aspire-dashboard:9.0
+ mcr.microsoft.com/dotnet/aspire-dashboard:latest
if ($LASTEXITCODE -ne 0) {
Write-Host "Failed to start Aspire Dashboard container" -ForegroundColor Red
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj
index e01a9f74587..7236ee50447 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -10,8 +10,6 @@
-
-
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/README.md
index ce7a9174b06..536514306ed 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_A2A/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Access to the A2A agent host service
**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be spun up locally by following the guidelines at: https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/README.md
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj
new file mode 100644
index 00000000000..eb29d1d310f
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj
@@ -0,0 +1,21 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+ $(NoWarn);IDE0059
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs
new file mode 100644
index 00000000000..df070c335b2
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Program.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create and use an AI agent with Anthropic as the backend.
+
+using System.Net.Http.Headers;
+using Anthropic;
+using Anthropic.Foundry;
+using Azure.Core;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Sample;
+
+var deploymentName = Environment.GetEnvironmentVariable("ANTHROPIC_DEPLOYMENT_NAME") ?? "claude-haiku-4-5";
+
+// The resource is the subdomain name / first name coming before '.services.ai.azure.com' in the endpoint Uri
+// ie: https://(resource name).services.ai.azure.com/anthropic/v1/chat/completions
+string? resource = Environment.GetEnvironmentVariable("ANTHROPIC_RESOURCE");
+string? apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY");
+
+const string JokerInstructions = "You are good at telling jokes.";
+const string JokerName = "JokerAgent";
+
+AnthropicClient? client = (resource is null)
+ ? new AnthropicClient() { APIKey = apiKey ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is required when no ANTHROPIC_RESOURCE is provided") } // If no resource is provided, use Anthropic public API
+ : (apiKey is not null)
+ ? new AnthropicFoundryClient(new AnthropicFoundryApiKeyCredentials(apiKey, resource)) // If an apiKey is provided, use Foundry with ApiKey authentication
+ : new AnthropicFoundryClient(new AnthropicAzureTokenCredential(new AzureCliCredential(), resource)); // Otherwise, use Foundry with Azure Client authentication
+
+AIAgent agent = client.CreateAIAgent(model: deploymentName, instructions: JokerInstructions, name: JokerName);
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent.RunAsync("Tell me a joke about a pirate."));
+
+namespace Sample
+{
+ ///
+ /// Provides methods for invoking the Azure hosted Anthropic models using types.
+ ///
+ public sealed class AnthropicAzureTokenCredential : IAnthropicFoundryCredentials
+ {
+ private readonly TokenCredential _tokenCredential;
+ private readonly Lock _lock = new();
+ private AccessToken? _cachedAccessToken;
+
+ ///
+ public string ResourceName { get; }
+
+ ///
+ /// Creates a new instance of the .
+ ///
+ /// The credential provider. Use any specialization of to get your access token in supported environments.
+ /// The service resource subdomain name to use in the anthropic azure endpoint
+ internal AnthropicAzureTokenCredential(TokenCredential tokenCredential, string resourceName)
+ {
+ this.ResourceName = resourceName ?? throw new ArgumentNullException(nameof(resourceName));
+ this._tokenCredential = tokenCredential ?? throw new ArgumentNullException(nameof(tokenCredential));
+ }
+
+ ///
+ public void Apply(HttpRequestMessage requestMessage)
+ {
+ lock (this._lock)
+ {
+ // Add a 5-minute buffer to avoid using tokens that are about to expire
+ if (this._cachedAccessToken is null || this._cachedAccessToken.Value.ExpiresOn <= DateTimeOffset.Now.AddMinutes(5))
+ {
+ this._cachedAccessToken = this._tokenCredential.GetToken(new TokenRequestContext(scopes: ["https://ai.azure.com/.default"]), CancellationToken.None);
+ }
+ }
+
+ requestMessage.Headers.Authorization = new AuthenticationHeaderValue("bearer", this._cachedAccessToken.Value.Token);
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/README.md
new file mode 100644
index 00000000000..afcf3915721
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Anthropic/README.md
@@ -0,0 +1,53 @@
+# Creating an AIAgent with Anthropic
+
+This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service.
+
+The sample supports three deployment scenarios:
+
+1. **Anthropic Public API** - Direct connection to Anthropic's public API
+2. **Azure Foundry with API Key** - Anthropic models deployed through Azure Foundry using API key authentication
+3. **Azure Foundry with Azure CLI** - Anthropic models deployed through Azure Foundry using Azure CLI credentials
+
+## Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 8.0 SDK or later
+
+### For Anthropic Public API
+
+- Anthropic API key
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
+$env:ANTHROPIC_DEPLOYMENT_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
+```
+
+### For Azure Foundry with API Key
+
+- Azure Foundry service endpoint and deployment configured
+- Anthropic API key
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Azure Foundry resource name (subdomain before .services.ai.azure.com)
+$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
+$env:ANTHROPIC_DEPLOYMENT_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
+```
+
+### For Azure Foundry with Azure CLI
+
+- Azure Foundry service endpoint and deployment configured
+- Azure CLI installed and authenticated (for Azure credential authentication)
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_RESOURCE="your-foundry-resource-name" # Replace with your Azure Foundry resource name (subdomain before .services.ai.azure.com)
+$env:ANTHROPIC_DEPLOYMENT_NAME="claude-haiku-4-5" # Optional, defaults to claude-haiku-4-5
+```
+
+**Note**: When using Azure Foundry with Azure CLI, make sure you're logged in with `az login` and have access to the Azure Foundry resource. For more information, see the [Azure CLI documentation](https://learn.microsoft.com/cli/azure/authenticate-azure-cli-interactively).
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj
index 11c7beb3bf7..d40e93232b9 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
index df0854ba2f5..d6b54976016 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md
@@ -1,8 +1,18 @@
+# Classic Foundry Agents
+
+This sample demonstrates how to create an agent using the classic Foundry Agents experience.
+
+# Classic vs New Foundry Agents
+
+Below is a comparison between the classic and new Foundry Agents approaches:
+
+[Migration Guide](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/migrate?view=foundry)
+
# Prerequisites
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj
index 057a0fc507d..a8deaa57b59 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs
index dd4a011e4d3..2c2b9d19699 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Program.cs
@@ -10,35 +10,34 @@
var endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
-const string JokerInstructions = "You are good at telling jokes.";
const string JokerName = "JokerAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
var aiProjectClient = new AIProjectClient(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
-var agentVersionCreationOptions = new AgentVersionCreationOptions(new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructions });
+var agentVersionCreationOptions = new AgentVersionCreationOptions(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are good at telling jokes." });
// Azure.AI.Agents SDK creates and manages agent by name and versions.
// You can create a server side agent version with the Azure.AI.Agents SDK client below.
-var agentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: JokerName, options: agentVersionCreationOptions);
+var createdAgentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: JokerName, options: agentVersionCreationOptions);
// Note:
// agentVersion.Id = ":",
// agentVersion.Version = ,
// agentVersion.Name =
-// You can retrieve an AIAgent for a already created server side agent version.
-AIAgent jokerAgentV1 = aiProjectClient.GetAIAgent(agentVersion);
+// You can retrieve an AIAgent for an already created server side agent version.
+AIAgent existingJokerAgent = aiProjectClient.GetAIAgent(createdAgentVersion);
-// You can also create another AIAgent version (V2) by providing the same name with a different definition.
-AIAgent jokerAgentV2 = aiProjectClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructions + "V2");
+// You can also create another AIAgent version by providing the same name with a different definition.
+AIAgent newJokerAgent = aiProjectClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: "You are extremely hilarious at telling jokes.");
// You can also get the AIAgent latest version just providing its name.
AIAgent jokerAgentLatest = aiProjectClient.GetAIAgent(name: JokerName);
-var latestVersion = jokerAgentLatest.GetService()!;
+var latestAgentVersion = jokerAgentLatest.GetService()!;
// The AIAgent version can be accessed via the GetService method.
-Console.WriteLine($"Latest agent version id: {latestVersion.Id}");
+Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
// Once you have the AIAgent, you can invoke it like any other AIAgent.
AgentThread thread = jokerAgentLatest.GetNewThread();
@@ -47,5 +46,5 @@
// This will use the same thread to continue the conversation.
Console.WriteLine(await jokerAgentLatest.RunAsync("Now tell me a joke about a cat and a dog using last joke as the anchor.", thread));
-// Cleanup by agent name removes both agent versions created (jokerAgentV1 + jokerAgentV2).
-aiProjectClient.Agents.DeleteAgent(jokerAgentV1.Name);
+// Cleanup by agent name removes both agent versions created.
+aiProjectClient.Agents.DeleteAgent(existingJokerAgent.Name);
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/README.md
index df0854ba2f5..7e4a28f6a10 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/README.md
@@ -1,8 +1,18 @@
+# New Foundry Agents
+
+This sample demonstrates how to create an agent using the new Foundry Agents experience.
+
+# Classic vs New Foundry Agents
+
+Below is a comparison between the classic and new Foundry Agents approaches:
+
+[Migration Guide](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/migrate?view=foundry)
+
# Prerequisites
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj
index cd545ddb486..0c4701fafd8 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/README.md
index 9147bda1da6..cff8767770b 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/README.md
@@ -10,7 +10,7 @@ You could use models from Microsoft, OpenAI, DeepSeek, Hugging Face, Meta, xAI o
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure AI Foundry resource
- A model deployment in your Azure AI Foundry resource. This example defaults to using the `Phi-4-mini-instruct` model,
so if you want to use a different model, ensure that you set your `AZURE_FOUNDRY_MODEL_DEPLOYMENT` environment
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj
index 0eacdab258a..41aafe34372 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/README.md
index 1278eb59e5b..4cacf30131d 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj
index 0eacdab258a..41aafe34372 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/README.md
index 1278eb59e5b..4cacf30131d 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj
index aa1c382aef6..945912bfd49 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs
index fd00618f5fe..8f1039251d0 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Program.cs
@@ -39,11 +39,16 @@ public override async Task RunAsync(IEnumerable m
// Create a thread if the user didn't supply one.
thread ??= this.GetNewThread();
+ if (thread is not CustomAgentThread typedThread)
+ {
+ throw new ArgumentException($"The provided thread is not of type {nameof(CustomAgentThread)}.", nameof(thread));
+ }
+
// Clone the input messages and turn them into response messages with upper case text.
List responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList();
// Notify the thread of the input and output messages.
- await NotifyThreadOfNewMessagesAsync(thread, messages.Concat(responseMessages), cancellationToken);
+ await typedThread.MessageStore.AddMessagesAsync(messages.Concat(responseMessages), cancellationToken);
return new AgentRunResponse
{
@@ -58,11 +63,16 @@ public override async IAsyncEnumerable RunStreamingAsync
// Create a thread if the user didn't supply one.
thread ??= this.GetNewThread();
+ if (thread is not CustomAgentThread typedThread)
+ {
+ throw new ArgumentException($"The provided thread is not of type {nameof(CustomAgentThread)}.", nameof(thread));
+ }
+
// Clone the input messages and turn them into response messages with upper case text.
List responseMessages = CloneAndToUpperCase(messages, this.DisplayName).ToList();
// Notify the thread of the input and output messages.
- await NotifyThreadOfNewMessagesAsync(thread, messages.Concat(responseMessages), cancellationToken);
+ await typedThread.MessageStore.AddMessagesAsync(messages.Concat(responseMessages), cancellationToken);
foreach (var message in responseMessages)
{
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj
new file mode 100644
index 00000000000..d01f015a4b9
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net8.0;net9.0;net10.0
+
+ enable
+ enable
+ $(NoWarn);IDE0059;NU1510
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GeminiChatClient.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GeminiChatClient.cs
new file mode 100644
index 00000000000..2a1d47a456a
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GeminiChatClient.cs
@@ -0,0 +1,558 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Runtime.CompilerServices;
+using Google.Apis.Util;
+using Google.GenAI;
+using Google.GenAI.Types;
+
+namespace Microsoft.Extensions.AI;
+
+/// Provides an implementation based on .
+internal sealed class GoogleGenAIChatClient : IChatClient
+{
+ /// The wrapped instance (optional).
+ private readonly Client? _client;
+
+ /// The wrapped instance.
+ private readonly Models _models;
+
+ /// The default model that should be used when no override is specified.
+ private readonly string? _defaultModelId;
+
+ /// Lazily-initialized metadata describing the implementation.
+ private ChatClientMetadata? _metadata;
+
+ /// Initializes a new instance.
+ public GoogleGenAIChatClient(Client client, string? defaultModelId)
+ {
+ this._client = client;
+ this._models = client.Models;
+ this._defaultModelId = defaultModelId;
+ }
+
+ /// Initializes a new instance.
+ public GoogleGenAIChatClient(Models client, string? defaultModelId)
+ {
+ this._models = client;
+ this._defaultModelId = defaultModelId;
+ }
+
+ ///
+ public async Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ Utilities.ThrowIfNull(messages, nameof(messages));
+
+ // Create the request.
+ (string? modelId, List contents, GenerateContentConfig config) = this.CreateRequest(messages, options);
+
+ // Send it.
+ GenerateContentResponse generateResult = await this._models.GenerateContentAsync(modelId!, contents, config).ConfigureAwait(false);
+
+ // Create the response.
+ ChatResponse chatResponse = new(new ChatMessage(ChatRole.Assistant, new List()))
+ {
+ CreatedAt = generateResult.CreateTime is { } dt ? new DateTimeOffset(dt) : null,
+ ModelId = !string.IsNullOrWhiteSpace(generateResult.ModelVersion) ? generateResult.ModelVersion : modelId,
+ RawRepresentation = generateResult,
+ ResponseId = generateResult.ResponseId,
+ };
+
+ // Populate the response messages.
+ chatResponse.FinishReason = PopulateResponseContents(generateResult, chatResponse.Messages[0].Contents);
+
+ // Populate usage information if there is any.
+ if (generateResult.UsageMetadata is { } usageMetadata)
+ {
+ chatResponse.Usage = ExtractUsageDetails(usageMetadata);
+ }
+
+ // Return the response.
+ return chatResponse;
+ }
+
+ ///
+ public async IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ Utilities.ThrowIfNull(messages, nameof(messages));
+
+ // Create the request.
+ (string? modelId, List contents, GenerateContentConfig config) = this.CreateRequest(messages, options);
+
+ // Send it, and process the results.
+ await foreach (GenerateContentResponse generateResult in this._models.GenerateContentStreamAsync(modelId!, contents, config).WithCancellation(cancellationToken).ConfigureAwait(false))
+ {
+ // Create a response update for each result in the stream.
+ ChatResponseUpdate responseUpdate = new(ChatRole.Assistant, new List())
+ {
+ CreatedAt = generateResult.CreateTime is { } dt ? new DateTimeOffset(dt) : null,
+ ModelId = !string.IsNullOrWhiteSpace(generateResult.ModelVersion) ? generateResult.ModelVersion : modelId,
+ RawRepresentation = generateResult,
+ ResponseId = generateResult.ResponseId,
+ };
+
+ // Populate the response update contents.
+ responseUpdate.FinishReason = PopulateResponseContents(generateResult, responseUpdate.Contents);
+
+ // Populate usage information if there is any.
+ if (generateResult.UsageMetadata is { } usageMetadata)
+ {
+ responseUpdate.Contents.Add(new UsageContent(ExtractUsageDetails(usageMetadata)));
+ }
+
+ // Yield the update.
+ yield return responseUpdate;
+ }
+ }
+
+ ///
+ public object? GetService(System.Type serviceType, object? serviceKey = null)
+ {
+ Utilities.ThrowIfNull(serviceType, nameof(serviceType));
+
+ if (serviceKey is null)
+ {
+ // If there's a request for metadata, lazily-initialize it and return it. We don't need to worry about race conditions,
+ // as there's no requirement that the same instance be returned each time, and creation is idempotent.
+ if (serviceType == typeof(ChatClientMetadata))
+ {
+ return this._metadata ??= new("gcp.gen_ai", new("https://generativelanguage.googleapis.com/"), defaultModelId: this._defaultModelId);
+ }
+
+ // Allow a consumer to "break glass" and access the underlying client if they need it.
+ if (serviceType.IsInstanceOfType(this._models))
+ {
+ return this._models;
+ }
+
+ if (this._client is not null && serviceType.IsInstanceOfType(this._client))
+ {
+ return this._client;
+ }
+
+ if (serviceType.IsInstanceOfType(this))
+ {
+ return this;
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ void IDisposable.Dispose() { /* nop */ }
+
+ /// Creates the message parameters for from and .
+ private (string? ModelId, List Contents, GenerateContentConfig Config) CreateRequest(IEnumerable messages, ChatOptions? options)
+ {
+ // Create the GenerateContentConfig object. If the options contains a RawRepresentationFactory, try to use it to
+ // create the request instance, allowing the caller to populate it with GenAI-specific options. Otherwise, create
+ // a new instance directly.
+ string? model = this._defaultModelId;
+ List contents = new();
+ GenerateContentConfig config = options?.RawRepresentationFactory?.Invoke(this) as GenerateContentConfig ?? new();
+
+ if (options is not null)
+ {
+ if (options.FrequencyPenalty is { } frequencyPenalty)
+ {
+ config.FrequencyPenalty ??= frequencyPenalty;
+ }
+
+ if (options.Instructions is { } instructions)
+ {
+ ((config.SystemInstruction ??= new()).Parts ??= new()).Add(new() { Text = instructions });
+ }
+
+ if (options.MaxOutputTokens is { } maxOutputTokens)
+ {
+ config.MaxOutputTokens ??= maxOutputTokens;
+ }
+
+ if (!string.IsNullOrWhiteSpace(options.ModelId))
+ {
+ model = options.ModelId;
+ }
+
+ if (options.PresencePenalty is { } presencePenalty)
+ {
+ config.PresencePenalty ??= presencePenalty;
+ }
+
+ if (options.Seed is { } seed)
+ {
+ config.Seed ??= (int)seed;
+ }
+
+ if (options.StopSequences is { } stopSequences)
+ {
+ (config.StopSequences ??= new()).AddRange(stopSequences);
+ }
+
+ if (options.Temperature is { } temperature)
+ {
+ config.Temperature ??= temperature;
+ }
+
+ if (options.TopP is { } topP)
+ {
+ config.TopP ??= topP;
+ }
+
+ if (options.TopK is { } topK)
+ {
+ config.TopK ??= topK;
+ }
+
+ // Populate tools. Each kind of tool is added on its own, except for function declarations,
+ // which are grouped into a single FunctionDeclaration.
+ List? functionDeclarations = null;
+ if (options.Tools is { } tools)
+ {
+ foreach (var tool in tools)
+ {
+ switch (tool)
+ {
+ case AIFunctionDeclaration af:
+ functionDeclarations ??= new();
+ functionDeclarations.Add(new()
+ {
+ Name = af.Name,
+ Description = af.Description ?? "",
+ ParametersJsonSchema = af.JsonSchema,
+ });
+ break;
+
+ case HostedCodeInterpreterTool:
+ (config.Tools ??= new()).Add(new() { CodeExecution = new() });
+ break;
+
+ case HostedFileSearchTool:
+ (config.Tools ??= new()).Add(new() { Retrieval = new() });
+ break;
+
+ case HostedWebSearchTool:
+ (config.Tools ??= new()).Add(new() { GoogleSearch = new() });
+ break;
+ }
+ }
+ }
+
+ if (functionDeclarations is { Count: > 0 })
+ {
+ Tool functionTools = new();
+ (functionTools.FunctionDeclarations ??= new()).AddRange(functionDeclarations);
+ (config.Tools ??= new()).Add(functionTools);
+ }
+
+ // Transfer over the tool mode if there are any tools.
+ if (options.ToolMode is { } toolMode && config.Tools?.Count > 0)
+ {
+ switch (toolMode)
+ {
+ case NoneChatToolMode:
+ config.ToolConfig = new() { FunctionCallingConfig = new() { Mode = FunctionCallingConfigMode.NONE } };
+ break;
+
+ case AutoChatToolMode:
+ config.ToolConfig = new() { FunctionCallingConfig = new() { Mode = FunctionCallingConfigMode.AUTO } };
+ break;
+
+ case RequiredChatToolMode required:
+ config.ToolConfig = new() { FunctionCallingConfig = new() { Mode = FunctionCallingConfigMode.ANY } };
+ if (required.RequiredFunctionName is not null)
+ {
+ ((config.ToolConfig.FunctionCallingConfig ??= new()).AllowedFunctionNames ??= new()).Add(required.RequiredFunctionName);
+ }
+ break;
+ }
+ }
+
+ // Set the response format if specified.
+ if (options.ResponseFormat is ChatResponseFormatJson responseFormat)
+ {
+ config.ResponseMimeType = "application/json";
+ if (responseFormat.Schema is { } schema)
+ {
+ config.ResponseJsonSchema = schema;
+ }
+ }
+ }
+
+ // Transfer messages to request, handling system messages specially
+ Dictionary? callIdToFunctionNames = null;
+ foreach (var message in messages)
+ {
+ if (message.Role == ChatRole.System)
+ {
+ string instruction = message.Text;
+ if (!string.IsNullOrWhiteSpace(instruction))
+ {
+ ((config.SystemInstruction ??= new()).Parts ??= new()).Add(new() { Text = instruction });
+ }
+
+ continue;
+ }
+
+ Content content = new() { Role = message.Role == ChatRole.Assistant ? "model" : "user" };
+ content.Parts ??= new();
+ AddPartsForAIContents(ref callIdToFunctionNames, message.Contents, content.Parts);
+
+ contents.Add(content);
+ }
+
+ // Make sure the request contains at least one content part (the request would always fail if empty).
+ if (!contents.SelectMany(c => c.Parts ?? Enumerable.Empty()).Any())
+ {
+ contents.Add(new() { Role = "user", Parts = new() { { new() { Text = "" } } } });
+ }
+
+ return (model, contents, config);
+ }
+
+ /// Creates s for and adds them to .
+ private static void AddPartsForAIContents(ref Dictionary? callIdToFunctionNames, IList contents, List parts)
+ {
+ for (int i = 0; i < contents.Count; i++)
+ {
+ var content = contents[i];
+
+ byte[]? thoughtSignature = null;
+ if (content is not TextReasoningContent { ProtectedData: not null } &&
+ i + 1 < contents.Count &&
+ contents[i + 1] is TextReasoningContent nextReasoning &&
+ string.IsNullOrWhiteSpace(nextReasoning.Text) &&
+ nextReasoning.ProtectedData is { } protectedData)
+ {
+ i++;
+ thoughtSignature = Convert.FromBase64String(protectedData);
+ }
+
+ Part? part = null;
+ switch (content)
+ {
+ case TextContent textContent:
+ part = new() { Text = textContent.Text };
+ break;
+
+ case TextReasoningContent reasoningContent:
+ part = new()
+ {
+ Thought = true,
+ Text = !string.IsNullOrWhiteSpace(reasoningContent.Text) ? reasoningContent.Text : null,
+ ThoughtSignature = reasoningContent.ProtectedData is not null ? Convert.FromBase64String(reasoningContent.ProtectedData) : null,
+ };
+ break;
+
+ case DataContent dataContent:
+ part = new()
+ {
+ InlineData = new()
+ {
+ MimeType = dataContent.MediaType,
+ Data = dataContent.Data.ToArray(),
+ DisplayName = dataContent.Name,
+ }
+ };
+ break;
+
+ case UriContent uriContent:
+ part = new()
+ {
+ FileData = new()
+ {
+ FileUri = uriContent.Uri.AbsoluteUri,
+ MimeType = uriContent.MediaType,
+ }
+ };
+ break;
+
+ case FunctionCallContent functionCallContent:
+ (callIdToFunctionNames ??= new())[functionCallContent.CallId] = functionCallContent.Name;
+ callIdToFunctionNames[""] = functionCallContent.Name; // track last function name in case calls don't have IDs
+
+ part = new()
+ {
+ FunctionCall = new()
+ {
+ Id = functionCallContent.CallId,
+ Name = functionCallContent.Name,
+ Args = functionCallContent.Arguments is null ? null : functionCallContent.Arguments as Dictionary ?? new(functionCallContent.Arguments!),
+ }
+ };
+ break;
+
+ case FunctionResultContent functionResultContent:
+ part = new()
+ {
+ FunctionResponse = new()
+ {
+ Id = functionResultContent.CallId,
+ Name = callIdToFunctionNames?.TryGetValue(functionResultContent.CallId, out string? functionName) is true || callIdToFunctionNames?.TryGetValue("", out functionName) is true ?
+ functionName :
+ null,
+ Response = functionResultContent.Result is null ? null : new() { ["result"] = functionResultContent.Result },
+ }
+ };
+ break;
+ }
+
+ if (part is not null)
+ {
+ part.ThoughtSignature ??= thoughtSignature;
+ parts.Add(part);
+ }
+ }
+ }
+
+ /// Creates s for and adds them to .
+ private static void AddAIContentsForParts(List parts, IList contents)
+ {
+ foreach (var part in parts)
+ {
+ AIContent? content = null;
+
+ if (!string.IsNullOrEmpty(part.Text))
+ {
+ content = part.Thought is true ?
+ new TextReasoningContent(part.Text) :
+ new TextContent(part.Text);
+ }
+ else if (part.InlineData is { } inlineData)
+ {
+ content = new DataContent(inlineData.Data, inlineData.MimeType ?? "application/octet-stream")
+ {
+ Name = inlineData.DisplayName,
+ };
+ }
+ else if (part.FileData is { FileUri: not null } fileData)
+ {
+ content = new UriContent(new Uri(fileData.FileUri), fileData.MimeType ?? "application/octet-stream");
+ }
+ else if (part.FunctionCall is { Name: not null } functionCall)
+ {
+ content = new FunctionCallContent(functionCall.Id ?? "", functionCall.Name, functionCall.Args!);
+ }
+ else if (part.FunctionResponse is { } functionResponse)
+ {
+ content = new FunctionResultContent(
+ functionResponse.Id ?? "",
+ functionResponse.Response?.TryGetValue("output", out var output) is true ? output :
+ functionResponse.Response?.TryGetValue("error", out var error) is true ? error :
+ null);
+ }
+
+ if (content is not null)
+ {
+ content.RawRepresentation = part;
+ contents.Add(content);
+
+ if (part.ThoughtSignature is { } thoughtSignature)
+ {
+ contents.Add(new TextReasoningContent(null)
+ {
+ ProtectedData = Convert.ToBase64String(thoughtSignature),
+ });
+ }
+ }
+ }
+ }
+
+ private static ChatFinishReason? PopulateResponseContents(GenerateContentResponse generateResult, IList responseContents)
+ {
+ ChatFinishReason? finishReason = null;
+
+ // Populate the response messages. There should only be at most one candidate, but if there are more, ignore all but the first.
+ if (generateResult.Candidates is { Count: > 0 } &&
+ generateResult.Candidates[0] is { Content: { } candidateContent } candidate)
+ {
+ // Grab the finish reason if one exists.
+ finishReason = ConvertFinishReason(candidate.FinishReason);
+
+ // Add all of the response content parts as AIContents.
+ if (candidateContent.Parts is { } parts)
+ {
+ AddAIContentsForParts(parts, responseContents);
+ }
+
+ // Add any citation metadata.
+ if (candidate.CitationMetadata is { Citations: { Count: > 0 } citations } &&
+ responseContents.OfType().FirstOrDefault() is TextContent textContent)
+ {
+ foreach (var citation in citations)
+ {
+ textContent.Annotations = new List()
+ {
+ new CitationAnnotation()
+ {
+ Title = citation.Title,
+ Url = Uri.TryCreate(citation.Uri, UriKind.Absolute, out Uri? uri) ? uri : null,
+ AnnotatedRegions = new List()
+ {
+ new TextSpanAnnotatedRegion()
+ {
+ StartIndex = citation.StartIndex,
+ EndIndex = citation.EndIndex,
+ }
+ },
+ }
+ };
+ }
+ }
+ }
+
+ // Populate error information if there is any.
+ if (generateResult.PromptFeedback is { } promptFeedback)
+ {
+ responseContents.Add(new ErrorContent(promptFeedback.BlockReasonMessage));
+ }
+
+ return finishReason;
+ }
+
+ /// Creates an M.E.AI from a Google .
+ private static ChatFinishReason? ConvertFinishReason(FinishReason? finishReason)
+ {
+ return finishReason switch
+ {
+ null => null,
+
+ FinishReason.MAX_TOKENS =>
+ ChatFinishReason.Length,
+
+ FinishReason.MALFORMED_FUNCTION_CALL or
+ FinishReason.UNEXPECTED_TOOL_CALL =>
+ ChatFinishReason.ToolCalls,
+
+ FinishReason.FINISH_REASON_UNSPECIFIED or
+ FinishReason.STOP =>
+ ChatFinishReason.Stop,
+
+ _ => ChatFinishReason.ContentFilter,
+ };
+ }
+
+ /// Creates a populated from the supplied .
+ private static UsageDetails ExtractUsageDetails(GenerateContentResponseUsageMetadata usageMetadata)
+ {
+ UsageDetails details = new()
+ {
+ InputTokenCount = usageMetadata.PromptTokenCount,
+ OutputTokenCount = usageMetadata.CandidatesTokenCount,
+ TotalTokenCount = usageMetadata.TotalTokenCount,
+ };
+
+ AddIfPresent(nameof(usageMetadata.CachedContentTokenCount), usageMetadata.CachedContentTokenCount);
+ AddIfPresent(nameof(usageMetadata.ThoughtsTokenCount), usageMetadata.ThoughtsTokenCount);
+ AddIfPresent(nameof(usageMetadata.ToolUsePromptTokenCount), usageMetadata.ToolUsePromptTokenCount);
+
+ return details;
+
+ void AddIfPresent(string key, int? value)
+ {
+ if (value is int i)
+ {
+ (details.AdditionalCounts ??= new())[key] = i;
+ }
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GoogleGenAIExtensions.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GoogleGenAIExtensions.cs
new file mode 100644
index 00000000000..b1044fa3731
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/GoogleGenAIExtensions.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Google.Apis.Util;
+using Google.GenAI;
+
+namespace Microsoft.Extensions.AI;
+
+/// Provides implementations of Microsoft.Extensions.AI abstractions based on .
+public static class GoogleGenAIExtensions
+{
+ ///
+ /// Creates an wrapper around the specified .
+ ///
+ /// The to wrap.
+ /// The default model ID to use for chat requests if not specified in .
+ /// An that wraps the specified client.
+ /// is .
+ public static IChatClient AsIChatClient(this Client client, string? defaultModelId = null)
+ {
+ Utilities.ThrowIfNull(client, nameof(client));
+ return new GoogleGenAIChatClient(client, defaultModelId);
+ }
+
+ ///
+ /// Creates an wrapper around the specified .
+ ///
+ /// The client to wrap.
+ /// The default model ID to use for chat requests if not specified in .
+ /// An that wraps the specified client.
+ /// is .
+ public static IChatClient AsIChatClient(this Models models, string? defaultModelId = null)
+ {
+ Utilities.ThrowIfNull(models, nameof(models));
+ return new GoogleGenAIChatClient(models, defaultModelId);
+ }
+}
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Program.cs
new file mode 100644
index 00000000000..db633dc47d3
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Program.cs
@@ -0,0 +1,36 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create and use an AI agent with Google Gemini
+
+using Google.GenAI;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Mscc.GenerativeAI.Microsoft;
+
+const string JokerInstructions = "You are good at telling jokes.";
+const string JokerName = "JokerAgent";
+
+string apiKey = Environment.GetEnvironmentVariable("GOOGLE_GENAI_API_KEY") ?? throw new InvalidOperationException("Please set the GOOGLE_GENAI_API_KEY environment variable.");
+string model = Environment.GetEnvironmentVariable("GOOGLE_GENAI_MODEL") ?? "gemini-2.5-fast";
+
+// Using a Google GenAI IChatClient implementation
+// Until the PR https://github.com/googleapis/dotnet-genai/pull/81 is not merged this option
+// requires usage of also both GeminiChatClient.cs and GoogleGenAIExtensions.cs polyfills to work.
+
+ChatClientAgent agentGenAI = new(
+ new Client(vertexAI: false, apiKey: apiKey).AsIChatClient(model),
+ name: JokerName,
+ instructions: JokerInstructions);
+
+AgentRunResponse response = await agentGenAI.RunAsync("Tell me a joke about a pirate.");
+Console.WriteLine($"Google GenAI client based agent response:\n{response}");
+
+// Using a community driven Mscc.GenerativeAI.Microsoft package
+
+ChatClientAgent agentCommunity = new(
+ new GeminiChatClient(apiKey, model),
+ name: JokerName,
+ instructions: JokerInstructions);
+
+response = await agentCommunity.RunAsync("Tell me a joke about a pirate.");
+Console.WriteLine($"Community client based agent response:\n{response}");
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/README.md
new file mode 100644
index 00000000000..bc3a3592e6c
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/README.md
@@ -0,0 +1,37 @@
+# Creating an AIAgent with Google Gemini
+
+This sample demonstrates how to create an AIAgent using Google Gemini models as the underlying inference service.
+
+The sample showcases two different `IChatClient` implementations:
+
+1. **Google GenAI** - Using the official [Google.GenAI](https://www.nuget.org/packages/Google.GenAI) package
+2. **Mscc.GenerativeAI.Microsoft** - Using the community-driven [Mscc.GenerativeAI.Microsoft](https://www.nuget.org/packages/Mscc.GenerativeAI.Microsoft) package
+
+## Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 10.0 SDK or later
+- Google AI Studio API key (get one at [Google AI Studio](https://aistudio.google.com/apikey))
+
+Set the following environment variables:
+
+```powershell
+$env:GOOGLE_GENAI_API_KEY="your-google-api-key" # Replace with your Google AI Studio API key
+$env:GOOGLE_GENAI_MODEL="gemini-2.5-fast" # Optional, defaults to gemini-2.5-fast
+```
+
+## Package Options
+
+### Google GenAI (Official)
+
+The official Google GenAI package provides direct access to Google's Generative AI models. This sample uses an extension method to convert the Google client to an `IChatClient`.
+
+> [!NOTE]
+> Until PR [googleapis/dotnet-genai#81](https://github.com/googleapis/dotnet-genai/pull/81) is merged, this option requires the additional `GeminiChatClient.cs` and `GoogleGenAIExtensions.cs` files included in this sample.
+>
+> We appreciate any community push by liking and commenting in the above PR to get it merged and release as part of official Google GenAI package.
+
+### Mscc.GenerativeAI.Microsoft (Community)
+
+The community-driven Mscc.GenerativeAI.Microsoft package provides a ready-to-use `IChatClient` implementation for Google Gemini models through the `GeminiChatClient` class.
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj
index c4a9467179c..61acc80e9cb 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md
index cb86e0d7c49..d97b0075ac6 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_ONNX/README.md
@@ -4,7 +4,7 @@ WARNING: ONNX doesn't support function calling, so any function tools passed to
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- An ONNX model downloaded to your machine
You can download an ONNX model from hugging face, using git clone:
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj
index 1ad175831bf..c538cbedd1b 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md
index be76a75de01..d448f31d659 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_Ollama/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Docker installed and running on your machine
- An Ollama model downloaded into Ollama
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj
index 0629a84bd01..eeda3eef6f4 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/README.md
index 22a4bae18c6..ad2b8e14d96 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/README.md
@@ -5,7 +5,7 @@ For more information see the OpenAI documentation: https://platform.openai.com/d
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- OpenAI API key
Set the following environment variables:
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj
index 0629a84bd01..4ea7a45b8a6 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Agent_With_OpenAIChatCompletion.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Program.cs b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Program.cs
index 9b03c989e1d..331109fba9e 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/Program.cs
@@ -3,6 +3,7 @@
// This sample shows how to create and use a simple AI agent with OpenAI Chat Completion as the backend.
using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
using OpenAI;
var apiKey = Environment.GetEnvironmentVariable("OPENAI_APIKEY") ?? throw new InvalidOperationException("OPENAI_APIKEY is not set.");
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/README.md
index 80b63e7cd0d..4df942f676d 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIChatCompletion/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- OpenAI api key
Set the following environment variables:
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj
index 0629a84bd01..eeda3eef6f4 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/Agent_With_OpenAIResponses.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/README.md b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/README.md
index 80b63e7cd0d..4df942f676d 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/Agent_With_OpenAIResponses/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- OpenAI api key
Set the following environment variables:
diff --git a/dotnet/samples/GettingStarted/AgentProviders/README.md b/dotnet/samples/GettingStarted/AgentProviders/README.md
index 5d32f2542b7..964e560c9ac 100644
--- a/dotnet/samples/GettingStarted/AgentProviders/README.md
+++ b/dotnet/samples/GettingStarted/AgentProviders/README.md
@@ -15,6 +15,7 @@ See the README.md for each sample for the prerequisites for that sample.
|Sample|Description|
|---|---|
|[Creating an AIAgent with A2A](./Agent_With_A2A/)|This sample demonstrates how to create AIAgent for an existing A2A agent.|
+|[Creating an AIAgent with Anthropic](./Agent_With_Anthropic/)|This sample demonstrates how to create an AIAgent using Anthropic Claude models as the underlying inference service|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Agents.Persistent](./Agent_With_AzureAIAgentsPersistent/)|This sample demonstrates how to create a Foundry Persistent agent and expose it as an AIAgent using the Azure.AI.Agents.Persistent SDK|
|[Creating an AIAgent with Foundry Agents using Azure.AI.Project](./Agent_With_AzureAIProject/)|This sample demonstrates how to create an Foundry Project agent and expose it as an AIAgent using the Azure.AI.Project SDK|
|[Creating an AIAgent with AzureFoundry Model](./Agent_With_AzureFoundryModel/)|This sample demonstrates how to use any model deployed to Azure Foundry to create an AIAgent|
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj
new file mode 100644
index 00000000000..09359c5e781
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Program.cs b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Program.cs
new file mode 100644
index 00000000000..cf7e29c2fe9
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Program.cs
@@ -0,0 +1,24 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create and use a simple AI agent with Anthropic as the backend.
+
+using Anthropic;
+using Anthropic.Core;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
+var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
+
+AIAgent agent = new AnthropicClient(new ClientOptions { APIKey = apiKey })
+ .CreateAIAgent(model: model, instructions: "You are good at telling jokes.", name: "Joker");
+
+// Invoke the agent and output the text result.
+var response = await agent.RunAsync("Tell me a joke about a pirate.");
+Console.WriteLine(response);
+
+// Invoke the agent with streaming support.
+await foreach (var update in agent.RunStreamingAsync("Tell me a joke about a pirate."))
+{
+ Console.WriteLine(update);
+}
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/README.md b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/README.md
new file mode 100644
index 00000000000..4800650bd93
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/README.md
@@ -0,0 +1,43 @@
+# Running a simple agent with Anthropic
+
+This sample demonstrates how to create and run a basic agent with Anthropic Claude models.
+
+## What this sample demonstrates
+
+- Creating an AI agent with Anthropic Claude
+- Running a simple agent with instructions
+- Managing agent lifecycle
+
+## Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 8.0 SDK or later
+- Anthropic API key configured
+
+**Note**: This sample uses Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
+$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model
+```
+
+## Run the sample
+
+Navigate to the AgentWithAnthropic sample directory and run:
+
+```powershell
+cd dotnet\samples\GettingStarted\AgentWithAnthropic
+dotnet run --project .\Agent_Anthropic_Step01_Running
+```
+
+## Expected behavior
+
+The sample will:
+
+1. Create an agent with Anthropic Claude
+2. Run the agent with a simple prompt
+3. Display the agent's response
+
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj
new file mode 100644
index 00000000000..fc0914f1fc1
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Program.cs b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Program.cs
new file mode 100644
index 00000000000..d362a9dd0d6
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Program.cs
@@ -0,0 +1,59 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create and use an AI agent with reasoning capabilities.
+
+using Anthropic;
+using Anthropic.Core;
+using Anthropic.Models.Messages;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
+var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
+var maxTokens = 4096;
+var thinkingTokens = 2048;
+
+var agent = new AnthropicClient(new ClientOptions { APIKey = apiKey })
+ .CreateAIAgent(
+ model: model,
+ clientFactory: (chatClient) => chatClient
+ .AsBuilder()
+ .ConfigureOptions(
+ options => options.RawRepresentationFactory = (_) => new MessageCreateParams()
+ {
+ Model = options.ModelId ?? model,
+ MaxTokens = options.MaxOutputTokens ?? maxTokens,
+ Messages = [],
+ Thinking = new ThinkingConfigParam(new ThinkingConfigEnabled(budgetTokens: thinkingTokens))
+ })
+ .Build());
+
+Console.WriteLine("1. Non-streaming:");
+var response = await agent.RunAsync("Solve this problem step by step: If a train travels 60 miles per hour and needs to cover 180 miles, how long will the journey take? Show your reasoning.");
+
+Console.WriteLine("#### Start Thinking ####");
+Console.WriteLine($"\e[92m{string.Join("\n", response.Messages.SelectMany(m => m.Contents.OfType().Select(c => c.Text)))}\e[0m");
+Console.WriteLine("#### End Thinking ####");
+
+Console.WriteLine("\n#### Final Answer ####");
+Console.WriteLine(response.Text);
+
+Console.WriteLine("Token usage:");
+Console.WriteLine($"Input: {response.Usage?.InputTokenCount}, Output: {response.Usage?.OutputTokenCount}, {string.Join(", ", response.Usage?.AdditionalCounts ?? [])}");
+Console.WriteLine();
+
+Console.WriteLine("2. Streaming");
+await foreach (var update in agent.RunStreamingAsync("Explain the theory of relativity in simple terms."))
+{
+ foreach (var item in update.Contents)
+ {
+ if (item is TextReasoningContent reasoningContent)
+ {
+ Console.WriteLine($"\e[92m{reasoningContent.Text}\e[0m");
+ }
+ else if (item is TextContent textContent)
+ {
+ Console.WriteLine(textContent.Text);
+ }
+ }
+}
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/README.md b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/README.md
new file mode 100644
index 00000000000..ae088b23867
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/README.md
@@ -0,0 +1,46 @@
+# Using reasoning with Anthropic agents
+
+This sample demonstrates how to use extended thinking/reasoning capabilities with Anthropic Claude agents.
+
+## What this sample demonstrates
+
+- Creating an AI agent with Anthropic Claude extended thinking
+- Using reasoning capabilities for complex problem solving
+- Extracting thinking and response content from agent output
+- Managing agent lifecycle
+
+## Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 8.0 SDK or later
+- Anthropic API key configured
+- Access to Anthropic Claude models with extended thinking support
+
+**Note**: This sample uses Anthropic Claude models with extended thinking. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
+$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model
+```
+
+## Run the sample
+
+Navigate to the AgentWithAnthropic sample directory and run:
+
+```powershell
+cd dotnet\samples\GettingStarted\AgentWithAnthropic
+dotnet run --project .\Agent_Anthropic_Step02_Reasoning
+```
+
+## Expected behavior
+
+The sample will:
+
+1. Create an agent with Anthropic Claude extended thinking enabled
+2. Run the agent with a complex reasoning prompt
+3. Display the agent's thinking process
+4. Display the agent's final response
+
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj
new file mode 100644
index 00000000000..fdb9a2f50f9
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj
@@ -0,0 +1,15 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs
new file mode 100644
index 00000000000..a56db8d4a22
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Program.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample demonstrates how to use an agent with function tools.
+// It shows both non-streaming and streaming agent interactions using weather-related tools.
+
+using System.ComponentModel;
+using Anthropic;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var apiKey = Environment.GetEnvironmentVariable("ANTHROPIC_API_KEY") ?? throw new InvalidOperationException("ANTHROPIC_API_KEY is not set.");
+var model = Environment.GetEnvironmentVariable("ANTHROPIC_MODEL") ?? "claude-haiku-4-5";
+
+[Description("Get the weather for a given location.")]
+static string GetWeather([Description("The location to get the weather for.")] string location)
+ => $"The weather in {location} is cloudy with a high of 15°C.";
+
+const string AssistantInstructions = "You are a helpful assistant that can get weather information.";
+const string AssistantName = "WeatherAssistant";
+
+// Define the agent with function tools.
+AITool tool = AIFunctionFactory.Create(GetWeather);
+
+// Get anthropic client to create agents.
+AIAgent agent = new AnthropicClient { APIKey = apiKey }
+ .CreateAIAgent(model: model, instructions: AssistantInstructions, name: AssistantName, tools: [tool]);
+
+// Non-streaming agent interaction with function tools.
+AgentThread thread = agent.GetNewThread();
+Console.WriteLine(await agent.RunAsync("What is the weather like in Amsterdam?", thread));
+
+// Streaming agent interaction with function tools.
+thread = agent.GetNewThread();
+await foreach (AgentRunResponseUpdate update in agent.RunStreamingAsync("What is the weather like in Amsterdam?", thread))
+{
+ Console.WriteLine(update);
+}
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/README.md b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/README.md
new file mode 100644
index 00000000000..6c905864ef7
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/README.md
@@ -0,0 +1,47 @@
+# Using Function Tools with Anthropic agents
+
+This sample demonstrates how to use function tools with Anthropic Claude agents, allowing agents to call custom functions to retrieve information.
+
+## What this sample demonstrates
+
+- Creating function tools using AIFunctionFactory
+- Passing function tools to an Anthropic Claude agent
+- Running agents with function tools (text output)
+- Running agents with function tools (streaming output)
+- Managing agent lifecycle
+
+## Prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 8.0 SDK or later
+- Anthropic API key configured
+
+**Note**: This sample uses Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
+$env:ANTHROPIC_MODEL="your-anthropic-model" # Replace with your Anthropic model
+```
+
+## Run the sample
+
+Navigate to the AgentWithAnthropic sample directory and run:
+
+```powershell
+cd dotnet\samples\GettingStarted\AgentWithAnthropic
+dotnet run --project .\Agent_Anthropic_Step03_UsingFunctionTools
+```
+
+## Expected behavior
+
+The sample will:
+
+1. Create an agent named "WeatherAssistant" with a GetWeather function tool
+2. Run the agent with a text prompt asking about weather
+3. The agent will invoke the GetWeather function tool to retrieve weather information
+4. Run the agent again with streaming to display the response as it's generated
+5. Clean up resources by deleting the agent
+
diff --git a/dotnet/samples/GettingStarted/AgentWithAnthropic/README.md b/dotnet/samples/GettingStarted/AgentWithAnthropic/README.md
new file mode 100644
index 00000000000..44c15b384b3
--- /dev/null
+++ b/dotnet/samples/GettingStarted/AgentWithAnthropic/README.md
@@ -0,0 +1,72 @@
+# Getting started with agents using Anthropic
+
+The getting started with agents using Anthropic samples demonstrate the fundamental concepts and functionalities
+of single agents using Anthropic as the AI provider.
+
+These samples use Anthropic Claude models as the AI provider and use ChatCompletion as the type of service.
+
+For other samples that demonstrate how to create and configure each type of agent that come with the agent framework,
+see the [How to create an agent for each provider](../AgentProviders/README.md) samples.
+
+## Getting started with agents using Anthropic prerequisites
+
+Before you begin, ensure you have the following prerequisites:
+
+- .NET 8.0 SDK or later
+- Anthropic API key configured
+- User has access to Anthropic Claude models
+
+**Note**: These samples use Anthropic Claude models. For more information, see [Anthropic documentation](https://docs.anthropic.com/).
+
+## Using Anthropic with Azure Foundry
+
+To use Anthropic with Azure Foundry, you can check the sample [AgentProviders/Agent_With_Anthropic](../AgentProviders/Agent_With_Anthropic/README.md) for more details.
+
+## Samples
+
+|Sample|Description|
+|---|---|
+|[Running a simple agent](./Agent_Anthropic_Step01_Running/)|This sample demonstrates how to create and run a basic agent with Anthropic Claude|
+|[Using reasoning with an agent](./Agent_Anthropic_Step02_Reasoning/)|This sample demonstrates how to use extended thinking/reasoning capabilities with Anthropic Claude agents|
+|[Using function tools with an agent](./Agent_Anthropic_Step03_UsingFunctionTools/)|This sample demonstrates how to use function tools with an Anthropic Claude agent|
+
+## Running the samples from the console
+
+To run the samples, navigate to the desired sample directory, e.g.
+
+```powershell
+cd Agent_Anthropic_Step01_Running
+```
+
+Set the following environment variables:
+
+```powershell
+$env:ANTHROPIC_API_KEY="your-anthropic-api-key" # Replace with your Anthropic API key
+```
+
+If the variables are not set, you will be prompted for the values when running the samples.
+
+Execute the following command to build the sample:
+
+```powershell
+dotnet build
+```
+
+Execute the following command to run the sample:
+
+```powershell
+dotnet run --no-build
+```
+
+Or just build and run in one step:
+
+```powershell
+dotnet run
+```
+
+## Running the samples from Visual Studio
+
+Open the solution in Visual Studio and set the desired sample project as the startup project. Then, run the project using the built-in debugger or by pressing `F5`.
+
+You will be prompted for any required environment variables if they are not already set.
+
diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj
index 1caf270c49a..860089b6218 100644
--- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -13,7 +13,6 @@
-
diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs
index 9e4c27cebb8..19e1fead1e6 100644
--- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/Program.cs
@@ -32,7 +32,7 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
- Instructions = "You are good at telling jokes.",
+ ChatOptions = new() { Instructions = "You are good at telling jokes." },
Name = "Joker",
AIContextProviderFactory = (ctx) => new ChatHistoryMemoryProvider(
vectorStore,
diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj
index 9d7aa41a99c..1e0863d66fe 100644
--- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/AgentWithMemory_Step02_MemoryUsingMem0.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs
index 539ebbaecb9..87f5842e2c9 100644
--- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step02_MemoryUsingMem0/Program.cs
@@ -30,8 +30,8 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions()
{
- Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details.",
- AIContextProviderFactory = ctx => ctx.SerializedState.ValueKind is not JsonValueKind.Null or JsonValueKind.Undefined
+ ChatOptions = new() { Instructions = "You are a friendly travel assistant. Use known memories about the user when responding, and do not invent details." },
+ AIContextProviderFactory = ctx => ctx.SerializedState.ValueKind is not JsonValueKind.Null and not JsonValueKind.Undefined
// If each thread should have its own Mem0 scope, you can create a new id per thread here:
// ? new Mem0Provider(mem0HttpClient, new Mem0ProviderScope() { ThreadId = Guid.NewGuid().ToString() })
// In this case we are storing memories scoped by application and user instead so that memories are retained across threads.
diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/AgentWithMemory_Step03_CustomMemory.csproj b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/AgentWithMemory_Step03_CustomMemory.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/AgentWithMemory_Step03_CustomMemory.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/AgentWithMemory_Step03_CustomMemory.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs
index ad59deb97fd..4b9b1866a9b 100644
--- a/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step03_CustomMemory/Program.cs
@@ -33,7 +33,7 @@
// and its storage to that user id.
AIAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions()
{
- Instructions = "You are a friendly assistant. Always address the user by their name.",
+ ChatOptions = new() { Instructions = "You are a friendly assistant. Always address the user by their name." },
AIContextProviderFactory = ctx => new UserInfoMemory(chatClient.AsIChatClient(), ctx.SerializedState, ctx.JsonSerializerOptions)
});
diff --git a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj
index 0629a84bd01..eeda3eef6f4 100644
--- a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj
index 4253d9cf9e8..78f09816769 100644
--- a/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj
index 0c8a9f2dfc7..860089b6218 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs
index ec665325a7e..a30e0371a06 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/Program.cs
@@ -62,7 +62,7 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
- Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
+ ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
AIContextProviderFactory = ctx => new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
});
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStore.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStore.cs
index 502c17dba16..82559ecf83c 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStore.cs
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStore.cs
@@ -98,8 +98,8 @@ public TextSearchStore(
// Create a definition so that we can use the dimensions provided at runtime.
VectorStoreCollectionDefinition ragDocumentDefinition = new()
{
- Properties = new List()
- {
+ Properties =
+ [
new VectorStoreKeyProperty("Key", this._options.KeyType ?? typeof(string)),
new VectorStoreDataProperty("Namespaces", typeof(List)) { IsIndexed = true },
new VectorStoreDataProperty("SourceId", typeof(string)) { IsIndexed = true },
@@ -107,7 +107,7 @@ public TextSearchStore(
new VectorStoreDataProperty("SourceName", typeof(string)),
new VectorStoreDataProperty("SourceLink", typeof(string)),
new VectorStoreVectorProperty("TextEmbedding", typeof(string), vectorDimensions),
- }
+ ]
};
this._vectorStoreRecordCollection = this._vectorStore.GetDynamicCollection(collectionName, ragDocumentDefinition);
@@ -267,7 +267,7 @@ public async Task> SearchAsync(string query, int
cancellationToken: cancellationToken);
// Retrieve the documents from the search results.
- List> searchResponseDocs = new();
+ List> searchResponseDocs = [];
await foreach (var searchResponseDoc in searchResult.WithCancellation(cancellationToken).ConfigureAwait(false))
{
searchResponseDocs.Add(searchResponseDoc.Record);
@@ -291,12 +291,8 @@ public async Task> SearchAsync(string query, int
}
// Retrieve the source text for the documents that need it.
- var retrievalResponses = await this._options.SourceRetrievalCallback(sourceIdsToRetrieve).ConfigureAwait(false);
-
- if (retrievalResponses is null)
- {
+ var retrievalResponses = await this._options.SourceRetrievalCallback(sourceIdsToRetrieve).ConfigureAwait(false) ??
throw new InvalidOperationException($"The {nameof(TextSearchStoreOptions.SourceRetrievalCallback)} must return a non-null value.");
- }
// Update the retrieved documents with the retrieved text.
return searchResponseDocs.GroupJoin(
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStoreOptions.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStoreOptions.cs
index 53da092c825..d9b8761be63 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStoreOptions.cs
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/TextSearchStore/TextSearchStoreOptions.cs
@@ -107,15 +107,8 @@ public sealed class SourceRetrievalResponse
/// The source text that was retrieved.
public SourceRetrievalResponse(SourceRetrievalRequest request, string text)
{
- if (request == null)
- {
- throw new ArgumentNullException(nameof(request));
- }
-
- if (text == null)
- {
- throw new ArgumentNullException(nameof(text));
- }
+ ArgumentNullException.ThrowIfNull(request);
+ ArgumentNullException.ThrowIfNull(text);
this.SourceId = request.SourceId;
this.SourceLink = request.SourceLink;
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj
index 56e2ad232b0..33029395dd9 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs
index 89ced52b69e..89312f85973 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/Program.cs
@@ -71,7 +71,7 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
- Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief.",
+ ChatOptions = new() { Instructions = "You are a helpful support specialist for the Microsoft Agent Framework. Answer questions using the provided context and cite the source document when available. Keep responses brief." },
AIContextProviderFactory = ctx => new TextSearchProvider(SearchAdapter, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
});
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md
index 1817f0d8ca9..131adde82bb 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/README.md
@@ -6,7 +6,7 @@ This sample uses Qdrant for the vector store, but this can easily be swapped out
## Prerequisites
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint
- Both a chat completion and embedding deployment configured in the Azure OpenAI resource
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs
index 38bc2e09f39..5e7b2c41329 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/Program.cs
@@ -29,7 +29,7 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
- Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available.",
+ ChatOptions = new() { Instructions = "You are a helpful support specialist for Contoso Outdoors. Answer questions using the provided context and cite the source document when available." },
AIContextProviderFactory = ctx => new TextSearchProvider(MockSearchAsync, ctx.SerializedState, ctx.JsonSerializerOptions, textSearchOptions)
});
@@ -48,7 +48,7 @@
{
// The mock search inspects the user's question and returns pre-defined snippets
// that resemble documents stored in an external knowledge source.
- List results = new();
+ List results = [];
if (query.Contains("return", StringComparison.OrdinalIgnoreCase) || query.Contains("refund", StringComparison.OrdinalIgnoreCase))
{
diff --git a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj
index aefb46524f8..d90e1c394b6 100644
--- a/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj
+++ b/dotnet/samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Agent_Step01_Running.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Agent_Step01_Running.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Agent_Step01_Running.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step01_Running/Agent_Step01_Running.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Agent_Step02_MultiturnConversation.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Agent_Step02_MultiturnConversation.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Agent_Step02_MultiturnConversation.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step02_MultiturnConversation/Agent_Step02_MultiturnConversation.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step03_UsingFunctionTools/Agent_Step03_UsingFunctionTools.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step04_UsingFunctionToolsWithApprovals/Agent_Step04_UsingFunctionToolsWithApprovals.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Agent_Step05_StructuredOutput.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
index b18d8e2d847..ef1849fe02c 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step05_StructuredOutput/Program.cs
@@ -22,7 +22,7 @@
.GetChatClient(deploymentName);
// Create the ChatClientAgent with the specified name and instructions.
-ChatClientAgent agent = chatClient.CreateAIAgent(new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant."));
+ChatClientAgent agent = chatClient.CreateAIAgent(name: "HelpfulAssistant", instructions: "You are a helpful assistant.");
// Set PersonInfo as the type parameter of RunAsync method to specify the expected structured output from the agent and invoke the agent with some unstructured input.
AgentRunResponse response = await agent.RunAsync("Please provide information about John Smith, who is a 35-year-old software engineer.");
@@ -34,12 +34,10 @@
Console.WriteLine($"Occupation: {response.Result.Occupation}");
// Create the ChatClientAgent with the specified name, instructions, and expected structured output the agent should produce.
-ChatClientAgent agentWithPersonInfo = chatClient.CreateAIAgent(new ChatClientAgentOptions(name: "HelpfulAssistant", instructions: "You are a helpful assistant.")
+ChatClientAgent agentWithPersonInfo = chatClient.CreateAIAgent(new ChatClientAgentOptions()
{
- ChatOptions = new()
- {
- ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema()
- }
+ Name = "HelpfulAssistant",
+ ChatOptions = new() { Instructions = "You are a helpful assistant.", ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema() }
});
// Invoke the agent with some unstructured input while streaming, to extract the structured information from.
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Agent_Step06_PersistedConversations.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs
index 1ffe3c99935..559fc03d8c6 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step06_PersistedConversations/Program.cs
@@ -32,7 +32,7 @@
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedThread));
// Load the serialized thread from the temporary file (for demonstration purposes).
-JsonElement reloadedSerializedThread = JsonSerializer.Deserialize(await File.ReadAllTextAsync(tempFilePath));
+JsonElement reloadedSerializedThread = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath));
// Deserialize the thread state after loading from storage.
AgentThread resumedThread = agent.DeserializeThread(reloadedSerializedThread);
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Agent_Step07_3rdPartyThreadStorage.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Agent_Step07_3rdPartyThreadStorage.csproj
index 1caf270c49a..860089b6218 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Agent_Step07_3rdPartyThreadStorage.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Agent_Step07_3rdPartyThreadStorage.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -13,7 +13,6 @@
-
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs
index 89867349721..d1316b6c803 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step07_3rdPartyThreadStorage/Program.cs
@@ -28,7 +28,7 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
- Instructions = "You are good at telling jokes.",
+ ChatOptions = new() { Instructions = "You are good at telling jokes." },
Name = "Joker",
ChatMessageStoreFactory = ctx =>
{
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj
index 980e2826410..1a618d660af 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step08_Observability/Agent_Step08_Observability.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Agent_Step09_DependencyInjection.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Agent_Step09_DependencyInjection.csproj
index b0890e1817f..0aaa4712602 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Agent_Step09_DependencyInjection.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Agent_Step09_DependencyInjection.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs
index 894c034eb0c..d1b75d2fe5d 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step09_DependencyInjection/Program.cs
@@ -18,8 +18,7 @@
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
// Add agent options to the service collection.
-builder.Services.AddSingleton(
- new ChatClientAgentOptions(instructions: "You are good at telling jokes.", name: "Joker"));
+builder.Services.AddSingleton(new ChatClientAgentOptions() { Name = "Joker", ChatOptions = new() { Instructions = "You are good at telling jokes." } });
// Add a chat client to the service collection.
builder.Services.AddKeyedChatClient("AzureOpenAI", (sp) => new AzureOpenAIClient(
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Agent_Step10_AsMcpTool.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Agent_Step10_AsMcpTool.csproj
index 1fb367c0443..db776afd1ed 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Agent_Step10_AsMcpTool.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step10_AsMcpTool/Agent_Step10_AsMcpTool.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -14,7 +14,6 @@
-
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Agent_Step11_UsingImages.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Agent_Step11_UsingImages.csproj
index 7e9e70c763b..73a41005f14 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Agent_Step11_UsingImages.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step11_UsingImages/Agent_Step11_UsingImages.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Agent_Step12_AsFunctionTool.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Agent_Step12_AsFunctionTool.csproj
index 21c8d9e49ea..26600904041 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Agent_Step12_AsFunctionTool.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step12_AsFunctionTool/Agent_Step12_AsFunctionTool.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Agent_Step13_BackgroundResponsesWithToolsAndPersistence.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Agent_Step13_BackgroundResponsesWithToolsAndPersistence.csproj
index 4735f4a7a09..29fab5f992f 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Agent_Step13_BackgroundResponsesWithToolsAndPersistence.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Agent_Step13_BackgroundResponsesWithToolsAndPersistence.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs
index f2a3bdf5c0a..83a97d76c55 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/Program.cs
@@ -44,7 +44,7 @@
await Task.Delay(TimeSpan.FromSeconds(10));
- RestoreAgentState(agent, out thread, out object? continuationToken);
+ RestoreAgentState(agent, out thread, out ResponseContinuationToken? continuationToken);
options.ContinuationToken = continuationToken;
response = await agent.RunAsync(thread, options);
@@ -52,19 +52,19 @@
Console.WriteLine(response.Text);
-void PersistAgentState(AgentThread thread, object? continuationToken)
+void PersistAgentState(AgentThread thread, ResponseContinuationToken? continuationToken)
{
stateStore["thread"] = thread.Serialize();
stateStore["continuationToken"] = JsonSerializer.SerializeToElement(continuationToken, AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
}
-void RestoreAgentState(AIAgent agent, out AgentThread thread, out object? continuationToken)
+void RestoreAgentState(AIAgent agent, out AgentThread thread, out ResponseContinuationToken? continuationToken)
{
JsonElement serializedThread = stateStore["thread"] ?? throw new InvalidOperationException("No serialized thread found in state store.");
JsonElement? serializedToken = stateStore["continuationToken"];
thread = agent.DeserializeThread(serializedThread);
- continuationToken = serializedToken?.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
+ continuationToken = (ResponseContinuationToken?)serializedToken?.Deserialize(AgentAbstractionsJsonUtilities.DefaultOptions.GetTypeInfo(typeof(ResponseContinuationToken)));
}
[Description("Researches relevant space facts and scientific information for writing a science fiction novel")]
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/README.md b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/README.md
index 146f4185125..ca52e8afa31 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/README.md
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step13_BackgroundResponsesWithToolsAndPersistence/README.md
@@ -14,7 +14,7 @@ For more information, see the [official documentation](https://learn.microsoft.c
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj
index 09beb78195c..6582c30cd52 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Agent_Step14_Middleware.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs
index 28a50cc7d7d..a0ca3382970 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step14_Middleware/Program.cs
@@ -154,10 +154,11 @@ static IList FilterMessages(IEnumerable messages)
static string FilterPii(string content)
{
// Regex patterns for PII detection (simplified for demonstration)
- Regex[] piiPatterns = [
+ Regex[] piiPatterns =
+ [
new(@"\b\d{3}-\d{3}-\d{4}\b", RegexOptions.Compiled), // Phone number (e.g., 123-456-7890)
- new(@"\b[\w\.-]+@[\w\.-]+\.\w+\b", RegexOptions.Compiled), // Email address
- new(@"\b[A-Z][a-z]+\s[A-Z][a-z]+\b", RegexOptions.Compiled) // Full name (e.g., John Doe)
+ new(@"\b[\w\.-]+@[\w\.-]+\.\w+\b", RegexOptions.Compiled), // Email address
+ new(@"\b[A-Z][a-z]+\s[A-Z][a-z]+\b", RegexOptions.Compiled) // Full name (e.g., John Doe)
];
foreach (var pattern in piiPatterns)
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj
index c1cf0bf930e..ae2f9ac1945 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step15_Plugins/Agent_Step15_Plugins.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj
index 8298cfe6e8e..0f9de7c3599 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs
index 590b5308d5f..04704b5da0d 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Program.cs
@@ -21,7 +21,7 @@
.GetChatClient(deploymentName)
.CreateAIAgent(new ChatClientAgentOptions
{
- Instructions = "You are good at telling jokes.",
+ ChatOptions = new() { Instructions = "You are good at telling jokes." },
Name = "Joker",
ChatMessageStoreFactory = ctx => new InMemoryChatMessageStore(new MessageCountingChatReducer(2), ctx.SerializedState, ctx.JsonSerializerOptions)
});
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj
index c5b2ae56a60..1c95b4af256 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/README.md b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/README.md
index 5b7df74ca99..e898733bc3e 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/README.md
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/README.md
@@ -13,7 +13,7 @@ For more information, see the [official documentation](https://learn.microsoft.c
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Agent_Step18_DeepResearch.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Agent_Step18_DeepResearch.csproj
index 11c7beb3bf7..d40e93232b9 100644
--- a/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Agent_Step18_DeepResearch.csproj
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Agent_Step18_DeepResearch.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Agent_Step19_Declarative.csproj b/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Agent_Step19_Declarative.csproj
new file mode 100644
index 00000000000..550e1f22cb8
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Agent_Step19_Declarative.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs b/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs
new file mode 100644
index 00000000000..1fc985b3bb4
--- /dev/null
+++ b/dotnet/samples/GettingStarted/Agents/Agent_Step19_Declarative/Program.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to create an agent from a YAML based declarative representation.
+
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+// Create the chat client
+IChatClient chatClient = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient();
+
+// Define the agent using a YAML definition.
+var text =
+ """
+ kind: Prompt
+ name: Assistant
+ description: Helpful assistant
+ instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+ model:
+ options:
+ temperature: 0.9
+ topP: 0.95
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+// Create the agent from the YAML definition.
+var agentFactory = new ChatClientPromptAgentFactory(chatClient);
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync("Tell me a joke about a pirate in English."));
+
+// Invoke the agent with streaming support.
+await foreach (var update in agent!.RunStreamingAsync("Tell me a joke about a pirate in French."))
+{
+ Console.WriteLine(update);
+}
diff --git a/dotnet/samples/GettingStarted/Agents/README.md b/dotnet/samples/GettingStarted/Agents/README.md
index f510b03fafe..d023d6455c0 100644
--- a/dotnet/samples/GettingStarted/Agents/README.md
+++ b/dotnet/samples/GettingStarted/Agents/README.md
@@ -13,7 +13,7 @@ see the [How to create an agent for each provider](../AgentProviders/README.md)
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource.
@@ -45,6 +45,7 @@ Before you begin, ensure you have the following prerequisites:
|[Reducing chat history size](./Agent_Step16_ChatReduction/)|This sample demonstrates how to reduce the chat history to constrain its size, where chat history is maintained locally|
|[Background responses](./Agent_Step17_BackgroundResponses/)|This sample demonstrates how to use background responses for long-running operations with polling and resumption support|
|[Deep research with an agent](./Agent_Step18_DeepResearch/)|This sample demonstrates how to use the Deep Research Tool to perform comprehensive research on complex topics|
+|[Declarative agent](./Agent_Step19_Declarative/)|This sample demonstrates how to declaratively define an agent.|
## Running the samples from the console
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj
new file mode 100644
index 00000000000..0fc316acaca
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj
@@ -0,0 +1,25 @@
+
+
+
+ Exe
+ net10.0
+
+ enable
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs
new file mode 100644
index 00000000000..bed16f496ac
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Program.cs
@@ -0,0 +1,55 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+// This sample shows how to load an AI agent from a YAML file and process a prompt using Azure OpenAI as the backend.
+
+using System.ComponentModel;
+using Azure.AI.OpenAI;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+
+var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
+var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
+
+// Create the chat client
+IChatClient chatClient = new AzureOpenAIClient(
+ new Uri(endpoint),
+ new AzureCliCredential())
+ .GetChatClient(deploymentName)
+ .AsIChatClient();
+
+// Read command-line arguments
+if (args.Length < 2)
+{
+ Console.WriteLine("Usage: DeclarativeAgents ");
+ Console.WriteLine(" : The path to the YAML file containing the agent definition");
+ Console.WriteLine(" : The prompt to send to the agent");
+ return;
+}
+
+var yamlFilePath = args[0];
+var prompt = args[1];
+
+// Verify the YAML file exists
+if (!File.Exists(yamlFilePath))
+{
+ Console.WriteLine($"Error: File not found: {yamlFilePath}");
+ return;
+}
+
+// Read the YAML content from the file
+var text = await File.ReadAllTextAsync(yamlFilePath);
+
+// Example function tool that can be used by the agent.
+[Description("Get the weather for a given location.")]
+static string GetWeather(
+ [Description("The city and state, e.g. San Francisco, CA")] string location,
+ [Description("The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.")] string unit)
+ => $"The weather in {location} is cloudy with a high of {(unit.Equals("celsius", StringComparison.Ordinal) ? "15°C" : "59°F")}.";
+
+// Create the agent from the YAML definition.
+var agentFactory = new ChatClientPromptAgentFactory(chatClient, [AIFunctionFactory.Create(GetWeather, "GetWeather")]);
+var agent = await agentFactory.CreateFromYamlAsync(text);
+
+// Invoke the agent and output the text result.
+Console.WriteLine(await agent!.RunAsync(prompt));
diff --git a/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Properties/launchSettings.json b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Properties/launchSettings.json
new file mode 100644
index 00000000000..5ec486626ce
--- /dev/null
+++ b/dotnet/samples/GettingStarted/DeclarativeAgents/ChatClient/Properties/launchSettings.json
@@ -0,0 +1,12 @@
+{
+ "profiles": {
+ "GetWeather": {
+ "commandName": "Project",
+ "commandLineArgs": "..\\..\\..\\..\\..\\..\\..\\..\\agent-samples\\chatclient\\GetWeather.yaml \"What is the weather in Cambridge, MA in °C?\""
+ },
+ "Assistant": {
+ "commandName": "Project",
+ "commandLineArgs": "..\\..\\..\\..\\..\\..\\..\\..\\agent-samples\\chatclient\\Assistant.yaml \"Tell me a joke about a pirate in Italian.\""
+ }
+ }
+}
\ No newline at end of file
diff --git a/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj b/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj
index 8ae36b52e0a..09037b5f1d7 100644
--- a/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj
+++ b/dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
DevUI_Step01_BasicUsage
@@ -19,7 +19,6 @@
-
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/FoundryAgents_Step01.1_Basics.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/FoundryAgents_Step01.1_Basics.csproj
index a2ccc2a339a..89b9d8ddc0a 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/FoundryAgents_Step01.1_Basics.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/FoundryAgents_Step01.1_Basics.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs
index 3c374d799f5..9a7ee0736a9 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/Program.cs
@@ -11,19 +11,17 @@
string endpoint = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_FOUNDRY_PROJECT_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_FOUNDRY_PROJECT_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
-const string JokerInstructionsV1 = "You are good at telling jokes.";
-const string JokerInstructionsV2 = "You are extremely hilarious at telling jokes.";
const string JokerName = "JokerAgent";
// Get a client to create/retrieve/delete server side agents with Azure Foundry Agents.
AIProjectClient aiProjectClient = new(new Uri(endpoint), new AzureCliCredential());
// Define the agent you want to create. (Prompt Agent in this case)
-AgentVersionCreationOptions options = new(new PromptAgentDefinition(model: deploymentName) { Instructions = JokerInstructionsV1 });
+AgentVersionCreationOptions options = new(new PromptAgentDefinition(model: deploymentName) { Instructions = "You are good at telling jokes." });
// Azure.AI.Agents SDK creates and manages agent by name and versions.
// You can create a server side agent version with the Azure.AI.Agents SDK client below.
-AgentVersion agentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: JokerName, options);
+AgentVersion createdAgentVersion = aiProjectClient.Agents.CreateAgentVersion(agentName: JokerName, options);
// Note:
// agentVersion.Id = ":",
@@ -31,20 +29,20 @@
// agentVersion.Name =
// You can retrieve an AIAgent for an already created server side agent version.
-AIAgent jokerAgentV1 = aiProjectClient.GetAIAgent(agentVersion);
+AIAgent existingJokerAgent = aiProjectClient.GetAIAgent(createdAgentVersion);
-// You can also create another AIAgent version (V2) by providing the same name with a different definition/instruction.
-AIAgent jokerAgentV2 = aiProjectClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: JokerInstructionsV2);
+// You can also create another AIAgent version by providing the same name with a different definition/instruction.
+AIAgent newJokerAgent = aiProjectClient.CreateAIAgent(name: JokerName, model: deploymentName, instructions: "You are extremely hilarious at telling jokes.");
// You can also get the AIAgent latest version by just providing its name.
AIAgent jokerAgentLatest = aiProjectClient.GetAIAgent(name: JokerName);
-AgentVersion latestVersion = jokerAgentLatest.GetService()!;
+AgentVersion latestAgentVersion = jokerAgentLatest.GetService()!;
// The AIAgent version can be accessed via the GetService method.
-Console.WriteLine($"Latest agent version id: {latestVersion.Id}");
+Console.WriteLine($"Latest agent version id: {latestAgentVersion.Id}");
// Once you have the AIAgent, you can invoke it like any other AIAgent.
Console.WriteLine(await jokerAgentLatest.RunAsync("Tell me a joke about a pirate."));
-// Cleanup by agent name removes both agent versions created (jokerAgentV1 + jokerAgentV2).
-await aiProjectClient.Agents.DeleteAgentAsync(jokerAgentV1.Name);
+// Cleanup by agent name removes both agent versions created.
+await aiProjectClient.Agents.DeleteAgentAsync(existingJokerAgent.Name);
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/README.md
index 6a22b1df811..ce56e057557 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/README.md
@@ -10,7 +10,7 @@ This sample demonstrates how to create and manage AI agents with Azure Foundry A
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/FoundryAgents_Step01.2_Running.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/FoundryAgents_Step01.2_Running.csproj
index 3ed207aadfd..daf7e244940 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/FoundryAgents_Step01.2_Running.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/FoundryAgents_Step01.2_Running.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/README.md
index 26725e016e7..53254e19754 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/README.md
@@ -13,7 +13,7 @@ This sample demonstrates how to create and run a simple AI agent with Azure Foun
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/FoundryAgents_Step02_MultiturnConversation.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/FoundryAgents_Step02_MultiturnConversation.csproj
index 3ed207aadfd..daf7e244940 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/FoundryAgents_Step02_MultiturnConversation.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/FoundryAgents_Step02_MultiturnConversation.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/README.md
index 2c38002f500..dab9f596db0 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to implement multi-turn conversations with AI agent
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/FoundryAgents_Step03_UsingFunctionTools.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/FoundryAgents_Step03_UsingFunctionTools.csproj
index 3ed207aadfd..daf7e244940 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/FoundryAgents_Step03_UsingFunctionTools.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/FoundryAgents_Step03_UsingFunctionTools.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/README.md
index 934373aa809..35bef8a9992 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to use function tools with AI agents, allowing agen
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/FoundryAgents_Step04_UsingFunctionToolsWithApprovals.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/FoundryAgents_Step04_UsingFunctionToolsWithApprovals.csproj
index 3ed207aadfd..daf7e244940 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/FoundryAgents_Step04_UsingFunctionToolsWithApprovals.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/FoundryAgents_Step04_UsingFunctionToolsWithApprovals.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/README.md
index 55aac6c8df8..5a797acd0f3 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to use function tools that require human approval b
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/FoundryAgents_Step05_StructuredOutput.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/FoundryAgents_Step05_StructuredOutput.csproj
index 3ed207aadfd..daf7e244940 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/FoundryAgents_Step05_StructuredOutput.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/FoundryAgents_Step05_StructuredOutput.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs
index 0edbed70e83..ac055658366 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/Program.cs
@@ -24,10 +24,12 @@
// Create ChatClientAgent directly
ChatClientAgent agent = await aiProjectClient.CreateAIAgentAsync(
model: deploymentName,
- new ChatClientAgentOptions(name: AssistantName, instructions: AssistantInstructions)
+ new ChatClientAgentOptions()
{
+ Name = AssistantName,
ChatOptions = new()
{
+ Instructions = AssistantInstructions,
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema()
}
});
@@ -44,10 +46,12 @@
// Create the ChatClientAgent with the specified name, instructions, and expected structured output the agent should produce.
ChatClientAgent agentWithPersonInfo = aiProjectClient.CreateAIAgent(
model: deploymentName,
- new ChatClientAgentOptions(name: AssistantName, instructions: AssistantInstructions)
+ new ChatClientAgentOptions()
{
+ Name = AssistantName,
ChatOptions = new()
{
+ Instructions = AssistantInstructions,
ResponseFormat = Microsoft.Extensions.AI.ChatResponseFormat.ForJsonSchema()
}
});
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/README.md
index 57887fc0d3d..956a2542e97 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to configure AI agents to produce structured output
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/FoundryAgents_Step06_PersistedConversations.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/FoundryAgents_Step06_PersistedConversations.csproj
index 3ed207aadfd..daf7e244940 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/FoundryAgents_Step06_PersistedConversations.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/FoundryAgents_Step06_PersistedConversations.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs
index 305422aa4d2..d404a814c0c 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/Program.cs
@@ -32,7 +32,7 @@
await File.WriteAllTextAsync(tempFilePath, JsonSerializer.Serialize(serializedThread));
// Load the serialized thread from the temporary file (for demonstration purposes).
-JsonElement reloadedSerializedThread = JsonSerializer.Deserialize(await File.ReadAllTextAsync(tempFilePath))!;
+JsonElement reloadedSerializedThread = JsonElement.Parse(await File.ReadAllTextAsync(tempFilePath))!;
// Deserialize the thread state after loading from storage.
AgentThread resumedThread = agent.DeserializeThread(reloadedSerializedThread);
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/README.md
index f0ae5905456..29c22337486 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to serialize and persist agent conversation threads
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/FoundryAgents_Step07_Observability.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/FoundryAgents_Step07_Observability.csproj
index 49b903d0416..5ceeabb2042 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/FoundryAgents_Step07_Observability.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/FoundryAgents_Step07_Observability.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/README.md
index 57d4e5df139..30f7014dff4 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/README.md
@@ -15,7 +15,7 @@ This sample demonstrates how to add observability to AI agents using OpenTelemet
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- (Optional) Application Insights connection string for Azure Monitor integration
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/FoundryAgents_Step08_DependencyInjection.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/FoundryAgents_Step08_DependencyInjection.csproj
index ea8fc63de01..f1812befeb2 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/FoundryAgents_Step08_DependencyInjection.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/FoundryAgents_Step08_DependencyInjection.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/README.md
index ab2b01e5d14..580821bb0a9 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/README.md
@@ -15,7 +15,7 @@ This sample demonstrates how to use dependency injection to register and manage
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/FoundryAgents_Step09_UsingMcpClientAsTools.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/FoundryAgents_Step09_UsingMcpClientAsTools.csproj
index 0ee9c80764e..a6d96cb3db2 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/FoundryAgents_Step09_UsingMcpClientAsTools.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/FoundryAgents_Step09_UsingMcpClientAsTools.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/README.md
index 9b3322b3fbd..b2d923fc2fa 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to use Model Context Protocol (MCP) client tools wi
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- Node.js and npm installed (for running the GitHub MCP server)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/FoundryAgents_Step10_UsingImages.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/FoundryAgents_Step10_UsingImages.csproj
index 1d423b22bca..53661ff1999 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/FoundryAgents_Step10_UsingImages.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/FoundryAgents_Step10_UsingImages.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/FoundryAgents_Step11_AsFunctionTool.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/FoundryAgents_Step11_AsFunctionTool.csproj
index f9336d45566..54f37f1aa6c 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/FoundryAgents_Step11_AsFunctionTool.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/FoundryAgents_Step11_AsFunctionTool.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/README.md
index 3702134ab39..4b64b7e7125 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to expose an AI agent as a function tool, enabling
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/FoundryAgents_Step12_Middleware.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/FoundryAgents_Step12_Middleware.csproj
index 4de5d131d9a..9f29a8d7e60 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/FoundryAgents_Step12_Middleware.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/FoundryAgents_Step12_Middleware.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md
index 1f7321051f6..04192a2cc65 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/README.md
@@ -21,7 +21,7 @@ Attempting to use function middleware on agents that do not wrap a ChatClientAge
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/FoundryAgents_Step13_Plugins.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/FoundryAgents_Step13_Plugins.csproj
index 1c8496b2391..4a345609467 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/FoundryAgents_Step13_Plugins.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/FoundryAgents_Step13_Plugins.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/README.md
index d086e28aa0a..0aeccf57894 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/README.md
@@ -14,7 +14,7 @@ This sample demonstrates how to use plugins with AI agents, where plugins are se
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/FoundryAgents_Step14_CodeInterpreter.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/FoundryAgents_Step14_CodeInterpreter.csproj
index 1c8496b2391..4a345609467 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/FoundryAgents_Step14_CodeInterpreter.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/FoundryAgents_Step14_CodeInterpreter.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/README.md
index 007f283a760..a3dd4d50b9f 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/README.md
@@ -15,7 +15,7 @@ This sample demonstrates how to use the code interpreter tool with AI agents. Th
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/FoundryAgents_Step15_ComputerUse.csproj b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/FoundryAgents_Step15_ComputerUse.csproj
index 383b55a939e..041c72c43e5 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/FoundryAgents_Step15_ComputerUse.csproj
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/FoundryAgents_Step15_ComputerUse.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/README.md b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/README.md
index 94ddf6b2693..4686ec5984d 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/README.md
@@ -15,7 +15,7 @@ This sample demonstrates how to use the computer use tool with AI agents. The co
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/FoundryAgents/README.md b/dotnet/samples/GettingStarted/FoundryAgents/README.md
index c5d027d8d76..daeb2db8df8 100644
--- a/dotnet/samples/GettingStarted/FoundryAgents/README.md
+++ b/dotnet/samples/GettingStarted/FoundryAgents/README.md
@@ -6,11 +6,20 @@ of Azure Foundry Agents and can be used with Azure Foundry as the AI provider.
These samples showcase how to work with agents managed through Azure Foundry, including agent creation,
versioning, multi-turn conversations, and advanced features like code interpretation and computer use.
+## Classic vs New Foundry Agents
+
+> [!NOTE]
+> Recently, Azure Foundry introduced a new and improved experience for creating and managing AI agents, which is the target of these samples.
+
+For more information about the previous classic agents and for what's new in Foundry Agents, see the [Foundry Agents migration documentation](https://learn.microsoft.com/en-us/azure/ai-foundry/agents/how-to/migrate?view=foundry).
+
+For a sample demonstrating how to use classic Foundry Agents, see the following: [Agent with Azure AI Persistent](../AgentProviders/Agent_With_AzureAIAgentsPersistent/README.md).
+
## Getting started with Foundry Agents prerequisites
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and project configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj
index c5e06bc3822..aa73860c141 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server/Agent_MCP_Server.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -13,7 +13,6 @@
-
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj
index 389b504c508..46c13061496 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/Agent_MCP_Server_Auth.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -15,7 +15,6 @@
-
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md
index ae88df95eef..a6505d65244 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/Agent_MCP_Server_Auth/README.md
@@ -17,7 +17,7 @@ The sample shows:
## Installing Prerequisites
- A self-signed certificate to enable HTTPS use in development, see [dotnet dev-certs](https://learn.microsoft.com/en-us/dotnet/core/tools/dotnet-dev-certs)
-- .NET 9.0 or later
+- .NET 10.0 or later
- A running TestOAuthServer (for OAuth authentication), see [Start the Test OAuth Server](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/ProtectedMcpClient#step-1-start-the-test-oauth-server)
- A running ProtectedMCPServer (for MCP services), see [Start the Protected MCP Server](https://github.com/modelcontextprotocol/csharp-sdk/tree/main/samples/ProtectedMcpClient#step-2-start-the-protected-mcp-server)
@@ -38,7 +38,7 @@ First, you need to start the TestOAuthServer which provides OAuth authentication
```bash
cd \tests\ModelContextProtocol.TestOAuthServer
-dotnet run --framework net9.0
+dotnet run --framework net10.0
```
The OAuth server will start at `https://localhost:7029`
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj
index 11c7beb3bf7..d40e93232b9 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/FoundryAgent_Hosted_MCP.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
index f824f099910..123d666f093 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/Program.cs
@@ -34,9 +34,9 @@
options: new()
{
Name = "MicrosoftLearnAgent",
- Instructions = "You answer questions by searching the Microsoft Learn content only.",
ChatOptions = new()
{
+ Instructions = "You answer questions by searching the Microsoft Learn content only.",
Tools = [mcpTool]
},
});
@@ -67,9 +67,9 @@
options: new()
{
Name = "MicrosoftLearnAgentWithApproval",
- Instructions = "You answer questions by searching the Microsoft Learn content only.",
ChatOptions = new()
{
+ Instructions = "You answer questions by searching the Microsoft Learn content only.",
Tools = [mcpToolWithApproval]
},
});
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
index e320a6c3d73..f3be7da5765 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/FoundryAgent_Hosted_MCP/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure Foundry service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/README.md
index 874afa28b84..be1aa835138 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/README.md
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/README.md
@@ -6,7 +6,7 @@ The getting started with Model Content Protocol samples demonstrate how to use M
Before you begin, ensure you have the following prerequisites:
-- .NET 9.0 SDK or later
+- .NET 10.0 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource.
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/README.md b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/README.md
index f84bd8f1b4f..c311edae403 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/README.md
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/README.md
@@ -2,7 +2,7 @@
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
- User has the `Cognitive Services OpenAI Contributor` role for the Azure OpenAI resource.
diff --git a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/ResponseAgent_Hosted_MCP.csproj b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/ResponseAgent_Hosted_MCP.csproj
index 0eacdab258a..41aafe34372 100644
--- a/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/ResponseAgent_Hosted_MCP.csproj
+++ b/dotnet/samples/GettingStarted/ModelContextProtocol/ResponseAgent_Hosted_MCP/ResponseAgent_Hosted_MCP.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/README.md b/dotnet/samples/GettingStarted/README.md
index 4e349c77427..7a46d81a62c 100644
--- a/dotnet/samples/GettingStarted/README.md
+++ b/dotnet/samples/GettingStarted/README.md
@@ -15,5 +15,6 @@ of the agent framework.
|[A2A](./A2A/README.md)|Getting started with A2A (Agent-to-Agent) specific features|
|[Agent Open Telemetry](./AgentOpenTelemetry/README.md)|Getting started with OpenTelemetry for agents|
|[Agent With OpenAI exchange types](./AgentWithOpenAI/README.md)|Using OpenAI exchange types with agents|
+|[Agent With Anthropic](./AgentWithAnthropic/README.md)|Getting started with agents using Anthropic Claude|
|[Workflow](./Workflows/README.md)|Getting started with Workflow|
|[Model Context Protocol](./ModelContextProtocol/README.md)|Getting started with Model Context Protocol|
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj
index 51b18bdeb22..881f20e1af3 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/CustomAgentExecutors.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs
index 5d5369883cb..91f58f460e6 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/CustomAgentExecutors/Program.cs
@@ -118,10 +118,11 @@ internal sealed class SloganWriterExecutor : Executor
/// The chat client to use for the AI agent.
public SloganWriterExecutor(string id, IChatClient chatClient) : base(id)
{
- ChatClientAgentOptions agentOptions = new(instructions: "You are a professional slogan writer. You will be given a task to create a slogan.")
+ ChatClientAgentOptions agentOptions = new()
{
ChatOptions = new()
{
+ Instructions = "You are a professional slogan writer. You will be given a task to create a slogan.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
};
@@ -193,10 +194,11 @@ internal sealed class FeedbackExecutor : Executor
/// The chat client to use for the AI agent.
public FeedbackExecutor(string id, IChatClient chatClient) : base(id)
{
- ChatClientAgentOptions agentOptions = new(instructions: "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.")
+ ChatClientAgentOptions agentOptions = new()
{
ChatOptions = new()
{
+ Instructions = "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
};
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/FoundryAgent.csproj b/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/FoundryAgent.csproj
index 888274205a7..f75c7fd28b8 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/FoundryAgent.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/FoundryAgent/FoundryAgent.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj b/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj
index 51b18bdeb22..881f20e1af3 100644
--- a/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Agents/WorkflowAsAnAgent/WorkflowAsAnAgent.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndRehydrate/CheckpointAndRehydrate.csproj b/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndRehydrate/CheckpointAndRehydrate.csproj
index 0a0945caffb..2f410707596 100644
--- a/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndRehydrate/CheckpointAndRehydrate.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndRehydrate/CheckpointAndRehydrate.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndResume/CheckpointAndResume.csproj b/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndResume/CheckpointAndResume.csproj
index 0a0945caffb..2f410707596 100644
--- a/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndResume/CheckpointAndResume.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointAndResume/CheckpointAndResume.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointWithHumanInTheLoop/CheckpointWithHumanInTheLoop.csproj b/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointWithHumanInTheLoop/CheckpointWithHumanInTheLoop.csproj
index 0a0945caffb..2f410707596 100644
--- a/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointWithHumanInTheLoop/CheckpointWithHumanInTheLoop.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Checkpoint/CheckpointWithHumanInTheLoop/CheckpointWithHumanInTheLoop.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Concurrent.csproj b/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Concurrent.csproj
index 3f3fe6d56ca..28a01e4540f 100644
--- a/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Concurrent.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Concurrent/Concurrent/Concurrent.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Concurrent/MapReduce/MapReduce.csproj b/dotnet/samples/GettingStarted/Workflows/Concurrent/MapReduce/MapReduce.csproj
index 7282e3fde4f..fd311b7be3a 100644
--- a/dotnet/samples/GettingStarted/Workflows/Concurrent/MapReduce/MapReduce.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Concurrent/MapReduce/MapReduce.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/01_EdgeCondition.csproj b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/01_EdgeCondition.csproj
index 17b1cb882ac..495f645f832 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/01_EdgeCondition.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/01_EdgeCondition.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs
index b6e3d4d5136..0f762ea40d0 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/01_EdgeCondition/Program.cs
@@ -85,10 +85,11 @@ private static async Task Main()
///
/// A ChatClientAgent configured for spam detection
private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are a spam detection assistant that identifies spam emails.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -98,10 +99,11 @@ private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) =>
///
/// A ChatClientAgent configured for email assistance
private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/02_SwitchCase.csproj b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/02_SwitchCase.csproj
index 17b1cb882ac..495f645f832 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/02_SwitchCase.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/02_SwitchCase.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs
index 13f0a75bc2f..ccda3fa19e7 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/02_SwitchCase/Program.cs
@@ -100,10 +100,11 @@ private static async Task Main()
///
/// A ChatClientAgent configured for spam detection
private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails. Be less confident in your assessments.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are a spam detection assistant that identifies spam emails. Be less confident in your assessments.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -113,10 +114,11 @@ private static ChatClientAgent GetSpamDetectionAgent(IChatClient chatClient) =>
///
/// A ChatClientAgent configured for email assistance
private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/03_MultiSelection.csproj b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/03_MultiSelection.csproj
index 17b1cb882ac..495f645f832 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/03_MultiSelection.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/03_MultiSelection.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs
index 9d340cbae3b..49faff39da7 100644
--- a/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/ConditionalEdges/03_MultiSelection/Program.cs
@@ -140,10 +140,11 @@ private static async Task Main()
///
/// A ChatClientAgent configured for email analysis
private static ChatClientAgent GetEmailAnalysisAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are a spam detection assistant that identifies spam emails.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are a spam detection assistant that identifies spam emails.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -153,10 +154,11 @@ private static ChatClientAgent GetEmailAnalysisAgent(IChatClient chatClient) =>
///
/// A ChatClientAgent configured for email assistance
private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are an email assistant that helps users draft responses to emails with professionalism.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are an email assistant that helps users draft responses to emails with professionalism.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
@@ -166,10 +168,11 @@ private static ChatClientAgent GetEmailAssistantAgent(IChatClient chatClient) =>
///
/// A ChatClientAgent configured for email summarization
private static ChatClientAgent GetEmailSummaryAgent(IChatClient chatClient) =>
- new(chatClient, new ChatClientAgentOptions(instructions: "You are an assistant that helps users summarize emails.")
+ new(chatClient, new ChatClientAgentOptions()
{
ChatOptions = new()
{
+ Instructions = "You are an assistant that helps users summarize emails.",
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ConfirmInput/ConfirmInput.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/ConfirmInput/ConfirmInput.csproj
index 3254317876b..da32d18b99b 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/ConfirmInput/ConfirmInput.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ConfirmInput/ConfirmInput.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/CustomerSupport.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/CustomerSupport.csproj
index 5ddd5705710..583dbc6e8fb 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/CustomerSupport.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/CustomerSupport/CustomerSupport.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/DeepResearch.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/DeepResearch.csproj
index 619c727b1ba..413fa562108 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/DeepResearch.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/DeepResearch/DeepResearch.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/ExecuteCode.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/ExecuteCode.csproj
index ca7c10cde35..9725826c7ac 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/ExecuteCode.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteCode/ExecuteCode.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj
index 1fb6abe55dd..074a31121da 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
$(NoWarn);CA1812
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/FunctionTools.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/FunctionTools.csproj
index 888a48f5df7..f8a51cb0f20 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/FunctionTools.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/FunctionTools/FunctionTools.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/GenerateCode/GenerateCode.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/GenerateCode/GenerateCode.csproj
index b10f7c5e95a..117e27abd85 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/GenerateCode/GenerateCode.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/GenerateCode/GenerateCode.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
5ee045b0-aea3-4f08-8d31-32d1a6f8fed0
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj
index 1f57e7e7bc4..3cbd0ada956 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
$(NoWarn);CA1812
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/InputArguments.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/InputArguments.csproj
index 51582438eb1..5ef0b7e99eb 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/InputArguments.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/InputArguments/InputArguments.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Marketing.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Marketing.csproj
index 12599a1b791..ceba7b740ba 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Marketing.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/Marketing/Marketing.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/StudentTeacher.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/StudentTeacher.csproj
index 7c210d6f96d..862e39bd992 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/StudentTeacher.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/StudentTeacher/StudentTeacher.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj b/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj
index 6fa1cf12d94..1ebaa26645f 100644
--- a/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj
@@ -2,9 +2,7 @@
Exe
- net9.0
- net9.0
- $(ProjectsDebugTargetFrameworks)
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj b/dotnet/samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj
index 0a0945caffb..2f410707596 100644
--- a/dotnet/samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/HumanInTheLoop/HumanInTheLoopBasic/HumanInTheLoopBasic.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Loop/Loop.csproj b/dotnet/samples/GettingStarted/Workflows/Loop/Loop.csproj
index fcc2aaf5c8e..0de620de0cb 100644
--- a/dotnet/samples/GettingStarted/Workflows/Loop/Loop.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Loop/Loop.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj b/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj
index f7a5a4424f7..4c91a01fad3 100644
--- a/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Observability/ApplicationInsights/ApplicationInsights.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -11,6 +11,9 @@
+
+
+
diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/AspireDashboard/AspireDashboard.csproj b/dotnet/samples/GettingStarted/Workflows/Observability/AspireDashboard/AspireDashboard.csproj
index db5479dd0f0..57b34f3d69a 100644
--- a/dotnet/samples/GettingStarted/Workflows/Observability/AspireDashboard/AspireDashboard.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Observability/AspireDashboard/AspireDashboard.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -12,6 +12,9 @@
+
+
+
diff --git a/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj b/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj
index 2193722d261..3e27c6b3034 100644
--- a/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
@@ -15,6 +15,9 @@
+
+
+
diff --git a/dotnet/samples/GettingStarted/Workflows/SharedStates/SharedStates.csproj b/dotnet/samples/GettingStarted/Workflows/SharedStates/SharedStates.csproj
index 2af5bbc1d75..35f87e7ebe8 100644
--- a/dotnet/samples/GettingStarted/Workflows/SharedStates/SharedStates.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/SharedStates/SharedStates.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/Visualization/Visualization.csproj b/dotnet/samples/GettingStarted/Workflows/Visualization/Visualization.csproj
index c9b83f7c38b..57b1fef0e13 100644
--- a/dotnet/samples/GettingStarted/Workflows/Visualization/Visualization.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/Visualization/Visualization.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/01_ExecutorsAndEdges.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/01_ExecutorsAndEdges.csproj
index 0a0945caffb..2f410707596 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/01_ExecutorsAndEdges.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/01_ExecutorsAndEdges.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/02_Streaming/02_Streaming.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/02_Streaming/02_Streaming.csproj
index 0a0945caffb..2f410707596 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/02_Streaming/02_Streaming.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/02_Streaming/02_Streaming.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj
index 51b18bdeb22..881f20e1af3 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/03_AgentsInWorkflows/03_AgentsInWorkflows.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj
index 51b18bdeb22..881f20e1af3 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/04_AgentWorkflowPatterns.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs
index 8cc66ed18a8..1fa3aabb5cd 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/04_AgentWorkflowPatterns/Program.cs
@@ -64,7 +64,7 @@ await RunWorkflowAsync(
while (true)
{
Console.Write("Q: ");
- messages.Add(new(ChatRole.User, Console.ReadLine()!));
+ messages.Add(new(ChatRole.User, Console.ReadLine()));
messages.AddRange(await RunWorkflowAsync(workflow, messages));
}
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj
index ea370c4eaa2..65d85d21af0 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/05_MultiModelService.csproj
@@ -2,14 +2,14 @@
Exe
- net9.0
+ net10.0
enable
enable
-
+
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/Program.cs
index c90131a27cd..8b4ac7645d8 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/05_MultiModelService/Program.cs
@@ -12,19 +12,16 @@
IChatClient aws = new AmazonBedrockRuntimeClient(
Environment.GetEnvironmentVariable("BEDROCK_ACCESSKEY"!),
Environment.GetEnvironmentVariable("BEDROCK_SECRETACCESSKEY")!,
- Amazon.RegionEndpoint.USEast1).AsIChatClient("amazon.nova-pro-v1:0");
+ Amazon.RegionEndpoint.USEast1)
+ .AsIChatClient("amazon.nova-pro-v1:0");
-IChatClient anthropic = new Anthropic.SDK.AnthropicClient(
- Environment.GetEnvironmentVariable("ANTHROPIC_APIKEY")!).Messages.AsBuilder()
- .ConfigureOptions(o =>
- {
- o.ModelId ??= "claude-sonnet-4-20250514";
- o.MaxOutputTokens ??= 10 * 1024;
- })
- .Build();
+IChatClient anthropic = new Anthropic.AnthropicClient(
+ new() { APIKey = Environment.GetEnvironmentVariable("ANTHROPIC_APIKEY") })
+ .AsIChatClient("claude-sonnet-4-20250514");
IChatClient openai = new OpenAI.OpenAIClient(
- Environment.GetEnvironmentVariable("OPENAI_APIKEY")!).GetChatClient("gpt-4o-mini").AsIChatClient();
+ Environment.GetEnvironmentVariable("OPENAI_APIKEY")!).GetChatClient("gpt-4o-mini")
+ .AsIChatClient();
// Define our agents.
AIAgent researcher = new ChatClientAgent(aws,
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj
index 89b1e4bbe00..e3913683e1a 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/06_SubWorkflows/06_SubWorkflows.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/07_MixedWorkflowAgentsAndExecutors.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/07_MixedWorkflowAgentsAndExecutors.csproj
index 51b18bdeb22..881f20e1af3 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/07_MixedWorkflowAgentsAndExecutors.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/07_MixedWorkflowAgentsAndExecutors/07_MixedWorkflowAgentsAndExecutors.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/08_WriterCriticWorkflow.csproj b/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/08_WriterCriticWorkflow.csproj
index 24901257c8c..e7a65f11a77 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/08_WriterCriticWorkflow.csproj
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/08_WriterCriticWorkflow.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
WriterCriticWorkflow
enable
enable
diff --git a/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs b/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs
index d9cc30f3950..265a87b5f60 100644
--- a/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs
+++ b/dotnet/samples/GettingStarted/Workflows/_Foundational/08_WriterCriticWorkflow/Program.cs
@@ -285,19 +285,19 @@ public CriticExecutor(IChatClient chatClient) : base("Critic")
this._agent = new ChatClientAgent(chatClient, new ChatClientAgentOptions
{
Name = "Critic",
- Instructions = """
- You are a constructive critic. Review the content and provide specific feedback.
- Always try to provide actionable suggestions for improvement and strive to identify improvement points.
- Only approve if the content is high quality, clear, and meets the original requirements and you see no improvement points.
-
- Provide your decision as structured output with:
- - approved: true if content is good, false if revisions needed
- - feedback: specific improvements needed (empty if approved)
-
- Be concise but specific in your feedback.
- """,
ChatOptions = new()
{
+ Instructions = """
+ You are a constructive critic. Review the content and provide specific feedback.
+ Always try to provide actionable suggestions for improvement and strive to identify improvement points.
+ Only approve if the content is high quality, clear, and meets the original requirements and you see no improvement points.
+
+ Provide your decision as structured output with:
+ - approved: true if content is good, false if revisions needed
+ - feedback: specific improvements needed (empty if approved)
+
+ Be concise but specific in your feedback.
+ """,
ResponseFormat = ChatResponseFormat.ForJsonSchema()
}
});
diff --git a/dotnet/samples/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj b/dotnet/samples/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj
index 2343fe016f0..60808f40517 100644
--- a/dotnet/samples/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj
+++ b/dotnet/samples/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj
@@ -1,8 +1,8 @@
-
+
Exe
- net9.0
+ net10.0
enable
enable
@@ -37,15 +37,15 @@
-
+
-
-
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -53,15 +53,15 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/dotnet/samples/HostedAgents/AgentWithHostedMCP/Dockerfile b/dotnet/samples/HostedAgents/AgentWithHostedMCP/Dockerfile
index 776f81041e5..a2590fc112b 100644
--- a/dotnet/samples/HostedAgents/AgentWithHostedMCP/Dockerfile
+++ b/dotnet/samples/HostedAgents/AgentWithHostedMCP/Dockerfile
@@ -1,5 +1,5 @@
# Build the application
-FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /src
# Copy files from the current directory on the host to the working directory in the container
@@ -7,10 +7,10 @@ COPY . .
RUN dotnet restore
RUN dotnet build -c Release --no-restore
-RUN dotnet publish -c Release --no-build -o /app
+RUN dotnet publish -c Release --no-build -o /app -f net10.0
# Run the application
-FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app
# Copy everything needed to run the app from the "build" stage.
diff --git a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj
index 4aafb7582a3..1cc019a1966 100644
--- a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj
+++ b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj
@@ -1,8 +1,8 @@
-
+
Exe
- net9.0
+ net10.0
enable
enable
@@ -36,15 +36,15 @@
-
+
-
-
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -52,15 +52,15 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Dockerfile b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Dockerfile
index b494ad22549..3d944c98834 100644
--- a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Dockerfile
+++ b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Dockerfile
@@ -1,5 +1,5 @@
# Build the application
-FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /src
# Copy files from the current directory on the host to the working directory in the container
@@ -7,10 +7,10 @@ COPY . .
RUN dotnet restore
RUN dotnet build -c Release --no-restore
-RUN dotnet publish -c Release --no-build -o /app
+RUN dotnet publish -c Release --no-build -o /app -f net10.0
# Run the application
-FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app
# Copy everything needed to run the app from the "build" stage.
diff --git a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs
index 94552a80141..d8be12c5b5a 100644
--- a/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs
+++ b/dotnet/samples/HostedAgents/AgentWithTextSearchRag/Program.cs
@@ -38,7 +38,7 @@
{
// The mock search inspects the user's question and returns pre-defined snippets
// that resemble documents stored in an external knowledge source.
- List results = new();
+ List results = [];
if (query.Contains("return", StringComparison.OrdinalIgnoreCase) || query.Contains("refund", StringComparison.OrdinalIgnoreCase))
{
diff --git a/dotnet/samples/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj b/dotnet/samples/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj
index 7e70caabdaf..1891ebab9db 100644
--- a/dotnet/samples/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj
+++ b/dotnet/samples/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj
@@ -1,8 +1,8 @@
-
+
Exe
- net9.0
+ net10.0
enable
enable
@@ -36,15 +36,15 @@
-
+
-
-
+
+
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -52,15 +52,15 @@
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
-
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/dotnet/samples/HostedAgents/AgentsInWorkflows/Dockerfile b/dotnet/samples/HostedAgents/AgentsInWorkflows/Dockerfile
index 0d3e5757cde..86b6c156f3a 100644
--- a/dotnet/samples/HostedAgents/AgentsInWorkflows/Dockerfile
+++ b/dotnet/samples/HostedAgents/AgentsInWorkflows/Dockerfile
@@ -1,5 +1,5 @@
# Build the application
-FROM mcr.microsoft.com/dotnet/sdk:9.0-alpine AS build
+FROM mcr.microsoft.com/dotnet/sdk:10.0-alpine AS build
WORKDIR /src
# Copy files from the current directory on the host to the working directory in the container
@@ -7,10 +7,10 @@ COPY . .
RUN dotnet restore
RUN dotnet build -c Release --no-restore
-RUN dotnet publish -c Release --no-build -o /app
+RUN dotnet publish -c Release --no-build -o /app -f net10.0
# Run the application
-FROM mcr.microsoft.com/dotnet/aspnet:9.0-alpine AS final
+FROM mcr.microsoft.com/dotnet/aspnet:10.0-alpine AS final
WORKDIR /app
# Copy everything needed to run the app from the "build" stage.
diff --git a/dotnet/samples/HostedAgents/AgentsInWorkflows/README.md b/dotnet/samples/HostedAgents/AgentsInWorkflows/README.md
index a92012157e0..5f6babc7556 100644
--- a/dotnet/samples/HostedAgents/AgentsInWorkflows/README.md
+++ b/dotnet/samples/HostedAgents/AgentsInWorkflows/README.md
@@ -13,7 +13,7 @@ The agents are connected sequentially, creating a translation chain that demonst
Before you begin, ensure you have the following prerequisites:
-- .NET 8.0 SDK or later
+- .NET 10 SDK or later
- Azure OpenAI service endpoint and deployment configured
- Azure CLI installed and authenticated (for Azure credential authentication)
diff --git a/dotnet/samples/M365Agent/Agents/WeatherForecastAgent.cs b/dotnet/samples/M365Agent/Agents/WeatherForecastAgent.cs
index e133170b36f..740b959a7a3 100644
--- a/dotnet/samples/M365Agent/Agents/WeatherForecastAgent.cs
+++ b/dotnet/samples/M365Agent/Agents/WeatherForecastAgent.cs
@@ -33,9 +33,9 @@ public WeatherForecastAgent(IChatClient chatClient)
new ChatClientAgentOptions()
{
Name = AgentName,
- Instructions = AgentInstructions,
ChatOptions = new ChatOptions()
{
+ Instructions = AgentInstructions,
Tools = [new ApprovalRequiredAIFunction(AIFunctionFactory.Create(GetWeather))],
// We want the agent to return structured output in a known format
// so that we can easily create adaptive cards from the response.
diff --git a/dotnet/samples/M365Agent/M365Agent.csproj b/dotnet/samples/M365Agent/M365Agent.csproj
index 9beff68dc7b..f40d4042046 100644
--- a/dotnet/samples/M365Agent/M365Agent.csproj
+++ b/dotnet/samples/M365Agent/M365Agent.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
b842df34-390f-490d-9dc0-73909363ad16
@@ -20,7 +20,6 @@
-
diff --git a/dotnet/samples/M365Agent/README.md b/dotnet/samples/M365Agent/README.md
index e669474ef99..e61fa438f58 100644
--- a/dotnet/samples/M365Agent/README.md
+++ b/dotnet/samples/M365Agent/README.md
@@ -8,7 +8,7 @@ This Agent Sample is intended to introduce you the basics of integrating Agent F
## Prerequisites
-- [.NET 8.0 SDK or later](https://dotnet.microsoft.com/download)
+- [.NET 10.0 SDK or later](https://dotnet.microsoft.com/download)
- [devtunnel](https://learn.microsoft.com/azure/developer/dev-tunnels/get-started?tabs=windows)
- [Microsoft 365 Agents Toolkit](https://github.com/OfficeDev/microsoft-365-agents-toolkit)
diff --git a/dotnet/samples/Purview/AgentWithPurview/AgentWithPurview.csproj b/dotnet/samples/Purview/AgentWithPurview/AgentWithPurview.csproj
index 8dc509efed1..0a79857d64d 100644
--- a/dotnet/samples/Purview/AgentWithPurview/AgentWithPurview.csproj
+++ b/dotnet/samples/Purview/AgentWithPurview/AgentWithPurview.csproj
@@ -2,7 +2,7 @@
Exe
- net9.0
+ net10.0
enable
enable
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs
index cafbf90b879..887e780104e 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgent.cs
@@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
+using System.Net.ServerSentEvents;
using System.Runtime.CompilerServices;
using System.Text.Json;
using System.Threading;
@@ -74,26 +75,28 @@ public override async Task RunAsync(IEnumerable m
{
_ = Throw.IfNull(messages);
- var a2aMessage = messages.ToA2AMessage();
+ A2AAgentThread typedThread = this.GetA2AThread(thread, options);
- thread ??= this.GetNewThread();
- if (thread is not A2AAgentThread typedThread)
- {
- throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
- }
+ this._logger.LogA2AAgentInvokingAgent(nameof(RunAsync), this.Id, this.Name);
- // Linking the message to the existing conversation, if any.
- a2aMessage.ContextId = typedThread.ContextId;
+ A2AResponse? a2aResponse = null;
- this._logger.LogA2AAgentInvokingAgent(nameof(RunAsync), this.Id, this.Name);
+ if (GetContinuationToken(messages, options) is { } token)
+ {
+ a2aResponse = await this._a2aClient.GetTaskAsync(token.TaskId, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ var a2aMessage = CreateA2AMessage(typedThread, messages);
- var a2aResponse = await this._a2aClient.SendMessageAsync(new MessageSendParams { Message = a2aMessage }, cancellationToken).ConfigureAwait(false);
+ a2aResponse = await this._a2aClient.SendMessageAsync(new MessageSendParams { Message = a2aMessage }, cancellationToken).ConfigureAwait(false);
+ }
this._logger.LogAgentChatClientInvokedAgent(nameof(RunAsync), this.Id, this.Name);
if (a2aResponse is AgentMessage message)
{
- UpdateThreadConversationId(typedThread, message.ContextId);
+ UpdateThread(typedThread, message.ContextId);
return new AgentRunResponse
{
@@ -101,21 +104,30 @@ public override async Task RunAsync(IEnumerable m
ResponseId = message.MessageId,
RawRepresentation = message,
Messages = [message.ToChatMessage()],
- AdditionalProperties = message.Metadata.ToAdditionalProperties(),
+ AdditionalProperties = message.Metadata?.ToAdditionalProperties(),
};
}
+
if (a2aResponse is AgentTask agentTask)
{
- UpdateThreadConversationId(typedThread, agentTask.ContextId);
+ UpdateThread(typedThread, agentTask.ContextId, agentTask.Id);
- return new AgentRunResponse
+ var response = new AgentRunResponse
{
AgentId = this.Id,
ResponseId = agentTask.Id,
RawRepresentation = agentTask,
- Messages = agentTask.ToChatMessages(),
- AdditionalProperties = agentTask.Metadata.ToAdditionalProperties(),
+ Messages = agentTask.ToChatMessages() ?? [],
+ ContinuationToken = CreateContinuationToken(agentTask.Id, agentTask.Status.State),
+ AdditionalProperties = agentTask.Metadata?.ToAdditionalProperties(),
};
+
+ if (agentTask.ToChatMessages() is { Count: > 0 } taskMessages)
+ {
+ response.Messages = taskMessages;
+ }
+
+ return response;
}
throw new NotSupportedException($"Only Message and AgentTask responses are supported from A2A agents. Received: {a2aResponse.GetType().FullName ?? "null"}");
@@ -126,43 +138,63 @@ public override async IAsyncEnumerable RunStreamingAsync
{
_ = Throw.IfNull(messages);
- var a2aMessage = messages.ToA2AMessage();
+ A2AAgentThread typedThread = this.GetA2AThread(thread, options);
- thread ??= this.GetNewThread();
- if (thread is not A2AAgentThread typedThread)
+ this._logger.LogA2AAgentInvokingAgent(nameof(RunStreamingAsync), this.Id, this.Name);
+
+ ConfiguredCancelableAsyncEnumerable> a2aSseEvents;
+
+ if (options?.ContinuationToken is not null)
{
- throw new InvalidOperationException("The provided thread is not compatible with the agent. Only threads created by the agent can be used.");
+ // Task stream resumption is not well defined in the A2A v2.* specification, leaving it to the agent implementations.
+ // The v3.0 specification improves this by defining task stream reconnection that allows obtaining the same stream
+ // from the beginning, but it does not define stream resumption from a specific point in the stream.
+ // Therefore, the code should be updated once the A2A .NET library supports the A2A v3.0 specification,
+ // and AF has the necessary model to allow consumers to know whether they need to resume the stream and add new updates to
+ // the existing ones or reconnect the stream and obtain all updates again.
+ // For more details, see the following issue: https://github.com/microsoft/agent-framework/issues/1764
+ throw new InvalidOperationException("Reconnecting to task streams using continuation tokens is not supported yet.");
+ // a2aSseEvents = this._a2aClient.SubscribeToTaskAsync(token.TaskId, cancellationToken).ConfigureAwait(false);
}
- // Linking the message to the existing conversation, if any.
- a2aMessage.ContextId = typedThread.ContextId;
-
- this._logger.LogA2AAgentInvokingAgent(nameof(RunStreamingAsync), this.Id, this.Name);
+ var a2aMessage = CreateA2AMessage(typedThread, messages);
- var a2aSseEvents = this._a2aClient.SendMessageStreamingAsync(new MessageSendParams { Message = a2aMessage }, cancellationToken).ConfigureAwait(false);
+ a2aSseEvents = this._a2aClient.SendMessageStreamingAsync(new MessageSendParams { Message = a2aMessage }, cancellationToken).ConfigureAwait(false);
this._logger.LogAgentChatClientInvokedAgent(nameof(RunStreamingAsync), this.Id, this.Name);
+ string? contextId = null;
+ string? taskId = null;
+
await foreach (var sseEvent in a2aSseEvents)
{
- if (sseEvent.Data is not AgentMessage message)
+ if (sseEvent.Data is AgentMessage message)
{
- throw new NotSupportedException($"Only message responses are supported from A2A agents. Received: {sseEvent.Data?.GetType().FullName ?? "null"}");
+ contextId = message.ContextId;
+
+ yield return this.ConvertToAgentResponseUpdate(message);
}
+ else if (sseEvent.Data is AgentTask task)
+ {
+ contextId = task.ContextId;
+ taskId = task.Id;
- UpdateThreadConversationId(typedThread, message.ContextId);
+ yield return this.ConvertToAgentResponseUpdate(task);
+ }
+ else if (sseEvent.Data is TaskUpdateEvent taskUpdateEvent)
+ {
+ contextId = taskUpdateEvent.ContextId;
+ taskId = taskUpdateEvent.TaskId;
- yield return new AgentRunResponseUpdate
+ yield return this.ConvertToAgentResponseUpdate(taskUpdateEvent);
+ }
+ else
{
- AgentId = this.Id,
- ResponseId = message.MessageId,
- RawRepresentation = message,
- Role = ChatRole.Assistant,
- MessageId = message.MessageId,
- Contents = [.. message.Parts.Select(part => part.ToAIContent()).OfType()],
- AdditionalProperties = message.Metadata.ToAdditionalProperties(),
- };
+ throw new NotSupportedException($"Only message, task, task update events are supported from A2A agents. Received: {sseEvent.Data.GetType().FullName ?? "null"}");
+ }
}
+
+ UpdateThread(typedThread, contextId, taskId);
}
///
@@ -177,7 +209,27 @@ public override async IAsyncEnumerable RunStreamingAsync
///
public override string? Description => this._description ?? base.Description;
- private static void UpdateThreadConversationId(A2AAgentThread? thread, string? contextId)
+ private A2AAgentThread GetA2AThread(AgentThread? thread, AgentRunOptions? options)
+ {
+ // Aligning with other agent implementations that support background responses, where
+ // a thread is required for background responses to prevent inconsistent experience
+ // for callers if they forget to provide the thread for initial or follow-up runs.
+ if (options?.AllowBackgroundResponses is true && thread is null)
+ {
+ throw new InvalidOperationException("A thread must be provided when AllowBackgroundResponses is enabled.");
+ }
+
+ thread ??= this.GetNewThread();
+
+ if (thread is not A2AAgentThread typedThread)
+ {
+ throw new InvalidOperationException($"The provided thread type {thread.GetType()} is not compatible with the agent. Only A2A agent created threads are supported.");
+ }
+
+ return typedThread;
+ }
+
+ private static void UpdateThread(A2AAgentThread? thread, string? contextId, string? taskId = null)
{
if (thread is null)
{
@@ -194,5 +246,93 @@ private static void UpdateThreadConversationId(A2AAgentThread? thread, string? c
// Assign a server-generated context Id to the thread if it's not already set.
thread.ContextId ??= contextId;
+ thread.TaskId = taskId;
+ }
+
+ private static AgentMessage CreateA2AMessage(A2AAgentThread typedThread, IEnumerable messages)
+ {
+ var a2aMessage = messages.ToA2AMessage();
+
+ // Linking the message to the existing conversation, if any.
+ // See: https://github.com/a2aproject/A2A/blob/main/docs/topics/life-of-a-task.md#group-related-interactions
+ a2aMessage.ContextId = typedThread.ContextId;
+
+ // Link the message as a follow-up to an existing task, if any.
+ // See: https://github.com/a2aproject/A2A/blob/main/docs/topics/life-of-a-task.md#task-refinements
+ a2aMessage.ReferenceTaskIds = typedThread.TaskId is null ? null : [typedThread.TaskId];
+
+ return a2aMessage;
+ }
+
+ private static A2AContinuationToken? GetContinuationToken(IEnumerable messages, AgentRunOptions? options = null)
+ {
+ if (options?.ContinuationToken is ResponseContinuationToken token)
+ {
+ if (messages.Any())
+ {
+ throw new InvalidOperationException("Messages are not allowed when continuing a background response using a continuation token.");
+ }
+
+ return A2AContinuationToken.FromToken(token);
+ }
+
+ return null;
+ }
+
+ private static A2AContinuationToken? CreateContinuationToken(string taskId, TaskState state)
+ {
+ if (state == TaskState.Submitted || state == TaskState.Working)
+ {
+ return new A2AContinuationToken(taskId);
+ }
+
+ return null;
+ }
+
+ private AgentRunResponseUpdate ConvertToAgentResponseUpdate(AgentMessage message)
+ {
+ return new AgentRunResponseUpdate
+ {
+ AgentId = this.Id,
+ ResponseId = message.MessageId,
+ RawRepresentation = message,
+ Role = ChatRole.Assistant,
+ MessageId = message.MessageId,
+ Contents = message.Parts.ConvertAll(part => part.ToAIContent()),
+ AdditionalProperties = message.Metadata?.ToAdditionalProperties(),
+ };
+ }
+
+ private AgentRunResponseUpdate ConvertToAgentResponseUpdate(AgentTask task)
+ {
+ return new AgentRunResponseUpdate
+ {
+ AgentId = this.Id,
+ ResponseId = task.Id,
+ RawRepresentation = task,
+ Role = ChatRole.Assistant,
+ Contents = task.ToAIContents(),
+ AdditionalProperties = task.Metadata?.ToAdditionalProperties(),
+ };
+ }
+
+ private AgentRunResponseUpdate ConvertToAgentResponseUpdate(TaskUpdateEvent taskUpdateEvent)
+ {
+ AgentRunResponseUpdate responseUpdate = new()
+ {
+ AgentId = this.Id,
+ ResponseId = taskUpdateEvent.TaskId,
+ RawRepresentation = taskUpdateEvent,
+ Role = ChatRole.Assistant,
+ AdditionalProperties = taskUpdateEvent.Metadata?.ToAdditionalProperties() ?? [],
+ };
+
+ if (taskUpdateEvent is TaskArtifactUpdateEvent artifactUpdateEvent)
+ {
+ responseUpdate.Contents = artifactUpdateEvent.Artifact.ToAIContents();
+ responseUpdate.RawRepresentation = artifactUpdateEvent;
+ }
+
+ return responseUpdate;
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentThread.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentThread.cs
index 010df78a02d..55942c8dd1c 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentThread.cs
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AAgentThread.cs
@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
+using System;
using System.Text.Json;
namespace Microsoft.Agents.AI.A2A;
@@ -7,22 +8,59 @@ namespace Microsoft.Agents.AI.A2A;
///
/// Thread for A2A based agents.
///
-public sealed class A2AAgentThread : ServiceIdAgentThread
+public sealed class A2AAgentThread : AgentThread
{
internal A2AAgentThread()
{
}
- internal A2AAgentThread(JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null) : base(serializedThreadState, jsonSerializerOptions)
+ internal A2AAgentThread(JsonElement serializedThreadState, JsonSerializerOptions? jsonSerializerOptions = null)
{
+ if (serializedThreadState.ValueKind != JsonValueKind.Object)
+ {
+ throw new ArgumentException("The serialized thread state must be a JSON object.", nameof(serializedThreadState));
+ }
+
+ var state = serializedThreadState.Deserialize(
+ A2AJsonUtilities.DefaultOptions.GetTypeInfo(typeof(A2AAgentThreadState))) as A2AAgentThreadState;
+
+ if (state?.ContextId is string contextId)
+ {
+ this.ContextId = contextId;
+ }
+
+ if (state?.TaskId is string taskId)
+ {
+ this.TaskId = taskId;
+ }
}
///
/// Gets the ID for the current conversation with the A2A agent.
///
- public string? ContextId
+ public string? ContextId { get; internal set; }
+
+ ///
+ /// Gets the ID for the task the agent is currently working on.
+ ///
+ public string? TaskId { get; internal set; }
+
+ ///
+ public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ var state = new A2AAgentThreadState
+ {
+ ContextId = this.ContextId,
+ TaskId = this.TaskId
+ };
+
+ return JsonSerializer.SerializeToElement(state, A2AJsonUtilities.DefaultOptions.GetTypeInfo(typeof(A2AAgentThreadState)));
+ }
+
+ internal sealed class A2AAgentThreadState
{
- get { return this.ServiceThreadId; }
- internal set { this.ServiceThreadId = value; }
+ public string? ContextId { get; set; }
+
+ public string? TaskId { get; set; }
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AContinuationToken.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AContinuationToken.cs
new file mode 100644
index 00000000000..5233adb88f4
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AContinuationToken.cs
@@ -0,0 +1,81 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.IO;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI.A2A;
+#pragma warning disable MEAI001 // Type is for evaluation purposes only and is subject to change or removal in future updates. Suppress this diagnostic to proceed.
+internal class A2AContinuationToken : ResponseContinuationToken
+{
+ internal A2AContinuationToken(string taskId)
+ {
+ _ = Throw.IfNullOrEmpty(taskId);
+
+ this.TaskId = taskId;
+ }
+
+ internal string TaskId { get; }
+
+ internal static A2AContinuationToken FromToken(ResponseContinuationToken token)
+ {
+ if (token is A2AContinuationToken longRunContinuationToken)
+ {
+ return longRunContinuationToken;
+ }
+
+ ReadOnlyMemory data = token.ToBytes();
+
+ if (data.Length == 0)
+ {
+ Throw.ArgumentException(nameof(token), "Failed to create A2AContinuationToken from provided token because it does not contain any data.");
+ }
+
+ Utf8JsonReader reader = new(data.Span);
+
+ string taskId = null!;
+
+ reader.Read();
+
+ while (reader.Read())
+ {
+ if (reader.TokenType == JsonTokenType.EndObject)
+ {
+ break;
+ }
+
+ string propertyName = reader.GetString() ?? throw new JsonException("Failed to read property name from continuation token.");
+
+ switch (propertyName)
+ {
+ case "taskId":
+ reader.Read();
+ taskId = reader.GetString()!;
+ break;
+ default:
+ throw new JsonException($"Unrecognized property '{propertyName}'.");
+ }
+ }
+
+ return new(taskId);
+ }
+
+ public override ReadOnlyMemory ToBytes()
+ {
+ using MemoryStream stream = new();
+ using Utf8JsonWriter writer = new(stream);
+
+ writer.WriteStartObject();
+
+ writer.WriteString("taskId", this.TaskId);
+
+ writer.WriteEndObject();
+
+ writer.Flush();
+ stream.Position = 0;
+
+ return stream.ToArray();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/A2AJsonUtilities.cs b/dotnet/src/Microsoft.Agents.AI.A2A/A2AJsonUtilities.cs
new file mode 100644
index 00000000000..2fbb2e86176
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/A2AJsonUtilities.cs
@@ -0,0 +1,80 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Text.Encodings.Web;
+using System.Text.Json;
+using System.Text.Json.Serialization;
+using Microsoft.Agents.AI.A2A;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides utility methods and configurations for JSON serialization operations for A2A agent types.
+///
+public static partial class A2AJsonUtilities
+{
+ ///
+ /// Gets the default instance used for JSON serialization operations of A2A agent types.
+ ///
+ ///
+ ///
+ /// For Native AOT or applications disabling , this instance
+ /// includes source generated contracts for A2A agent types.
+ ///
+ ///
+ /// It additionally turns on the following settings:
+ ///
+ /// - Enables defaults.
+ /// - Enables as the default ignore condition for properties.
+ /// - Enables as the default number handling for number types.
+ /// -
+ /// Enables when escaping JSON strings.
+ /// Consuming applications must ensure that JSON outputs are adequately escaped before embedding in other document formats, such as HTML and XML.
+ ///
+ ///
+ ///
+ ///
+ public static JsonSerializerOptions DefaultOptions { get; } = CreateDefaultOptions();
+
+ ///
+ /// Creates and configures the default JSON serialization options for agent abstraction types.
+ ///
+ /// The configured options.
+ [UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050:RequiresDynamicCode", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
+ [UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access", Justification = "Converter is guarded by IsReflectionEnabledByDefault check.")]
+ private static JsonSerializerOptions CreateDefaultOptions()
+ {
+ // Copy the configuration from the source generated context.
+ JsonSerializerOptions options = new(JsonContext.Default.Options)
+ {
+ Encoder = JavaScriptEncoder.UnsafeRelaxedJsonEscaping, // same as AIJsonUtilities
+ };
+
+ // Chain in the resolvers from both AIJsonUtilities and our source generated context.
+ // We want AIJsonUtilities first to ensure any M.E.AI types are handled via its resolver.
+ options.TypeInfoResolverChain.Clear();
+ options.TypeInfoResolverChain.Add(AgentAbstractionsJsonUtilities.DefaultOptions.TypeInfoResolver!);
+
+ // If reflection-based serialization is enabled by default, this includes
+ // the default type info resolver that utilizes reflection, but we need to manually
+ // apply the same converter AIJsonUtilities adds for string-based enum serialization,
+ // as that's not propagated as part of the resolver.
+ if (JsonSerializer.IsReflectionEnabledByDefault)
+ {
+ options.Converters.Add(new JsonStringEnumConverter());
+ }
+
+ options.MakeReadOnly();
+ return options;
+ }
+
+ [JsonSourceGenerationOptions(JsonSerializerDefaults.Web,
+ UseStringEnumConverter = true,
+ DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
+ NumberHandling = JsonNumberHandling.AllowReadingFromString)]
+
+ // A2A agent types
+ [JsonSerializable(typeof(A2AAgentThread.A2AAgentThreadState))]
+ [ExcludeFromCodeCoverage]
+ private sealed partial class JsonContext : JsonSerializerContext;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentTaskExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentTaskExtensions.cs
index 236ecfb174f..a577ad93648 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentTaskExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AAgentTaskExtensions.cs
@@ -11,20 +11,37 @@ namespace A2A;
///
internal static class A2AAgentTaskExtensions
{
- internal static IList ToChatMessages(this AgentTask agentTask)
+ internal static IList? ToChatMessages(this AgentTask agentTask)
{
_ = Throw.IfNull(agentTask);
- List messages = [];
+ List? messages = null;
- if (agentTask.Artifacts is not null)
+ if (agentTask?.Artifacts is { Count: > 0 })
{
foreach (var artifact in agentTask.Artifacts)
{
- messages.Add(artifact.ToChatMessage());
+ (messages ??= []).Add(artifact.ToChatMessage());
}
}
return messages;
}
+
+ internal static IList? ToAIContents(this AgentTask agentTask)
+ {
+ _ = Throw.IfNull(agentTask);
+
+ List? aiContents = null;
+
+ if (agentTask.Artifacts is not null)
+ {
+ foreach (var artifact in agentTask.Artifacts)
+ {
+ (aiContents ??= []).AddRange(artifact.ToAIContents());
+ }
+ }
+
+ return aiContents;
+ }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AArtifactExtensions.cs b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AArtifactExtensions.cs
index 36683d549b3..cecd9a85047 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AArtifactExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/Extensions/A2AArtifactExtensions.cs
@@ -12,21 +12,15 @@ internal static class A2AArtifactExtensions
{
internal static ChatMessage ToChatMessage(this Artifact artifact)
{
- List? aiContents = null;
-
- foreach (var part in artifact.Parts)
- {
- var content = part.ToAIContent();
- if (content is not null)
- {
- (aiContents ??= []).Add(content);
- }
- }
-
- return new ChatMessage(ChatRole.Assistant, aiContents)
+ return new ChatMessage(ChatRole.Assistant, artifact.ToAIContents())
{
AdditionalProperties = artifact.Metadata.ToAdditionalProperties(),
RawRepresentation = artifact,
};
}
+
+ internal static List ToAIContents(this Artifact artifact)
+ {
+ return artifact.Parts.ConvertAll(part => part.ToAIContent());
+ }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj b/dotnet/src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj
index 46e3c97d8fe..b1b9ba76710 100644
--- a/dotnet/src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj
@@ -1,21 +1,19 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
+ $(NoWarn);MEAI001
true
+ true
-
-
diff --git a/dotnet/src/Microsoft.Agents.AI.AGUI/AGUIChatClient.cs b/dotnet/src/Microsoft.Agents.AI.AGUI/AGUIChatClient.cs
index a168e2eab64..3571d97085c 100644
--- a/dotnet/src/Microsoft.Agents.AI.AGUI/AGUIChatClient.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AGUI/AGUIChatClient.cs
@@ -82,7 +82,7 @@ public override Task GetResponseAsync(IEnumerable mes
.ToChatResponseAsync(cancellationToken);
///
- public async override IAsyncEnumerable GetStreamingResponseAsync(
+ public override async IAsyncEnumerable GetStreamingResponseAsync(
IEnumerable messages,
ChatOptions? options = null,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
diff --git a/dotnet/src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj b/dotnet/src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj
index 35f89f889fb..57cb375e145 100644
--- a/dotnet/src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
@@ -24,8 +22,7 @@
-
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIJsonSerializerContext.cs b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIJsonSerializerContext.cs
index 0b571c4ff19..b13a8036252 100644
--- a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIJsonSerializerContext.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/AGUIJsonSerializerContext.cs
@@ -59,6 +59,4 @@ namespace Microsoft.Agents.AI.AGUI;
[JsonSerializable(typeof(float))]
[JsonSerializable(typeof(bool))]
[JsonSerializable(typeof(decimal))]
-internal sealed partial class AGUIJsonSerializerContext : JsonSerializerContext
-{
-}
+internal sealed partial class AGUIJsonSerializerContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
index 46184a6588f..f5fb103bd47 100644
--- a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/ChatResponseUpdateAGUIExtensions.cs
@@ -421,7 +421,7 @@ chatResponse.Contents[0] is TextContent &&
// State snapshot event
yield return new StateSnapshotEvent
{
-#if NET472 || NETSTANDARD2_0
+#if !NET
Snapshot = (JsonElement?)JsonSerializer.Deserialize(
dataContent.Data.ToArray(),
jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)))
@@ -438,7 +438,7 @@ chatResponse.Contents[0] is TextContent &&
// but its not up to us to validate that here.
yield return new StateDeltaEvent
{
-#if NET472 || NETSTANDARD2_0
+#if !NET
Delta = (JsonElement?)JsonSerializer.Deserialize(
dataContent.Data.ToArray(),
jsonSerializerOptions.GetTypeInfo(typeof(JsonElement)))
@@ -455,7 +455,7 @@ chatResponse.Contents[0] is TextContent &&
yield return new TextMessageContentEvent
{
MessageId = chatResponse.MessageId!,
-#if NET472 || NETSTANDARD2_0
+#if !NET
Delta = Encoding.UTF8.GetString(dataContent.Data.ToArray())
#else
Delta = Encoding.UTF8.GetString(dataContent.Data.Span)
diff --git a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/RunAgentInput.cs b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/RunAgentInput.cs
index a9396ff7228..f64177146fd 100644
--- a/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/RunAgentInput.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AGUI/Shared/RunAgentInput.cs
@@ -32,7 +32,7 @@ internal sealed class RunAgentInput
[JsonPropertyName("context")]
public AGUIContextItem[] Context { get; set; } = [];
- [JsonPropertyName("forwardedProperties")]
+ [JsonPropertyName("forwardedProps")]
[JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingDefault)]
public JsonElement ForwardedProperties { get; set; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs
index 35aa866552b..eba6f84687b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIAgent.cs
@@ -328,28 +328,4 @@ public abstract IAsyncEnumerable RunStreamingAsync(
AgentThread? thread = null,
AgentRunOptions? options = null,
CancellationToken cancellationToken = default);
-
- ///
- /// Notifies the specified thread about new messages that have been added to the conversation.
- ///
- /// The conversation thread to notify about the new messages.
- /// The collection of new messages to report to the thread.
- /// The to monitor for cancellation requests. The default is .
- /// A task that represents the asynchronous notification operation.
- /// or is .
- ///
- ///
- /// This method ensures that conversation threads are kept informed about message additions, which
- /// is important for threads that manage their own state, memory components, or derived context.
- /// While all agent implementations should notify their threads, the specific actions taken by
- /// each thread type may vary.
- ///
- ///
- protected static async Task NotifyThreadOfNewMessagesAsync(AgentThread thread, IEnumerable messages, CancellationToken cancellationToken)
- {
- _ = Throw.IfNull(thread);
- _ = Throw.IfNull(messages);
-
- await thread.MessagesReceivedAsync(messages, cancellationToken).ConfigureAwait(false);
- }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs
index a4b3f5d9564..fd3ff10fc22 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AIContextProvider.cs
@@ -124,7 +124,7 @@ public virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOption
/// that will be used. Context providers can use this information to determine what additional context
/// should be provided for the invocation.
///
- public class InvokingContext
+ public sealed class InvokingContext
{
///
/// Initializes a new instance of the class with the specified request messages.
@@ -153,7 +153,7 @@ public InvokingContext(IEnumerable requestMessages)
/// request messages that were used and the response messages that were generated. It also indicates
/// whether the invocation succeeded or failed.
///
- public class InvokedContext
+ public sealed class InvokedContext
{
///
/// Initializes a new instance of the class with the specified request messages.
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs
index 72629792079..9cd6d51680f 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunOptions.cs
@@ -49,7 +49,7 @@ public AgentRunOptions(AgentRunOptions options)
/// can be polled for completion by obtaining the token from the property
/// and passing it via this property on subsequent calls to .
///
- public object? ContinuationToken { get; set; }
+ public ResponseContinuationToken? ContinuationToken { get; set; }
///
/// Gets or sets a value indicating whether the background responses are allowed.
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs
index 2beb2879183..001cfd94694 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponse.cs
@@ -175,7 +175,7 @@ public IList Messages
/// to poll for completion.
///
///
- public object? ContinuationToken { get; set; }
+ public ResponseContinuationToken? ContinuationToken { get; set; }
///
/// Gets or sets the timestamp indicating when this response was created.
@@ -336,7 +336,7 @@ public bool TryDeserialize(JsonSerializerOptions serializerOptions, [NotNullW
private static T? DeserializeFirstTopLevelObject(string json, JsonTypeInfo typeInfo)
{
-#if NET9_0_OR_GREATER
+#if NET
// We need to deserialize only the first top-level object as a workaround for a common LLM backend
// issue. GPT 3.5 Turbo commonly returns multiple top-level objects after doing a function call.
// See https://community.openai.com/t/2-json-objects-returned-when-using-function-calling-and-json-mode/574348
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponseUpdate.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponseUpdate.cs
index 954893dbcbb..ccf3deae54b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponseUpdate.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentRunResponseUpdate.cs
@@ -38,9 +38,6 @@ public class AgentRunResponseUpdate
/// The response update content items.
private IList? _contents;
- /// The name of the author of the update.
- private string? _authorName;
-
/// Initializes a new instance of the class.
[JsonConstructor]
public AgentRunResponseUpdate()
@@ -84,8 +81,8 @@ public AgentRunResponseUpdate(ChatResponseUpdate chatResponseUpdate)
/// Gets or sets the name of the author of the response update.
public string? AuthorName
{
- get => this._authorName;
- set => this._authorName = string.IsNullOrWhiteSpace(value) ? null : value;
+ get => field;
+ set => field = string.IsNullOrWhiteSpace(value) ? null : value;
}
/// Gets or sets the role of the author of the response update.
@@ -162,7 +159,7 @@ public IList Contents
/// to resume streaming from the point of interruption.
///
///
- public object? ContinuationToken { get; set; }
+ public ResponseContinuationToken? ContinuationToken { get; set; }
///
public override string ToString() => this.Text;
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs
index fb5863a5c95..4794457f415 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/AgentThread.cs
@@ -1,11 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
-using System.Collections.Generic;
using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
-using Microsoft.Extensions.AI;
using Microsoft.Shared.Diagnostics;
namespace Microsoft.Agents.AI;
@@ -65,19 +61,6 @@ protected AgentThread()
public virtual JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
=> default;
- ///
- /// This method is called when new messages have been contributed to the chat by any participant.
- ///
- ///
- /// Inheritors can use this method to update their context based on the new message.
- ///
- /// The new messages.
- /// The to monitor for cancellation requests. The default is .
- /// A task that completes when the context has been updated.
- /// The thread has been deleted.
- protected internal virtual Task MessagesReceivedAsync(IEnumerable newMessages, CancellationToken cancellationToken = default)
- => Task.CompletedTask;
-
/// Asks the for an object of the specified type .
/// The type of object being requested.
/// An optional key that can be used to help identify the target service.
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs
index af6080a7150..13fcc134f01 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/InMemoryAgentThread.cs
@@ -4,8 +4,6 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Text.Json;
-using System.Threading;
-using System.Threading.Tasks;
using Microsoft.Extensions.AI;
namespace Microsoft.Agents.AI;
@@ -116,10 +114,6 @@ public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptio
public override object? GetService(Type serviceType, object? serviceKey = null) =>
base.GetService(serviceType, serviceKey) ?? this.MessageStore?.GetService(serviceType, serviceKey);
- ///
- protected internal override Task MessagesReceivedAsync(IEnumerable newMessages, CancellationToken cancellationToken = default)
- => this.MessageStore.AddMessagesAsync(newMessages, cancellationToken);
-
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
private string DebuggerDisplay => $"Count = {this.MessageStore.Count}";
diff --git a/dotnet/src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj b/dotnet/src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj
index 4add7f427c8..6b6f9d44f2c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
Microsoft.Agents.AI
$(NoWarn);MEAI001
preview
diff --git a/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicBetaServiceExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicBetaServiceExtensions.cs
new file mode 100644
index 00000000000..6b4f872a63e
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicBetaServiceExtensions.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+
+namespace Anthropic.Services;
+
+///
+/// Provides extension methods for the class.
+///
+public static class AnthropicBetaServiceExtensions
+{
+ ///
+ /// Specifies the default maximum number of tokens allowed for processing operations.
+ ///
+ public static int DefaultMaxTokens { get; set; } = 4096;
+
+ ///
+ /// Creates a new AI agent using the specified model and options.
+ ///
+ /// The Anthropic beta service.
+ /// The model to use for chat completions.
+ /// The instructions for the AI agent.
+ /// The name of the AI agent.
+ /// The description of the AI agent.
+ /// The tools available to the AI agent.
+ /// The default maximum tokens for chat completions. Defaults to if not provided.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// The created AI agent.
+ public static ChatClientAgent CreateAIAgent(
+ this IBetaService betaService,
+ string model,
+ string? instructions = null,
+ string? name = null,
+ string? description = null,
+ IList? tools = null,
+ int? defaultMaxTokens = null,
+ Func? clientFactory = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
+ {
+ var options = new ChatClientAgentOptions
+ {
+ Name = name,
+ Description = description,
+ };
+
+ if (!string.IsNullOrWhiteSpace(instructions))
+ {
+ options.ChatOptions ??= new();
+ options.ChatOptions.Instructions = instructions;
+ }
+
+ if (tools is { Count: > 0 })
+ {
+ options.ChatOptions ??= new();
+ options.ChatOptions.Tools = tools;
+ }
+
+ var chatClient = betaService.AsIChatClient(model, defaultMaxTokens ?? DefaultMaxTokens);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, options, loggerFactory, services);
+ }
+
+ ///
+ /// Creates an AI agent from an using the Anthropic Chat Completion API.
+ ///
+ /// The Anthropic to use for the agent.
+ /// Full set of options to configure the agent.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// An instance backed by the Anthropic Chat Completion service.
+ /// Thrown when or is .
+ public static ChatClientAgent CreateAIAgent(
+ this IBetaService betaService,
+ ChatClientAgentOptions options,
+ Func? clientFactory = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(betaService);
+ Throw.IfNull(options);
+
+ var chatClient = betaService.AsIChatClient();
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, options, loggerFactory, services);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientExtensions.cs
new file mode 100644
index 00000000000..b4b8e2bc1e7
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientExtensions.cs
@@ -0,0 +1,103 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Logging;
+using Microsoft.Shared.Diagnostics;
+
+namespace Anthropic;
+
+///
+/// Provides extension methods for the class.
+///
+public static class AnthropicClientExtensions
+{
+ ///
+ /// Specifies the default maximum number of tokens allowed for processing operations.
+ ///
+ public static int DefaultMaxTokens { get; set; } = 4096;
+
+ ///
+ /// Creates a new AI agent using the specified model and options.
+ ///
+ /// An Anthropic to use with the agent..
+ /// The model to use for chat completions.
+ /// The instructions for the AI agent.
+ /// The name of the AI agent.
+ /// The description of the AI agent.
+ /// The tools available to the AI agent.
+ /// The default maximum tokens for chat completions. Defaults to if not provided.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// The created AI agent.
+ public static ChatClientAgent CreateAIAgent(
+ this IAnthropicClient client,
+ string model,
+ string? instructions = null,
+ string? name = null,
+ string? description = null,
+ IList? tools = null,
+ int? defaultMaxTokens = null,
+ Func? clientFactory = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
+ {
+ var options = new ChatClientAgentOptions
+ {
+ Name = name,
+ Description = description,
+ };
+
+ if (!string.IsNullOrWhiteSpace(instructions))
+ {
+ options.ChatOptions ??= new();
+ options.ChatOptions.Instructions = instructions;
+ }
+
+ if (tools is { Count: > 0 })
+ {
+ options.ChatOptions ??= new();
+ options.ChatOptions.Tools = tools;
+ }
+
+ var chatClient = client.AsIChatClient(model, defaultMaxTokens ?? DefaultMaxTokens);
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, options, loggerFactory, services);
+ }
+
+ ///
+ /// Creates an AI agent from an using the Anthropic Chat Completion API.
+ ///
+ /// An Anthropic to use with the agent..
+ /// Full set of options to configure the agent.
+ /// Provides a way to customize the creation of the underlying used by the agent.
+ /// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// An instance backed by the Anthropic Chat Completion service.
+ /// Thrown when or is .
+ public static ChatClientAgent CreateAIAgent(
+ this IAnthropicClient client,
+ ChatClientAgentOptions options,
+ Func? clientFactory = null,
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
+ {
+ Throw.IfNull(client);
+ Throw.IfNull(options);
+
+ var chatClient = client.AsIChatClient();
+
+ if (clientFactory is not null)
+ {
+ chatClient = clientFactory(chatClient);
+ }
+
+ return new ChatClientAgent(chatClient, options, loggerFactory, services);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientJsonContext.cs b/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientJsonContext.cs
new file mode 100644
index 00000000000..080745f1483
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Anthropic/AnthropicClientJsonContext.cs
@@ -0,0 +1,13 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+#pragma warning disable CA1812
+
+using System.Text.Json;
+using System.Text.Json.Serialization;
+
+namespace Microsoft.Agents.AI.Anthropic;
+
+[JsonSerializable(typeof(JsonElement))]
+[JsonSerializable(typeof(string))]
+[JsonSerializable(typeof(Dictionary))]
+internal sealed partial class AnthropicClientJsonContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj b/dotnet/src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj
new file mode 100644
index 00000000000..60b90a02122
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj
@@ -0,0 +1,26 @@
+
+
+
+ preview
+ enable
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Microsoft Agent Framework Anthropic Agents
+ Provides Microsoft Agent Framework support for Anthropic Agents.
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj
index bdf668391b7..31785a8fa98 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
enable
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs
index 1d5f228fcc2..5ca14365871 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI.Persistent/PersistentAgentsClientExtensions.cs
@@ -17,15 +17,21 @@ public static class PersistentAgentsClientExtensions
/// The response containing the persistent agent to be converted. Cannot be .
/// The default to use when interacting with the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the persistent agent.
- public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, Response persistentAgentResponse, ChatOptions? chatOptions = null, Func? clientFactory = null)
+ public static ChatClientAgent GetAIAgent(
+ this PersistentAgentsClient persistentAgentsClient,
+ Response persistentAgentResponse,
+ ChatOptions? chatOptions = null,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (persistentAgentResponse is null)
{
throw new ArgumentNullException(nameof(persistentAgentResponse));
}
- return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, chatOptions, clientFactory);
+ return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, chatOptions, clientFactory, services);
}
///
@@ -35,8 +41,14 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA
/// The persistent agent metadata to be converted. Cannot be .
/// The default to use when interacting with the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the persistent agent.
- public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, PersistentAgent persistentAgentMetadata, ChatOptions? chatOptions = null, Func? clientFactory = null)
+ public static ChatClientAgent GetAIAgent(
+ this PersistentAgentsClient persistentAgentsClient,
+ PersistentAgent persistentAgentMetadata,
+ ChatOptions? chatOptions = null,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (persistentAgentMetadata is null)
{
@@ -55,14 +67,19 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA
chatClient = clientFactory(chatClient);
}
+ if (!string.IsNullOrWhiteSpace(persistentAgentMetadata.Instructions) && chatOptions?.Instructions is null)
+ {
+ chatOptions ??= new ChatOptions();
+ chatOptions.Instructions = persistentAgentMetadata.Instructions;
+ }
+
return new ChatClientAgent(chatClient, options: new()
{
Id = persistentAgentMetadata.Id,
Name = persistentAgentMetadata.Name,
Description = persistentAgentMetadata.Description,
- Instructions = persistentAgentMetadata.Instructions,
ChatOptions = chatOptions
- });
+ }, services: services);
}
///
@@ -73,6 +90,7 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA
/// The ID of the server side agent to create a for.
/// Options that should apply to all runs of the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the persistent agent.
public static ChatClientAgent GetAIAgent(
@@ -80,6 +98,7 @@ public static ChatClientAgent GetAIAgent(
string agentId,
ChatOptions? chatOptions = null,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -93,7 +112,7 @@ public static ChatClientAgent GetAIAgent(
}
var persistentAgentResponse = persistentAgentsClient.Administration.GetAgent(agentId, cancellationToken);
- return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory);
+ return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory, services);
}
///
@@ -104,6 +123,7 @@ public static ChatClientAgent GetAIAgent(
/// The ID of the server side agent to create a for.
/// Options that should apply to all runs of the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the persistent agent.
public static async Task GetAIAgentAsync(
@@ -111,6 +131,7 @@ public static async Task GetAIAgentAsync(
string agentId,
ChatOptions? chatOptions = null,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -124,7 +145,7 @@ public static async Task GetAIAgentAsync(
}
var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false);
- return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory);
+ return persistentAgentsClient.GetAIAgent(persistentAgentResponse, chatOptions, clientFactory, services);
}
///
@@ -134,16 +155,22 @@ public static async Task GetAIAgentAsync(
/// The response containing the persistent agent to be converted. Cannot be .
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the persistent agent.
/// Thrown when or is .
- public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, Response persistentAgentResponse, ChatClientAgentOptions options, Func? clientFactory = null)
+ public static ChatClientAgent GetAIAgent(
+ this PersistentAgentsClient persistentAgentsClient,
+ Response persistentAgentResponse,
+ ChatClientAgentOptions options,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (persistentAgentResponse is null)
{
throw new ArgumentNullException(nameof(persistentAgentResponse));
}
- return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, options, clientFactory);
+ return GetAIAgent(persistentAgentsClient, persistentAgentResponse.Value, options, clientFactory, services);
}
///
@@ -153,9 +180,15 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA
/// The persistent agent metadata to be converted. Cannot be .
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the persistent agent.
/// Thrown when or is .
- public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentAgentsClient, PersistentAgent persistentAgentMetadata, ChatClientAgentOptions options, Func? clientFactory = null)
+ public static ChatClientAgent GetAIAgent(
+ this PersistentAgentsClient persistentAgentsClient,
+ PersistentAgent persistentAgentMetadata,
+ ChatClientAgentOptions options,
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (persistentAgentMetadata is null)
{
@@ -179,19 +212,24 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA
chatClient = clientFactory(chatClient);
}
+ if (!string.IsNullOrWhiteSpace(persistentAgentMetadata.Instructions) && options.ChatOptions?.Instructions is null)
+ {
+ options.ChatOptions ??= new ChatOptions();
+ options.ChatOptions.Instructions = persistentAgentMetadata.Instructions;
+ }
+
var agentOptions = new ChatClientAgentOptions()
{
Id = persistentAgentMetadata.Id,
Name = options.Name ?? persistentAgentMetadata.Name,
Description = options.Description ?? persistentAgentMetadata.Description,
- Instructions = options.Instructions ?? persistentAgentMetadata.Instructions,
ChatOptions = options.ChatOptions,
AIContextProviderFactory = options.AIContextProviderFactory,
ChatMessageStoreFactory = options.ChatMessageStoreFactory,
UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs
};
- return new ChatClientAgent(chatClient, agentOptions);
+ return new ChatClientAgent(chatClient, agentOptions, services: services);
}
///
@@ -201,6 +239,7 @@ public static ChatClientAgent GetAIAgent(this PersistentAgentsClient persistentA
/// The ID of the server side agent to create a for.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the persistent agent.
/// Thrown when or is .
@@ -210,6 +249,7 @@ public static ChatClientAgent GetAIAgent(
string agentId,
ChatClientAgentOptions options,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -228,7 +268,7 @@ public static ChatClientAgent GetAIAgent(
}
var persistentAgentResponse = persistentAgentsClient.Administration.GetAgent(agentId, cancellationToken);
- return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory);
+ return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory, services);
}
///
@@ -238,6 +278,7 @@ public static ChatClientAgent GetAIAgent(
/// The ID of the server side agent to create a for.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the persistent agent.
/// Thrown when or is .
@@ -247,6 +288,7 @@ public static async Task GetAIAgentAsync(
string agentId,
ChatClientAgentOptions options,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -265,7 +307,7 @@ public static async Task GetAIAgentAsync(
}
var persistentAgentResponse = await persistentAgentsClient.Administration.GetAgentAsync(agentId, cancellationToken).ConfigureAwait(false);
- return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory);
+ return persistentAgentsClient.GetAIAgent(persistentAgentResponse, options, clientFactory, services);
}
///
@@ -283,6 +325,7 @@ public static async Task GetAIAgentAsync(
/// The response format for the agent.
/// The metadata for the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the newly created agent.
public static async Task CreateAIAgentAsync(
@@ -298,6 +341,7 @@ public static async Task CreateAIAgentAsync(
BinaryData? responseFormat = null,
IReadOnlyDictionary? metadata = null,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -319,7 +363,7 @@ public static async Task CreateAIAgentAsync(
cancellationToken: cancellationToken).ConfigureAwait(false);
// Get a local proxy for the agent to work with.
- return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, cancellationToken: cancellationToken).ConfigureAwait(false);
+ return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken).ConfigureAwait(false);
}
///
@@ -337,6 +381,7 @@ public static async Task CreateAIAgentAsync(
/// The response format for the agent.
/// The metadata for the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the newly created agent.
public static ChatClientAgent CreateAIAgent(
@@ -352,6 +397,7 @@ public static ChatClientAgent CreateAIAgent(
BinaryData? responseFormat = null,
IReadOnlyDictionary? metadata = null,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -373,7 +419,7 @@ public static ChatClientAgent CreateAIAgent(
cancellationToken: cancellationToken);
// Get a local proxy for the agent to work with.
- return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, cancellationToken: cancellationToken);
+ return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken);
}
///
@@ -383,6 +429,7 @@ public static ChatClientAgent CreateAIAgent(
/// The model to be used by the agent.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the newly created agent.
/// Thrown when or or is .
@@ -392,6 +439,7 @@ public static ChatClientAgent CreateAIAgent(
string model,
ChatClientAgentOptions options,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -415,7 +463,7 @@ public static ChatClientAgent CreateAIAgent(
model: model,
name: options.Name,
description: options.Description,
- instructions: options.Instructions,
+ instructions: options.ChatOptions?.Instructions,
tools: toolDefinitionsAndResources.ToolDefinitions,
toolResources: toolDefinitionsAndResources.ToolResources,
temperature: null,
@@ -431,7 +479,7 @@ public static ChatClientAgent CreateAIAgent(
}
// Get a local proxy for the agent to work with.
- return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, cancellationToken: cancellationToken);
+ return persistentAgentsClient.GetAIAgent(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken);
}
///
@@ -441,6 +489,7 @@ public static ChatClientAgent CreateAIAgent(
/// The model to be used by the agent.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the newly created agent.
/// Thrown when or or is .
@@ -450,6 +499,7 @@ public static async Task CreateAIAgentAsync(
string model,
ChatClientAgentOptions options,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (persistentAgentsClient is null)
@@ -473,7 +523,7 @@ public static async Task CreateAIAgentAsync(
model: model,
name: options.Name,
description: options.Description,
- instructions: options.Instructions,
+ instructions: options.ChatOptions?.Instructions,
tools: toolDefinitionsAndResources.ToolDefinitions,
toolResources: toolDefinitionsAndResources.ToolResources,
temperature: null,
@@ -489,7 +539,7 @@ public static async Task CreateAIAgentAsync(
}
// Get a local proxy for the agent to work with.
- return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, cancellationToken: cancellationToken).ConfigureAwait(false);
+ return await persistentAgentsClient.GetAIAgentAsync(createPersistentAgentResponse.Value.Id, options, clientFactory: clientFactory, services: services, cancellationToken: cancellationToken).ConfigureAwait(false);
}
private static (List? ToolDefinitions, ToolResources? ToolResources, List? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList? tools)
@@ -506,7 +556,7 @@ private static (List? ToolDefinitions, ToolResources? ToolResour
{
case HostedCodeInterpreterTool codeTool:
- toolDefinitions ??= new();
+ toolDefinitions ??= [];
toolDefinitions.Add(new CodeInterpreterToolDefinition());
if (codeTool.Inputs is { Count: > 0 })
@@ -527,7 +577,7 @@ private static (List? ToolDefinitions, ToolResources? ToolResour
break;
case HostedFileSearchTool fileSearchTool:
- toolDefinitions ??= new();
+ toolDefinitions ??= [];
toolDefinitions.Add(new FileSearchToolDefinition
{
FileSearch = new() { MaxNumResults = fileSearchTool.MaximumResultCount }
@@ -550,12 +600,12 @@ private static (List? ToolDefinitions, ToolResources? ToolResour
break;
case HostedWebSearchTool webSearch when webSearch.AdditionalProperties?.TryGetValue("connectionId", out object? connectionId) is true:
- toolDefinitions ??= new();
+ toolDefinitions ??= [];
toolDefinitions.Add(new BingGroundingToolDefinition(new BingGroundingSearchToolParameters([new BingGroundingSearchConfiguration(connectionId!.ToString())])));
break;
default:
- functionToolsAndOtherTools ??= new();
+ functionToolsAndOtherTools ??= [];
functionToolsAndOtherTools.Add(tool);
break;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs
index 2a5ace7a0ad..8acafc8fc35 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClient.cs
@@ -101,7 +101,7 @@ public override async Task GetResponseAsync(IEnumerable
- public async override IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ public override async IAsyncEnumerable GetStreamingResponseAsync(IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
{
var agentOptions = this.GetAgentEnabledChatOptions(options);
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs
index 0ec5f593fdd..dfbdad8e98c 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/AzureAIProjectChatClientExtensions.cs
@@ -393,7 +393,7 @@ public static ChatClientAgent CreateAIAgent(
PromptAgentDefinition agentDefinition = new(model)
{
- Instructions = options.Instructions,
+ Instructions = options.ChatOptions?.Instructions,
Temperature = options.ChatOptions?.Temperature,
TopP = options.ChatOptions?.TopP,
TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
@@ -459,7 +459,7 @@ public static async Task CreateAIAgentAsync(
PromptAgentDefinition agentDefinition = new(model)
{
- Instructions = options.Instructions,
+ Instructions = options.ChatOptions?.Instructions,
Temperature = options.ChatOptions?.Temperature,
TopP = options.ChatOptions?.TopP,
TextOptions = new() { TextFormat = ToOpenAIResponseTextFormat(options.ChatOptions?.ResponseFormat, options.ChatOptions) }
@@ -822,10 +822,9 @@ private static ChatClientAgentOptions CreateChatClientAgentOptions(AgentVersion
if (agentDefinition is PromptAgentDefinition promptAgentDefinition)
{
agentOptions.ChatOptions ??= chatOptions?.Clone() ?? new();
- agentOptions.Instructions = promptAgentDefinition.Instructions;
+ agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
agentOptions.ChatOptions.Temperature = promptAgentDefinition.Temperature;
agentOptions.ChatOptions.TopP = promptAgentDefinition.TopP;
- agentOptions.ChatOptions.Instructions = promptAgentDefinition.Instructions;
}
if (agentTools is { Count: > 0 })
diff --git a/dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj b/dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj
index ff9a1c38fa7..233718b3e47 100644
--- a/dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
enable
true
diff --git a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj
index d5aad73169c..daa27573853 100644
--- a/dotnet/src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatMessageStore.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatMessageStore.cs
new file mode 100644
index 00000000000..fff7f56fa5c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosChatMessageStore.cs
@@ -0,0 +1,688 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Azure.Core;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides a Cosmos DB implementation of the abstract class.
+///
+[RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
+[RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
+public sealed class CosmosChatMessageStore : ChatMessageStore, IDisposable
+{
+ private readonly CosmosClient _cosmosClient;
+ private readonly Container _container;
+ private readonly bool _ownsClient;
+ private bool _disposed;
+
+ // Hierarchical partition key support
+ private readonly string? _tenantId;
+ private readonly string? _userId;
+ private readonly PartitionKey _partitionKey;
+ private readonly bool _useHierarchicalPartitioning;
+
+ ///
+ /// Cached JSON serializer options for .NET 9.0 compatibility.
+ ///
+ private static readonly JsonSerializerOptions s_defaultJsonOptions = CreateDefaultJsonOptions();
+
+ private static JsonSerializerOptions CreateDefaultJsonOptions()
+ {
+ var options = new JsonSerializerOptions();
+#if NET9_0_OR_GREATER
+ // Configure TypeInfoResolver for .NET 9.0 to enable JSON serialization
+ options.TypeInfoResolver = new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver();
+#endif
+ return options;
+ }
+
+ ///
+ /// Gets or sets the maximum number of messages to return in a single query batch.
+ /// Default is 100 for optimal performance.
+ ///
+ public int MaxItemCount { get; set; } = 100;
+
+ ///
+ /// Gets or sets the maximum number of items per transactional batch operation.
+ /// Default is 100, maximum allowed by Cosmos DB is 100.
+ ///
+ public int MaxBatchSize { get; set; } = 100;
+
+ ///
+ /// Gets or sets the maximum number of messages to retrieve from the store.
+ /// This helps prevent exceeding LLM context windows in long conversations.
+ /// Default is null (no limit). When set, only the most recent messages are returned.
+ ///
+ public int? MaxMessagesToRetrieve { get; set; }
+
+ ///
+ /// Gets or sets the Time-To-Live (TTL) in seconds for messages.
+ /// Default is 86400 seconds (24 hours). Set to null to disable TTL.
+ ///
+ public int? MessageTtlSeconds { get; set; } = 86400;
+
+ ///
+ /// Gets the conversation ID associated with this message store.
+ ///
+ public string ConversationId { get; init; }
+
+ ///
+ /// Gets the database ID associated with this message store.
+ ///
+ public string DatabaseId { get; init; }
+
+ ///
+ /// Gets the container ID associated with this message store.
+ ///
+ public string ContainerId { get; init; }
+
+ ///
+ /// Internal primary constructor used by all public constructors.
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The unique identifier for this conversation thread.
+ /// Whether this instance owns the CosmosClient and should dispose it.
+ /// Optional tenant identifier for hierarchical partitioning.
+ /// Optional user identifier for hierarchical partitioning.
+ internal CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId, bool ownsClient, string? tenantId = null, string? userId = null)
+ {
+ this._cosmosClient = Throw.IfNull(cosmosClient);
+ this._container = this._cosmosClient.GetContainer(Throw.IfNullOrWhitespace(databaseId), Throw.IfNullOrWhitespace(containerId));
+ this.ConversationId = Throw.IfNullOrWhitespace(conversationId);
+ this.DatabaseId = databaseId;
+ this.ContainerId = containerId;
+ this._ownsClient = ownsClient;
+
+ // Initialize partitioning mode
+ this._tenantId = tenantId;
+ this._userId = userId;
+ this._useHierarchicalPartitioning = tenantId != null && userId != null;
+
+ this._partitionKey = this._useHierarchicalPartitioning
+ ? new PartitionKeyBuilder()
+ .Add(tenantId!)
+ .Add(userId!)
+ .Add(conversationId)
+ .Build()
+ : new PartitionKey(conversationId);
+ }
+
+ ///
+ /// Initializes a new instance of the class using a connection string.
+ ///
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(string connectionString, string databaseId, string containerId)
+ : this(connectionString, databaseId, containerId, Guid.NewGuid().ToString("N"))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using a connection string.
+ ///
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The unique identifier for this conversation thread.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(string connectionString, string databaseId, string containerId, string conversationId)
+ : this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString)), databaseId, containerId, conversationId, ownsClient: true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using TokenCredential for authentication.
+ ///
+ /// The Cosmos DB account endpoint URI.
+ /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId)
+ : this(accountEndpoint, tokenCredential, databaseId, containerId, Guid.NewGuid().ToString("N"))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using a TokenCredential for authentication.
+ ///
+ /// The Cosmos DB account endpoint URI.
+ /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The unique identifier for this conversation thread.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId, string conversationId)
+ : this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential)), databaseId, containerId, conversationId, ownsClient: true)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using an existing .
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Thrown when is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId)
+ : this(cosmosClient, databaseId, containerId, Guid.NewGuid().ToString("N"))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using an existing .
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The unique identifier for this conversation thread.
+ /// Thrown when is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string conversationId)
+ : this(cosmosClient, databaseId, containerId, conversationId, ownsClient: false)
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using a connection string with hierarchical partition keys.
+ ///
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The tenant identifier for hierarchical partitioning.
+ /// The user identifier for hierarchical partitioning.
+ /// The session identifier for hierarchical partitioning.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(string connectionString, string databaseId, string containerId, string tenantId, string userId, string sessionId)
+ : this(new CosmosClient(Throw.IfNullOrWhitespace(connectionString)), databaseId, containerId, Throw.IfNullOrWhitespace(sessionId), ownsClient: true, Throw.IfNullOrWhitespace(tenantId), Throw.IfNullOrWhitespace(userId))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using a TokenCredential for authentication with hierarchical partition keys.
+ ///
+ /// The Cosmos DB account endpoint URI.
+ /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The tenant identifier for hierarchical partitioning.
+ /// The user identifier for hierarchical partitioning.
+ /// The session identifier for hierarchical partitioning.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId, string tenantId, string userId, string sessionId)
+ : this(new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential)), databaseId, containerId, Throw.IfNullOrWhitespace(sessionId), ownsClient: true, Throw.IfNullOrWhitespace(tenantId), Throw.IfNullOrWhitespace(userId))
+ {
+ }
+
+ ///
+ /// Initializes a new instance of the class using an existing with hierarchical partition keys.
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The tenant identifier for hierarchical partitioning.
+ /// The user identifier for hierarchical partitioning.
+ /// The session identifier for hierarchical partitioning.
+ /// Thrown when is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosChatMessageStore(CosmosClient cosmosClient, string databaseId, string containerId, string tenantId, string userId, string sessionId)
+ : this(cosmosClient, databaseId, containerId, Throw.IfNullOrWhitespace(sessionId), ownsClient: false, Throw.IfNullOrWhitespace(tenantId), Throw.IfNullOrWhitespace(userId))
+ {
+ }
+
+ ///
+ /// Creates a new instance of the class from previously serialized state.
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// A representing the serialized state of the message store.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Optional settings for customizing the JSON deserialization process.
+ /// A new instance of initialized from the serialized state.
+ /// Thrown when is null.
+ /// Thrown when the serialized state cannot be deserialized.
+ public static CosmosChatMessageStore CreateFromSerializedState(CosmosClient cosmosClient, JsonElement serializedStoreState, string databaseId, string containerId, JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ Throw.IfNull(cosmosClient);
+ Throw.IfNullOrWhitespace(databaseId);
+ Throw.IfNullOrWhitespace(containerId);
+
+ if (serializedStoreState.ValueKind is not JsonValueKind.Object)
+ {
+ throw new ArgumentException("Invalid serialized state", nameof(serializedStoreState));
+ }
+
+ var state = JsonSerializer.Deserialize(serializedStoreState, jsonSerializerOptions);
+ if (state?.ConversationIdentifier is not { } conversationId)
+ {
+ throw new ArgumentException("Invalid serialized state", nameof(serializedStoreState));
+ }
+
+ // Use the internal constructor with all parameters to ensure partition key logic is centralized
+ return state.UseHierarchicalPartitioning && state.TenantId != null && state.UserId != null
+ ? new CosmosChatMessageStore(cosmosClient, databaseId, containerId, conversationId, ownsClient: false, state.TenantId, state.UserId)
+ : new CosmosChatMessageStore(cosmosClient, databaseId, containerId, conversationId, ownsClient: false);
+ }
+
+ ///
+ public override async Task> GetMessagesAsync(CancellationToken cancellationToken = default)
+ {
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ // Fetch most recent messages in descending order when limit is set, then reverse to ascending
+ var orderDirection = this.MaxMessagesToRetrieve.HasValue ? "DESC" : "ASC";
+ var query = new QueryDefinition($"SELECT * FROM c WHERE c.conversationId = @conversationId AND c.type = @type ORDER BY c.timestamp {orderDirection}")
+ .WithParameter("@conversationId", this.ConversationId)
+ .WithParameter("@type", "ChatMessage");
+
+ var iterator = this._container.GetItemQueryIterator(query, requestOptions: new QueryRequestOptions
+ {
+ PartitionKey = this._partitionKey,
+ MaxItemCount = this.MaxItemCount // Configurable query performance
+ });
+
+ var messages = new List();
+
+ while (iterator.HasMoreResults)
+ {
+ var response = await iterator.ReadNextAsync(cancellationToken).ConfigureAwait(false);
+
+ foreach (var document in response)
+ {
+ if (this.MaxMessagesToRetrieve.HasValue && messages.Count >= this.MaxMessagesToRetrieve.Value)
+ {
+ break;
+ }
+
+ if (!string.IsNullOrEmpty(document.Message))
+ {
+ var message = JsonSerializer.Deserialize(document.Message, s_defaultJsonOptions);
+ if (message != null)
+ {
+ messages.Add(message);
+ }
+ }
+ }
+
+ if (this.MaxMessagesToRetrieve.HasValue && messages.Count >= this.MaxMessagesToRetrieve.Value)
+ {
+ break;
+ }
+ }
+
+ // If we fetched in descending order (most recent first), reverse to ascending order
+ if (this.MaxMessagesToRetrieve.HasValue)
+ {
+ messages.Reverse();
+ }
+
+ return messages;
+ }
+
+ ///
+ public override async Task AddMessagesAsync(IEnumerable messages, CancellationToken cancellationToken = default)
+ {
+ if (messages is null)
+ {
+ throw new ArgumentNullException(nameof(messages));
+ }
+
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ var messageList = messages as IReadOnlyCollection ?? messages.ToList();
+ if (messageList.Count == 0)
+ {
+ return;
+ }
+
+ // Use transactional batch for atomic operations
+ if (messageList.Count > 1)
+ {
+ await this.AddMessagesInBatchAsync(messageList, cancellationToken).ConfigureAwait(false);
+ }
+ else
+ {
+ await this.AddSingleMessageAsync(messageList.First(), cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Adds multiple messages using transactional batch operations for atomicity.
+ ///
+ private async Task AddMessagesInBatchAsync(IReadOnlyCollection messages, CancellationToken cancellationToken)
+ {
+ var currentTimestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
+
+ // Process messages in optimal batch sizes
+ for (int i = 0; i < messages.Count; i += this.MaxBatchSize)
+ {
+ var batchMessages = messages.Skip(i).Take(this.MaxBatchSize).ToList();
+ await this.ExecuteBatchOperationAsync(batchMessages, currentTimestamp, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Executes a single batch operation with enhanced error handling.
+ /// Cosmos SDK handles throttling (429) retries automatically.
+ ///
+ private async Task ExecuteBatchOperationAsync(List messages, long timestamp, CancellationToken cancellationToken)
+ {
+ // Create all documents upfront for validation and batch operation
+ var documents = new List(messages.Count);
+ foreach (var message in messages)
+ {
+ documents.Add(this.CreateMessageDocument(message, timestamp));
+ }
+
+ // Defensive check: Verify all messages share the same partition key values
+ // In hierarchical partitioning, this means same tenantId, userId, and sessionId
+ // In simple partitioning, this means same conversationId
+ if (documents.Count > 0)
+ {
+ if (this._useHierarchicalPartitioning)
+ {
+ // Verify all documents have matching hierarchical partition key components
+ var firstDoc = documents[0];
+ if (!documents.All(d => d.TenantId == firstDoc.TenantId && d.UserId == firstDoc.UserId && d.SessionId == firstDoc.SessionId))
+ {
+ throw new InvalidOperationException("All messages in a batch must share the same partition key values (tenantId, userId, sessionId).");
+ }
+ }
+ else
+ {
+ // Verify all documents have matching conversationId
+ var firstConversationId = documents[0].ConversationId;
+ if (!documents.All(d => d.ConversationId == firstConversationId))
+ {
+ throw new InvalidOperationException("All messages in a batch must share the same partition key value (conversationId).");
+ }
+ }
+ }
+
+ // All messages in this store share the same partition key by design
+ // Transactional batches require all items to share the same partition key
+ var batch = this._container.CreateTransactionalBatch(this._partitionKey);
+
+ foreach (var document in documents)
+ {
+ batch.CreateItem(document);
+ }
+
+ try
+ {
+ var response = await batch.ExecuteAsync(cancellationToken).ConfigureAwait(false);
+ if (!response.IsSuccessStatusCode)
+ {
+ throw new InvalidOperationException($"Batch operation failed with status: {response.StatusCode}. Details: {response.ErrorMessage}");
+ }
+ }
+ catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.RequestEntityTooLarge)
+ {
+ // If batch is too large, split into smaller batches
+ if (messages.Count == 1)
+ {
+ // Can't split further, use single operation
+ await this.AddSingleMessageAsync(messages[0], cancellationToken).ConfigureAwait(false);
+ return;
+ }
+
+ // Split the batch in half and retry
+ var midpoint = messages.Count / 2;
+ var firstHalf = messages.Take(midpoint).ToList();
+ var secondHalf = messages.Skip(midpoint).ToList();
+
+ await this.ExecuteBatchOperationAsync(firstHalf, timestamp, cancellationToken).ConfigureAwait(false);
+ await this.ExecuteBatchOperationAsync(secondHalf, timestamp, cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ ///
+ /// Adds a single message to the store.
+ ///
+ private async Task AddSingleMessageAsync(ChatMessage message, CancellationToken cancellationToken)
+ {
+ var document = this.CreateMessageDocument(message, DateTimeOffset.UtcNow.ToUnixTimeSeconds());
+
+ try
+ {
+ await this._container.CreateItemAsync(document, this._partitionKey, cancellationToken: cancellationToken).ConfigureAwait(false);
+ }
+ catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.RequestEntityTooLarge)
+ {
+ throw new InvalidOperationException(
+ "Message exceeds Cosmos DB's maximum item size limit of 2MB. " +
+ "Message ID: " + message.MessageId + ", Serialized size is too large. " +
+ "Consider reducing message content or splitting into smaller messages.",
+ ex);
+ }
+ }
+
+ ///
+ /// Creates a message document with enhanced metadata.
+ ///
+ private CosmosMessageDocument CreateMessageDocument(ChatMessage message, long timestamp)
+ {
+ return new CosmosMessageDocument
+ {
+ Id = Guid.NewGuid().ToString(),
+ ConversationId = this.ConversationId,
+ Timestamp = timestamp,
+ MessageId = message.MessageId,
+ Role = message.Role.Value,
+ Message = JsonSerializer.Serialize(message, s_defaultJsonOptions),
+ Type = "ChatMessage", // Type discriminator
+ Ttl = this.MessageTtlSeconds, // Configurable TTL
+ // Include hierarchical metadata when using hierarchical partitioning
+ TenantId = this._useHierarchicalPartitioning ? this._tenantId : null,
+ UserId = this._useHierarchicalPartitioning ? this._userId : null,
+ SessionId = this._useHierarchicalPartitioning ? this.ConversationId : null
+ };
+ }
+
+ ///
+ public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ var state = new StoreState
+ {
+ ConversationIdentifier = this.ConversationId,
+ TenantId = this._tenantId,
+ UserId = this._userId,
+ UseHierarchicalPartitioning = this._useHierarchicalPartitioning
+ };
+
+ var options = jsonSerializerOptions ?? s_defaultJsonOptions;
+ return JsonSerializer.SerializeToElement(state, options);
+ }
+
+ ///
+ /// Gets the count of messages in this conversation.
+ /// This is an additional utility method beyond the base contract.
+ ///
+ /// The cancellation token.
+ /// The number of messages in the conversation.
+ public async Task GetMessageCountAsync(CancellationToken cancellationToken = default)
+ {
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ // Efficient count query
+ var query = new QueryDefinition("SELECT VALUE COUNT(1) FROM c WHERE c.conversationId = @conversationId AND c.Type = @type")
+ .WithParameter("@conversationId", this.ConversationId)
+ .WithParameter("@type", "ChatMessage");
+
+ var iterator = this._container.GetItemQueryIterator(query, requestOptions: new QueryRequestOptions
+ {
+ PartitionKey = this._partitionKey
+ });
+
+ // COUNT queries always return a result
+ var response = await iterator.ReadNextAsync(cancellationToken).ConfigureAwait(false);
+ return response.FirstOrDefault();
+ }
+
+ ///
+ /// Deletes all messages in this conversation.
+ /// This is an additional utility method beyond the base contract.
+ ///
+ /// The cancellation token.
+ /// The number of messages deleted.
+ public async Task ClearMessagesAsync(CancellationToken cancellationToken = default)
+ {
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ // Batch delete for efficiency
+ var query = new QueryDefinition("SELECT VALUE c.id FROM c WHERE c.conversationId = @conversationId AND c.Type = @type")
+ .WithParameter("@conversationId", this.ConversationId)
+ .WithParameter("@type", "ChatMessage");
+
+ var iterator = this._container.GetItemQueryIterator(query, requestOptions: new QueryRequestOptions
+ {
+ PartitionKey = this._partitionKey,
+ MaxItemCount = this.MaxItemCount
+ });
+
+ var deletedCount = 0;
+
+ while (iterator.HasMoreResults)
+ {
+ var response = await iterator.ReadNextAsync(cancellationToken).ConfigureAwait(false);
+ var batch = this._container.CreateTransactionalBatch(this._partitionKey);
+ var batchItemCount = 0;
+
+ foreach (var itemId in response)
+ {
+ if (!string.IsNullOrEmpty(itemId))
+ {
+ batch.DeleteItem(itemId);
+ batchItemCount++;
+ deletedCount++;
+ }
+ }
+
+ if (batchItemCount > 0)
+ {
+ await batch.ExecuteAsync(cancellationToken).ConfigureAwait(false);
+ }
+ }
+
+ return deletedCount;
+ }
+
+ ///
+ public void Dispose()
+ {
+ if (!this._disposed)
+ {
+ if (this._ownsClient)
+ {
+ this._cosmosClient?.Dispose();
+ }
+ this._disposed = true;
+ }
+ }
+
+ private sealed class StoreState
+ {
+ public string ConversationIdentifier { get; set; } = string.Empty;
+ public string? TenantId { get; set; }
+ public string? UserId { get; set; }
+ public bool UseHierarchicalPartitioning { get; set; }
+ }
+
+ ///
+ /// Represents a document stored in Cosmos DB for chat messages.
+ ///
+ [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by Cosmos DB operations")]
+ private sealed class CosmosMessageDocument
+ {
+ [Newtonsoft.Json.JsonProperty("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [Newtonsoft.Json.JsonProperty("conversationId")]
+ public string ConversationId { get; set; } = string.Empty;
+
+ [Newtonsoft.Json.JsonProperty("timestamp")]
+ public long Timestamp { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("messageId")]
+ public string? MessageId { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("role")]
+ public string? Role { get; set; }
+
+ [Newtonsoft.Json.JsonProperty("message")]
+ public string Message { get; set; } = string.Empty;
+
+ [Newtonsoft.Json.JsonProperty("type")]
+ public string Type { get; set; } = string.Empty;
+
+ [Newtonsoft.Json.JsonProperty("ttl")]
+ public int? Ttl { get; set; }
+
+ ///
+ /// Tenant ID for hierarchical partitioning scenarios (optional).
+ ///
+ [Newtonsoft.Json.JsonProperty("tenantId")]
+ public string? TenantId { get; set; }
+
+ ///
+ /// User ID for hierarchical partitioning scenarios (optional).
+ ///
+ [Newtonsoft.Json.JsonProperty("userId")]
+ public string? UserId { get; set; }
+
+ ///
+ /// Session ID for hierarchical partitioning scenarios (same as ConversationId for compatibility).
+ ///
+ [Newtonsoft.Json.JsonProperty("sessionId")]
+ public string? SessionId { get; set; }
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosCheckpointStore.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosCheckpointStore.cs
new file mode 100644
index 00000000000..62987b1dfce
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosCheckpointStore.cs
@@ -0,0 +1,279 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Azure.Core;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Shared.Diagnostics;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+
+namespace Microsoft.Agents.AI.Workflows.Checkpointing;
+
+///
+/// Provides a Cosmos DB implementation of the abstract class.
+///
+/// The type of objects to store as checkpoint values.
+[RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+[RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+public class CosmosCheckpointStore : JsonCheckpointStore, IDisposable
+{
+ private readonly CosmosClient _cosmosClient;
+ private readonly Container _container;
+ private readonly bool _ownsClient;
+ private bool _disposed;
+
+ ///
+ /// Initializes a new instance of the class using a connection string.
+ ///
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosCheckpointStore(string connectionString, string databaseId, string containerId)
+ {
+ var cosmosClientOptions = new CosmosClientOptions();
+
+ this._cosmosClient = new CosmosClient(Throw.IfNullOrWhitespace(connectionString), cosmosClientOptions);
+ this._container = this._cosmosClient.GetContainer(Throw.IfNullOrWhitespace(databaseId), Throw.IfNullOrWhitespace(containerId));
+ this._ownsClient = true;
+ }
+
+ ///
+ /// Initializes a new instance of the class using a TokenCredential for authentication.
+ ///
+ /// The Cosmos DB account endpoint URI.
+ /// The TokenCredential to use for authentication (e.g., DefaultAzureCredential, ManagedIdentityCredential).
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosCheckpointStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId)
+ {
+ var cosmosClientOptions = new CosmosClientOptions
+ {
+ SerializerOptions = new CosmosSerializationOptions
+ {
+ PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
+ }
+ };
+
+ this._cosmosClient = new CosmosClient(Throw.IfNullOrWhitespace(accountEndpoint), Throw.IfNull(tokenCredential), cosmosClientOptions);
+ this._container = this._cosmosClient.GetContainer(Throw.IfNullOrWhitespace(databaseId), Throw.IfNullOrWhitespace(containerId));
+ this._ownsClient = true;
+ }
+
+ ///
+ /// Initializes a new instance of the class using an existing .
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// Thrown when is null.
+ /// Thrown when any string parameter is null or whitespace.
+ public CosmosCheckpointStore(CosmosClient cosmosClient, string databaseId, string containerId)
+ {
+ this._cosmosClient = Throw.IfNull(cosmosClient);
+
+ this._container = this._cosmosClient.GetContainer(Throw.IfNullOrWhitespace(databaseId), Throw.IfNullOrWhitespace(containerId));
+ this._ownsClient = false;
+ }
+
+ ///
+ /// Gets the identifier of the Cosmos DB database.
+ ///
+ public string DatabaseId => this._container.Database.Id;
+
+ ///
+ /// Gets the identifier of the Cosmos DB container.
+ ///
+ public string ContainerId => this._container.Id;
+
+ ///
+ public override async ValueTask CreateCheckpointAsync(string runId, JsonElement value, CheckpointInfo? parent = null)
+ {
+ if (string.IsNullOrWhiteSpace(runId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(runId));
+ }
+
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ var checkpointId = Guid.NewGuid().ToString("N");
+ var checkpointInfo = new CheckpointInfo(runId, checkpointId);
+
+ var document = new CosmosCheckpointDocument
+ {
+ Id = $"{runId}_{checkpointId}",
+ RunId = runId,
+ CheckpointId = checkpointId,
+ Value = JToken.Parse(value.GetRawText()),
+ ParentCheckpointId = parent?.CheckpointId,
+ Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
+ };
+
+ await this._container.CreateItemAsync(document, new PartitionKey(runId)).ConfigureAwait(false);
+ return checkpointInfo;
+ }
+
+ ///
+ public override async ValueTask RetrieveCheckpointAsync(string runId, CheckpointInfo key)
+ {
+ if (string.IsNullOrWhiteSpace(runId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(runId));
+ }
+
+ if (key is null)
+ {
+ throw new ArgumentNullException(nameof(key));
+ }
+
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ var id = $"{runId}_{key.CheckpointId}";
+
+ try
+ {
+ var response = await this._container.ReadItemAsync(id, new PartitionKey(runId)).ConfigureAwait(false);
+ using var document = JsonDocument.Parse(response.Resource.Value.ToString());
+ return document.RootElement.Clone();
+ }
+ catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
+ {
+ throw new InvalidOperationException($"Checkpoint with ID '{key.CheckpointId}' for run '{runId}' not found.");
+ }
+ }
+
+ ///
+ public override async ValueTask> RetrieveIndexAsync(string runId, CheckpointInfo? withParent = null)
+ {
+ if (string.IsNullOrWhiteSpace(runId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(runId));
+ }
+
+#pragma warning disable CA1513 // Use ObjectDisposedException.ThrowIf - not available on all target frameworks
+ if (this._disposed)
+ {
+ throw new ObjectDisposedException(this.GetType().FullName);
+ }
+#pragma warning restore CA1513
+
+ QueryDefinition query = withParent == null
+ ? new QueryDefinition("SELECT c.runId, c.checkpointId FROM c WHERE c.runId = @runId ORDER BY c.timestamp ASC")
+ .WithParameter("@runId", runId)
+ : new QueryDefinition("SELECT c.runId, c.checkpointId FROM c WHERE c.runId = @runId AND c.parentCheckpointId = @parentCheckpointId ORDER BY c.timestamp ASC")
+ .WithParameter("@runId", runId)
+ .WithParameter("@parentCheckpointId", withParent.CheckpointId);
+
+ var iterator = this._container.GetItemQueryIterator(query);
+ var checkpoints = new List();
+
+ while (iterator.HasMoreResults)
+ {
+ var response = await iterator.ReadNextAsync().ConfigureAwait(false);
+ checkpoints.AddRange(response.Select(r => new CheckpointInfo(r.RunId, r.CheckpointId)));
+ }
+
+ return checkpoints;
+ }
+
+ ///
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ///
+ /// Releases the unmanaged resources used by the and optionally releases the managed resources.
+ ///
+ /// true to release both managed and unmanaged resources; false to release only unmanaged resources.
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!this._disposed)
+ {
+ if (disposing && this._ownsClient)
+ {
+ this._cosmosClient?.Dispose();
+ }
+ this._disposed = true;
+ }
+ }
+
+ ///
+ /// Represents a checkpoint document stored in Cosmos DB.
+ ///
+ internal sealed class CosmosCheckpointDocument
+ {
+ [JsonProperty("id")]
+ public string Id { get; set; } = string.Empty;
+
+ [JsonProperty("runId")]
+ public string RunId { get; set; } = string.Empty;
+
+ [JsonProperty("checkpointId")]
+ public string CheckpointId { get; set; } = string.Empty;
+
+ [JsonProperty("value")]
+ public JToken Value { get; set; } = JValue.CreateNull();
+
+ [JsonProperty("parentCheckpointId")]
+ public string? ParentCheckpointId { get; set; }
+
+ [JsonProperty("timestamp")]
+ public long Timestamp { get; set; }
+ }
+
+ ///
+ /// Represents the result of a checkpoint query.
+ ///
+ [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by Cosmos DB query deserialization")]
+ private sealed class CheckpointQueryResult
+ {
+ public string RunId { get; set; } = string.Empty;
+ public string CheckpointId { get; set; } = string.Empty;
+ }
+}
+
+///
+/// Provides a non-generic Cosmos DB implementation of the abstract class.
+///
+[RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+[RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+public sealed class CosmosCheckpointStore : CosmosCheckpointStore
+{
+ ///
+ public CosmosCheckpointStore(string connectionString, string databaseId, string containerId)
+ : base(connectionString, databaseId, containerId)
+ {
+ }
+
+ ///
+ public CosmosCheckpointStore(string accountEndpoint, TokenCredential tokenCredential, string databaseId, string containerId)
+ : base(accountEndpoint, tokenCredential, databaseId, containerId)
+ {
+ }
+
+ ///
+ public CosmosCheckpointStore(CosmosClient cosmosClient, string databaseId, string containerId)
+ : base(cosmosClient, databaseId, containerId)
+ {
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs
new file mode 100644
index 00000000000..4e3b66fd543
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs
@@ -0,0 +1,95 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Azure.Identity;
+using Microsoft.Azure.Cosmos;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides extension methods for integrating Cosmos DB chat message storage with the Agent Framework.
+///
+public static class CosmosDBChatExtensions
+{
+ ///
+ /// Configures the agent to use Cosmos DB for message storage with connection string authentication.
+ ///
+ /// The chat client agent options to configure.
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The configured .
+ /// Thrown when is null.
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static ChatClientAgentOptions WithCosmosDBMessageStore(
+ this ChatClientAgentOptions options,
+ string connectionString,
+ string databaseId,
+ string containerId)
+ {
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(connectionString, databaseId, containerId);
+ return options;
+ }
+
+ ///
+ /// Configures the agent to use Cosmos DB for message storage with managed identity authentication.
+ ///
+ /// The chat client agent options to configure.
+ /// The Cosmos DB account endpoint URI.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The configured .
+ /// Thrown when is null.
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static ChatClientAgentOptions WithCosmosDBMessageStoreUsingManagedIdentity(
+ this ChatClientAgentOptions options,
+ string accountEndpoint,
+ string databaseId,
+ string containerId)
+ {
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId);
+ return options;
+ }
+
+ ///
+ /// Configures the agent to use Cosmos DB for message storage with an existing .
+ ///
+ /// The chat client agent options to configure.
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// The configured .
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosChatMessageStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static ChatClientAgentOptions WithCosmosDBMessageStore(
+ this ChatClientAgentOptions options,
+ CosmosClient cosmosClient,
+ string databaseId,
+ string containerId)
+ {
+ if (options is null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ options.ChatMessageStoreFactory = context => new CosmosChatMessageStore(cosmosClient, databaseId, containerId);
+ return options;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBWorkflowExtensions.cs b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBWorkflowExtensions.cs
new file mode 100644
index 00000000000..9d8bc52e68c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBWorkflowExtensions.cs
@@ -0,0 +1,218 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using Azure.Identity;
+using Microsoft.Agents.AI.Workflows.Checkpointing;
+using Microsoft.Azure.Cosmos;
+
+namespace Microsoft.Agents.AI.Workflows;
+
+///
+/// Provides extension methods for integrating Cosmos DB checkpoint storage with the Agent Framework.
+///
+public static class CosmosDBWorkflowExtensions
+{
+ ///
+ /// Creates a Cosmos DB checkpoint store using connection string authentication.
+ ///
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// A new instance of .
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static CosmosCheckpointStore CreateCheckpointStore(
+ string connectionString,
+ string databaseId,
+ string containerId)
+ {
+ if (string.IsNullOrWhiteSpace(connectionString))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(connectionString));
+ }
+
+ if (string.IsNullOrWhiteSpace(databaseId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(databaseId));
+ }
+
+ if (string.IsNullOrWhiteSpace(containerId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(containerId));
+ }
+
+ return new CosmosCheckpointStore(connectionString, databaseId, containerId);
+ }
+
+ ///
+ /// Creates a Cosmos DB checkpoint store using managed identity authentication.
+ ///
+ /// The Cosmos DB account endpoint URI.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// A new instance of .
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static CosmosCheckpointStore CreateCheckpointStoreUsingManagedIdentity(
+ string accountEndpoint,
+ string databaseId,
+ string containerId)
+ {
+ if (string.IsNullOrWhiteSpace(accountEndpoint))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(accountEndpoint));
+ }
+
+ if (string.IsNullOrWhiteSpace(databaseId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(databaseId));
+ }
+
+ if (string.IsNullOrWhiteSpace(containerId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(containerId));
+ }
+
+ return new CosmosCheckpointStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId);
+ }
+
+ ///
+ /// Creates a Cosmos DB checkpoint store using an existing .
+ ///
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// A new instance of .
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static CosmosCheckpointStore CreateCheckpointStore(
+ CosmosClient cosmosClient,
+ string databaseId,
+ string containerId)
+ {
+ if (cosmosClient is null)
+ {
+ throw new ArgumentNullException(nameof(cosmosClient));
+ }
+
+ if (string.IsNullOrWhiteSpace(databaseId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(databaseId));
+ }
+
+ if (string.IsNullOrWhiteSpace(containerId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(containerId));
+ }
+
+ return new CosmosCheckpointStore(cosmosClient, databaseId, containerId);
+ }
+
+ ///
+ /// Creates a generic Cosmos DB checkpoint store using connection string authentication.
+ ///
+ /// The type of objects to store as checkpoint values.
+ /// The Cosmos DB connection string.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// A new instance of .
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static CosmosCheckpointStore CreateCheckpointStore(
+ string connectionString,
+ string databaseId,
+ string containerId)
+ {
+ if (string.IsNullOrWhiteSpace(connectionString))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(connectionString));
+ }
+
+ if (string.IsNullOrWhiteSpace(databaseId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(databaseId));
+ }
+
+ if (string.IsNullOrWhiteSpace(containerId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(containerId));
+ }
+
+ return new CosmosCheckpointStore(connectionString, databaseId, containerId);
+ }
+
+ ///
+ /// Creates a generic Cosmos DB checkpoint store using managed identity authentication.
+ ///
+ /// The type of objects to store as checkpoint values.
+ /// The Cosmos DB account endpoint URI.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// A new instance of .
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static CosmosCheckpointStore CreateCheckpointStoreUsingManagedIdentity(
+ string accountEndpoint,
+ string databaseId,
+ string containerId)
+ {
+ if (string.IsNullOrWhiteSpace(accountEndpoint))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(accountEndpoint));
+ }
+
+ if (string.IsNullOrWhiteSpace(databaseId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(databaseId));
+ }
+
+ if (string.IsNullOrWhiteSpace(containerId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(containerId));
+ }
+
+ return new CosmosCheckpointStore(accountEndpoint, new DefaultAzureCredential(), databaseId, containerId);
+ }
+
+ ///
+ /// Creates a generic Cosmos DB checkpoint store using an existing .
+ ///
+ /// The type of objects to store as checkpoint values.
+ /// The instance to use for Cosmos DB operations.
+ /// The identifier of the Cosmos DB database.
+ /// The identifier of the Cosmos DB container.
+ /// A new instance of .
+ /// Thrown when any required parameter is null.
+ /// Thrown when any string parameter is null or whitespace.
+ [RequiresUnreferencedCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with trimming.")]
+ [RequiresDynamicCode("The CosmosCheckpointStore uses JSON serialization which is incompatible with NativeAOT.")]
+ public static CosmosCheckpointStore CreateCheckpointStore(
+ CosmosClient cosmosClient,
+ string databaseId,
+ string containerId)
+ {
+ if (cosmosClient is null)
+ {
+ throw new ArgumentNullException(nameof(cosmosClient));
+ }
+
+ if (string.IsNullOrWhiteSpace(databaseId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(databaseId));
+ }
+
+ if (string.IsNullOrWhiteSpace(containerId))
+ {
+ throw new ArgumentException("Cannot be null or whitespace", nameof(containerId));
+ }
+
+ return new CosmosCheckpointStore(cosmosClient, databaseId, containerId);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj
new file mode 100644
index 00000000000..7e13ec5998e
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj
@@ -0,0 +1,41 @@
+
+
+
+ $(TargetFrameworksCore)
+ Microsoft.Agents.AI
+ $(NoWarn);MEAI001
+ preview
+
+
+
+ true
+ true
+ true
+ true
+ true
+ true
+
+
+
+
+
+
+ Microsoft Agent Framework Cosmos DB NoSQL Integration
+ Provides Cosmos DB NoSQL implementations for Microsoft Agent Framework storage abstractions including ChatMessageStore and CheckpointStore.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/AgentBotElementYaml.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/AgentBotElementYaml.cs
new file mode 100644
index 00000000000..808bf76462f
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/AgentBotElementYaml.cs
@@ -0,0 +1,91 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Linq;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Bot.ObjectModel.Abstractions;
+using Microsoft.Bot.ObjectModel.Yaml;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Helper methods for creating from YAML.
+///
+internal static class AgentBotElementYaml
+{
+ ///
+ /// Convert the given YAML text to a model.
+ ///
+ /// YAML representation of the to use to create the prompt function.
+ /// Optional instance which provides environment variables to the template.
+ [RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
+ public static GptComponentMetadata FromYaml(string text, IConfiguration? configuration = null)
+ {
+ Throw.IfNullOrEmpty(text);
+
+ using var yamlReader = new StringReader(text);
+ BotElement rootElement = YamlSerializer.Deserialize(yamlReader) ?? throw new InvalidDataException("Text does not contain a valid agent definition.");
+
+ if (rootElement is not GptComponentMetadata promptAgent)
+ {
+ throw new InvalidDataException($"Unsupported root element: {rootElement.GetType().Name}. Expected an {nameof(GptComponentMetadata)}.");
+ }
+
+ var botDefinition = WrapPromptAgentWithBot(promptAgent, configuration);
+
+ return botDefinition.Descendants().OfType().First();
+ }
+
+ #region private
+ private sealed class AgentFeatureConfiguration : IFeatureConfiguration
+ {
+ public long GetInt64Value(string settingName, long defaultValue) => defaultValue;
+
+ public string GetStringValue(string settingName, string defaultValue) => defaultValue;
+
+ public bool IsEnvironmentFeatureEnabled(string featureName, bool defaultValue) => true;
+
+ public bool IsTenantFeatureEnabled(string featureName, bool defaultValue) => defaultValue;
+ }
+
+ public static BotDefinition WrapPromptAgentWithBot(this GptComponentMetadata element, IConfiguration? configuration = null)
+ {
+ var botBuilder =
+ new BotDefinition.Builder
+ {
+ Components =
+ {
+ new GptComponent.Builder
+ {
+ SchemaName = "default-schema",
+ Metadata = element.ToBuilder(),
+ }
+ }
+ };
+
+ if (configuration is not null)
+ {
+ foreach (var kvp in configuration.AsEnumerable().Where(kvp => kvp.Value is not null))
+ {
+ botBuilder.EnvironmentVariables.Add(new EnvironmentVariableDefinition.Builder()
+ {
+ SchemaName = kvp.Key,
+ Id = Guid.NewGuid(),
+ DisplayName = kvp.Key,
+ ValueComponent = new EnvironmentVariableValue.Builder()
+ {
+ Id = Guid.NewGuid(),
+ Value = kvp.Value!,
+ },
+ });
+ }
+ }
+
+ return botBuilder.Build();
+ }
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/AggregatorPromptAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/AggregatorPromptAgentFactory.cs
new file mode 100644
index 00000000000..49027367f1c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/AggregatorPromptAgentFactory.cs
@@ -0,0 +1,50 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides a which aggregates multiple agent factories.
+///
+public sealed class AggregatorPromptAgentFactory : PromptAgentFactory
+{
+ private readonly PromptAgentFactory[] _agentFactories;
+
+ /// Initializes the instance.
+ /// Ordered instances to aggregate.
+ ///
+ /// Where multiple instances are provided, the first factory that supports the will be used.
+ ///
+ public AggregatorPromptAgentFactory(params PromptAgentFactory[] agentFactories)
+ {
+ Throw.IfNullOrEmpty(agentFactories);
+
+ foreach (PromptAgentFactory agentFactory in agentFactories)
+ {
+ Throw.IfNull(agentFactory, nameof(agentFactories));
+ }
+
+ this._agentFactories = agentFactories;
+ }
+
+ ///
+ public override async Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ foreach (var agentFactory in this._agentFactories)
+ {
+ var agent = await agentFactory.TryCreateAsync(promptAgent, cancellationToken).ConfigureAwait(false);
+ if (agent is not null)
+ {
+ return agent;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/ChatClient/ChatClientPromptAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/ChatClient/ChatClientPromptAgentFactory.cs
new file mode 100644
index 00000000000..a7918de0510
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/ChatClient/ChatClientPromptAgentFactory.cs
@@ -0,0 +1,54 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
+using Microsoft.PowerFx;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Provides an which creates instances of .
+///
+public sealed class ChatClientPromptAgentFactory : PromptAgentFactory
+{
+ ///
+ /// Creates a new instance of the class.
+ ///
+ public ChatClientPromptAgentFactory(IChatClient chatClient, IList? functions = null, RecalcEngine? engine = null, IConfiguration? configuration = null, ILoggerFactory? loggerFactory = null) : base(engine, configuration)
+ {
+ Throw.IfNull(chatClient);
+
+ this._chatClient = chatClient;
+ this._functions = functions;
+ this._loggerFactory = loggerFactory;
+ }
+
+ ///
+ public override Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var options = new ChatClientAgentOptions()
+ {
+ Name = promptAgent.Name,
+ Description = promptAgent.Description,
+ ChatOptions = promptAgent.GetChatOptions(this.Engine, this._functions),
+ };
+
+ var agent = new ChatClientAgent(this._chatClient, options, this._loggerFactory);
+
+ return Task.FromResult(agent);
+ }
+
+ #region private
+ private readonly IChatClient _chatClient;
+ private readonly IList? _functions;
+ private readonly ILoggerFactory? _loggerFactory;
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/BoolExpressionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/BoolExpressionExtensions.cs
new file mode 100644
index 00000000000..9926e0e6bee
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/BoolExpressionExtensions.cs
@@ -0,0 +1,56 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.PowerFx;
+using Microsoft.PowerFx.Types;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class BoolExpressionExtensions
+{
+ ///
+ /// Evaluates the given using the provided .
+ ///
+ /// Expression to evaluate.
+ /// Recalc engine to use for evaluation.
+ /// The evaluated boolean value, or null if the expression is null or cannot be evaluated.
+ internal static bool? Eval(this BoolExpression? expression, RecalcEngine? engine)
+ {
+ if (expression is null)
+ {
+ return null;
+ }
+
+ if (expression.IsLiteral)
+ {
+ return expression.LiteralValue;
+ }
+
+ if (engine is null)
+ {
+ return null;
+ }
+
+ if (expression.IsExpression)
+ {
+ return engine.Eval(expression.ExpressionText!).AsBoolean();
+ }
+ else if (expression.IsVariableReference)
+ {
+ var formulaValue = engine.Eval(expression.VariableReference!.VariableName);
+ if (formulaValue is BooleanValue booleanValue)
+ {
+ return booleanValue.Value;
+ }
+
+ if (formulaValue is StringValue stringValue && bool.TryParse(stringValue.Value, out bool result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/CodeInterpreterToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/CodeInterpreterToolExtensions.cs
new file mode 100644
index 00000000000..e6f13d5f547
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/CodeInterpreterToolExtensions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class CodeInterpreterToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static HostedCodeInterpreterTool AsCodeInterpreterTool(this CodeInterpreterTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new HostedCodeInterpreterTool();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FileSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FileSearchToolExtensions.cs
new file mode 100644
index 00000000000..5e1cb1bb5fd
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FileSearchToolExtensions.cs
@@ -0,0 +1,28 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Linq;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class FileSearchToolExtensions
+{
+ ///
+ /// Create a from a .
+ ///
+ /// Instance of
+ internal static HostedFileSearchTool CreateFileSearchTool(this FileSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new HostedFileSearchTool()
+ {
+ MaximumResultCount = (int?)tool.MaximumResultCount?.LiteralValue,
+ Inputs = tool.VectorStoreIds?.LiteralValue.Select(id => (AIContent)new HostedVectorStoreContent(id)).ToList(),
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FunctionToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FunctionToolExtensions.cs
new file mode 100644
index 00000000000..2c54d7e7491
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/FunctionToolExtensions.cs
@@ -0,0 +1,61 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class FunctionToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ ///
+ /// If a matching function already exists in the provided list, it will be returned.
+ /// Otherwise, a new function declaration will be created.
+ ///
+ /// Instance of
+ /// Instance of
+ internal static AITool CreateOrGetAITool(this InvokeClientTaskAction tool, IList? functions)
+ {
+ Throw.IfNull(tool);
+ Throw.IfNull(tool.Name);
+
+ // use the tool from the provided list if it exists
+ if (functions is not null)
+ {
+ var function = functions.FirstOrDefault(f => tool.Matches(f));
+
+ if (function is not null)
+ {
+ return function;
+ }
+ }
+
+ return AIFunctionFactory.CreateDeclaration(
+ name: tool.Name,
+ description: tool.Description,
+ jsonSchema: tool.ClientActionInputSchema?.GetSchema() ?? s_defaultSchema);
+ }
+
+ ///
+ /// Checks if a matches an .
+ ///
+ /// Instance of
+ /// Instance of
+ internal static bool Matches(this InvokeClientTaskAction tool, AIFunction aiFunc)
+ {
+ Throw.IfNull(tool);
+ Throw.IfNull(aiFunc);
+
+ return tool.Name == aiFunc.Name;
+ }
+
+ private static readonly JsonElement s_defaultSchema = JsonDocument.Parse("{\"type\":\"object\",\"properties\":{},\"additionalProperties\":false}").RootElement;
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/IntExpressionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/IntExpressionExtensions.cs
new file mode 100644
index 00000000000..479d6ccea39
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/IntExpressionExtensions.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Globalization;
+using Microsoft.PowerFx;
+using Microsoft.PowerFx.Types;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class IntExpressionExtensions
+{
+ ///
+ /// Evaluates the given using the provided .
+ ///
+ /// Expression to evaluate.
+ /// Recalc engine to use for evaluation.
+ /// The evaluated integer value, or null if the expression is null or cannot be evaluated.
+ internal static long? Eval(this IntExpression? expression, RecalcEngine? engine)
+ {
+ if (expression is null)
+ {
+ return null;
+ }
+
+ if (expression.IsLiteral)
+ {
+ return expression.LiteralValue;
+ }
+
+ if (engine is null)
+ {
+ return null;
+ }
+
+ if (expression.IsExpression)
+ {
+ return (long)engine.Eval(expression.ExpressionText!).AsDouble();
+ }
+ else if (expression.IsVariableReference)
+ {
+ var formulaValue = engine.Eval(expression.VariableReference!.VariableName);
+ if (formulaValue is NumberValue numberValue)
+ {
+ return (long)numberValue.Value;
+ }
+
+ if (formulaValue is StringValue stringValue && int.TryParse(stringValue.Value, NumberStyles.Integer, CultureInfo.InvariantCulture, out int result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolApprovalModeExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolApprovalModeExtensions.cs
new file mode 100644
index 00000000000..ee5632368b8
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolApprovalModeExtensions.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class McpServerToolApprovalModeExtensions
+{
+ ///
+ /// Converts a to a .
+ ///
+ /// Instance of
+ internal static HostedMcpServerToolApprovalMode AsHostedMcpServerToolApprovalMode(this McpServerToolApprovalMode mode)
+ {
+ return mode switch
+ {
+ McpServerToolNeverRequireApprovalMode => HostedMcpServerToolApprovalMode.NeverRequire,
+ McpServerToolAlwaysRequireApprovalMode => HostedMcpServerToolApprovalMode.AlwaysRequire,
+ McpServerToolRequireSpecificApprovalMode specificMode =>
+ HostedMcpServerToolApprovalMode.RequireSpecific(
+ specificMode?.AlwaysRequireApprovalToolNames?.LiteralValue ?? [],
+ specificMode?.NeverRequireApprovalToolNames?.LiteralValue ?? []
+ ),
+ _ => HostedMcpServerToolApprovalMode.AlwaysRequire,
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolExtensions.cs
new file mode 100644
index 00000000000..763e4026250
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/McpServerToolExtensions.cs
@@ -0,0 +1,35 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class McpServerToolExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static HostedMcpServerTool CreateHostedMcpTool(this McpServerTool tool)
+ {
+ Throw.IfNull(tool);
+ Throw.IfNull(tool.ServerName?.LiteralValue);
+ Throw.IfNull(tool.Connection);
+
+ var connection = tool.Connection as AnonymousConnection ?? throw new ArgumentException("Only AnonymousConnection is supported for MCP Server Tool connections.", nameof(tool));
+ var serverUrl = connection.Endpoint?.LiteralValue;
+ Throw.IfNullOrEmpty(serverUrl, nameof(connection.Endpoint));
+
+ return new HostedMcpServerTool(tool.ServerName.LiteralValue, serverUrl)
+ {
+ ServerDescription = tool.ServerDescription?.LiteralValue,
+ AllowedTools = tool.AllowedTools?.LiteralValue,
+ ApprovalMode = tool.ApprovalMode?.AsHostedMcpServerToolApprovalMode(),
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/ModelOptionsExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/ModelOptionsExtensions.cs
new file mode 100644
index 00000000000..7ad4d26a6b8
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/ModelOptionsExtensions.cs
@@ -0,0 +1,66 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Linq;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class ModelOptionsExtensions
+{
+ ///
+ /// Converts the 'chatToolMode' property from a to a .
+ ///
+ /// Instance of
+ internal static ChatToolMode? AsChatToolMode(this ModelOptions modelOptions)
+ {
+ Throw.IfNull(modelOptions);
+
+ var mode = modelOptions.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("chatToolMode"))?.Value;
+ if (mode is null)
+ {
+ return null;
+ }
+
+ return mode switch
+ {
+ "auto" => ChatToolMode.Auto,
+ "none" => ChatToolMode.None,
+ "require_any" => ChatToolMode.RequireAny,
+ _ => ChatToolMode.RequireSpecific(mode),
+ };
+ }
+
+ ///
+ /// Retrieves the 'additional_properties' property from a .
+ ///
+ /// Instance of
+ /// List of properties which should not be included in additional properties.
+ internal static AdditionalPropertiesDictionary? GetAdditionalProperties(this ModelOptions modelOptions, string[] excludedProperties)
+ {
+ Throw.IfNull(modelOptions);
+
+ var options = modelOptions.ExtensionData;
+ if (options is null || options.Properties.Count == 0)
+ {
+ return null;
+ }
+
+ var additionalProperties = options.Properties
+ .Where(kvp => !excludedProperties.Contains(kvp.Key))
+ .ToDictionary(
+ kvp => kvp.Key,
+ kvp => kvp.Value?.ToObject());
+
+ if (additionalProperties is null || additionalProperties.Count == 0)
+ {
+ return null;
+ }
+
+ return new AdditionalPropertiesDictionary(additionalProperties);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/NumberExpressionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/NumberExpressionExtensions.cs
new file mode 100644
index 00000000000..cfa36185cce
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/NumberExpressionExtensions.cs
@@ -0,0 +1,57 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Globalization;
+using Microsoft.PowerFx;
+using Microsoft.PowerFx.Types;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class NumberExpressionExtensions
+{
+ ///
+ /// Evaluates the given using the provided .
+ ///
+ /// Expression to evaluate.
+ /// Recalc engine to use for evaluation.
+ /// The evaluated number value, or null if the expression is null or cannot be evaluated.
+ internal static double? Eval(this NumberExpression? expression, RecalcEngine? engine)
+ {
+ if (expression is null)
+ {
+ return null;
+ }
+
+ if (expression.IsLiteral)
+ {
+ return expression.LiteralValue;
+ }
+
+ if (engine is null)
+ {
+ return null;
+ }
+
+ if (expression.IsExpression)
+ {
+ return engine.Eval(expression.ExpressionText!).AsDouble();
+ }
+ else if (expression.IsVariableReference)
+ {
+ var formulaValue = engine.Eval(expression.VariableReference!.VariableName);
+ if (formulaValue is NumberValue numberValue)
+ {
+ return numberValue.Value;
+ }
+
+ if (formulaValue is StringValue stringValue && double.TryParse(stringValue.Value, NumberStyles.Float, CultureInfo.InvariantCulture, out double result))
+ {
+ return result;
+ }
+ }
+
+ return null;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs
new file mode 100644
index 00000000000..1597c0c54b9
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PromptAgentExtensions.cs
@@ -0,0 +1,114 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.AI;
+using Microsoft.PowerFx;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class PromptAgentExtensions
+{
+ ///
+ /// Retrieves the 'options' property from a as a instance.
+ ///
+ /// Instance of
+ /// Instance of
+ /// Instance of
+ public static ChatOptions? GetChatOptions(this GptComponentMetadata promptAgent, RecalcEngine? engine, IList? functions)
+ {
+ Throw.IfNull(promptAgent);
+
+ var outputSchema = promptAgent.OutputType;
+ var modelOptions = promptAgent.Model?.Options;
+
+ var tools = promptAgent.GetAITools(functions);
+
+ if (modelOptions is null && tools is null)
+ {
+ return null;
+ }
+
+ return new ChatOptions()
+ {
+ Instructions = promptAgent.Instructions?.ToTemplateString(),
+ Temperature = (float?)modelOptions?.Temperature?.Eval(engine),
+ MaxOutputTokens = (int?)modelOptions?.MaxOutputTokens?.Eval(engine),
+ TopP = (float?)modelOptions?.TopP?.Eval(engine),
+ TopK = (int?)modelOptions?.TopK?.Eval(engine),
+ FrequencyPenalty = (float?)modelOptions?.FrequencyPenalty?.Eval(engine),
+ PresencePenalty = (float?)modelOptions?.PresencePenalty?.Eval(engine),
+ Seed = modelOptions?.Seed?.Eval(engine),
+ ResponseFormat = outputSchema?.AsChatResponseFormat(),
+ ModelId = promptAgent.Model?.ModelNameHint,
+ StopSequences = modelOptions?.StopSequences,
+ AllowMultipleToolCalls = modelOptions?.AllowMultipleToolCalls?.Eval(engine),
+ ToolMode = modelOptions?.AsChatToolMode(),
+ Tools = tools,
+ AdditionalProperties = modelOptions?.GetAdditionalProperties(s_chatOptionProperties),
+ };
+ }
+
+ ///
+ /// Retrieves the 'tools' property from a .
+ ///
+ /// Instance of
+ /// Instance of
+ internal static List? GetAITools(this GptComponentMetadata promptAgent, IList? functions)
+ {
+ return promptAgent.Tools.Select(tool =>
+ {
+ return tool switch
+ {
+ CodeInterpreterTool => ((CodeInterpreterTool)tool).AsCodeInterpreterTool(),
+ InvokeClientTaskAction => ((InvokeClientTaskAction)tool).CreateOrGetAITool(functions),
+ McpServerTool => ((McpServerTool)tool).CreateHostedMcpTool(),
+ FileSearchTool => ((FileSearchTool)tool).CreateFileSearchTool(),
+ WebSearchTool => ((WebSearchTool)tool).CreateWebSearchTool(),
+ _ => throw new NotSupportedException($"Unable to create tool definition because of unsupported tool type: {tool.Kind}, supported tool types are: {string.Join(",", s_validToolKinds)}"),
+ };
+ }).ToList() ?? [];
+ }
+
+ #region private
+ private const string CodeInterpreterKind = "codeInterpreter";
+ private const string FileSearchKind = "fileSearch";
+ private const string FunctionKind = "function";
+ private const string WebSearchKind = "webSearch";
+ private const string McpKind = "mcp";
+
+ private static readonly string[] s_validToolKinds =
+ [
+ CodeInterpreterKind,
+ FileSearchKind,
+ FunctionKind,
+ WebSearchKind,
+ McpKind
+ ];
+
+ private static readonly string[] s_chatOptionProperties =
+ [
+ "allowMultipleToolCalls",
+ "conversationId",
+ "chatToolMode",
+ "frequencyPenalty",
+ "additionalInstructions",
+ "maxOutputTokens",
+ "modelId",
+ "presencePenalty",
+ "responseFormat",
+ "seed",
+ "stopSequences",
+ "temperature",
+ "topK",
+ "topP",
+ "toolMode",
+ "tools",
+ ];
+
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PropertyInfoExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PropertyInfoExtensions.cs
new file mode 100644
index 00000000000..a62fddec88c
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/PropertyInfoExtensions.cs
@@ -0,0 +1,96 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class PropertyInfoExtensions
+{
+ ///
+ /// Creates a of and
+ /// from an of and .
+ ///
+ /// A read-only dictionary of property names and their corresponding objects.
+ public static Dictionary AsObjectDictionary(this IReadOnlyDictionary properties)
+ {
+ var result = new Dictionary();
+
+ foreach (var property in properties)
+ {
+ result[property.Key] = BuildPropertySchema(property.Value);
+ }
+
+ return result;
+ }
+
+ #region private
+ private static Dictionary BuildPropertySchema(PropertyInfo propertyInfo)
+ {
+ var propertySchema = new Dictionary();
+
+ // Map the DataType to JSON schema type and add type-specific properties
+ switch (propertyInfo.Type)
+ {
+ case StringDataType:
+ propertySchema["type"] = "string";
+ break;
+ case NumberDataType:
+ propertySchema["type"] = "number";
+ break;
+ case BooleanDataType:
+ propertySchema["type"] = "boolean";
+ break;
+ case DateTimeDataType:
+ propertySchema["type"] = "string";
+ propertySchema["format"] = "date-time";
+ break;
+ case DateDataType:
+ propertySchema["type"] = "string";
+ propertySchema["format"] = "date";
+ break;
+ case TimeDataType:
+ propertySchema["type"] = "string";
+ propertySchema["format"] = "time";
+ break;
+ case RecordDataType nestedRecordType:
+#pragma warning disable IL2026, IL3050
+ // For nested records, recursively build the schema
+ var nestedSchema = nestedRecordType.GetSchema();
+ var nestedJson = JsonSerializer.Serialize(nestedSchema, ElementSerializer.CreateOptions());
+ var nestedDict = JsonSerializer.Deserialize>(nestedJson, ElementSerializer.CreateOptions());
+#pragma warning restore IL2026, IL3050
+ if (nestedDict != null)
+ {
+ return nestedDict;
+ }
+ propertySchema["type"] = "object";
+ break;
+ case TableDataType tableType:
+ propertySchema["type"] = "array";
+ // TableDataType has Properties like RecordDataType
+ propertySchema["items"] = new Dictionary
+ {
+ ["type"] = "object",
+ ["properties"] = AsObjectDictionary(tableType.Properties),
+ ["additionalProperties"] = false
+ };
+ break;
+ default:
+ propertySchema["type"] = "string";
+ break;
+ }
+
+ // Add description if available
+ if (!string.IsNullOrEmpty(propertyInfo.Description))
+ {
+ propertySchema["description"] = propertyInfo.Description;
+ }
+
+ return propertySchema;
+ }
+ #endregion
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataTypeExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataTypeExtensions.cs
new file mode 100644
index 00000000000..b5c5793cab7
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataTypeExtensions.cs
@@ -0,0 +1,77 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class RecordDataTypeExtensions
+{
+ ///
+ /// Creates a from a .
+ ///
+ /// Instance of
+ internal static ChatResponseFormat? AsChatResponseFormat(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ if (recordDataType.Properties.Count == 0)
+ {
+ return null;
+ }
+
+ // TODO: Consider adding schemaName and schemaDescription parameters to this method.
+ return ChatResponseFormat.ForJsonSchema(
+ schema: recordDataType.GetSchema(),
+ schemaName: recordDataType.GetSchemaName(),
+ schemaDescription: recordDataType.GetSchemaDescription());
+ }
+
+ ///
+ /// Converts a to a .
+ ///
+ /// Instance of
+#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
+ public static JsonElement GetSchema(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ var schemaObject = new Dictionary
+ {
+ ["type"] = "object",
+ ["properties"] = recordDataType.Properties.AsObjectDictionary(),
+ ["additionalProperties"] = false
+ };
+
+ var json = JsonSerializer.Serialize(schemaObject, ElementSerializer.CreateOptions());
+ return JsonSerializer.Deserialize(json);
+ }
+#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
+#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+
+ ///
+ /// Retrieves the 'schemaName' property from a .
+ ///
+ private static string? GetSchemaName(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ return recordDataType.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("schemaName"))?.Value;
+ }
+
+ ///
+ /// Retrieves the 'schemaDescription' property from a .
+ ///
+ private static string? GetSchemaDescription(this RecordDataType recordDataType)
+ {
+ Throw.IfNull(recordDataType);
+
+ return recordDataType.ExtensionData?.GetPropertyOrNull(InitializablePropertyPath.Create("schemaDescription"))?.Value;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataValueExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataValueExtensions.cs
new file mode 100644
index 00000000000..6351b7badbe
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/RecordDataValueExtensions.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class RecordDataValueExtensions
+{
+ ///
+ /// Retrieves a 'number' property from a
+ ///
+ /// Instance of
+ /// Path of the property to retrieve
+ public static decimal? GetNumber(this RecordDataValue recordData, string propertyPath)
+ {
+ Throw.IfNull(recordData);
+
+ var numberValue = recordData.GetPropertyOrNull(InitializablePropertyPath.Create(propertyPath));
+ return numberValue?.Value;
+ }
+
+ ///
+ /// Retrieves a nullable boolean value from the specified property path within the given record data.
+ ///
+ /// Instance of
+ /// Path of the property to retrieve
+ public static bool? GetBoolean(this RecordDataValue recordData, string propertyPath)
+ {
+ Throw.IfNull(recordData);
+
+ var booleanValue = recordData.GetPropertyOrNull(InitializablePropertyPath.Create(propertyPath));
+ return booleanValue?.Value;
+ }
+
+ ///
+ /// Converts a to a .
+ ///
+ /// Instance of
+ public static IReadOnlyDictionary ToDictionary(this RecordDataValue recordData)
+ {
+ Throw.IfNull(recordData);
+
+ return recordData.Properties.ToDictionary(
+ kvp => kvp.Key,
+ kvp => kvp.Value?.ToString() ?? string.Empty
+ );
+ }
+
+ ///
+ /// Retrieves the 'schema' property from a .
+ ///
+ /// Instance of
+#pragma warning disable IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+#pragma warning disable IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
+ public static JsonElement? GetSchema(this RecordDataValue recordData)
+ {
+ Throw.IfNull(recordData);
+
+ try
+ {
+ var schemaStr = recordData.GetPropertyOrNull(InitializablePropertyPath.Create("json_schema.schema"));
+ if (schemaStr?.Value is not null)
+ {
+ return JsonSerializer.Deserialize(schemaStr.Value);
+ }
+ }
+ catch (InvalidCastException)
+ {
+ // Ignore and try next
+ }
+
+ var responseFormRec = recordData.GetPropertyOrNull(InitializablePropertyPath.Create("json_schema.schema"));
+ if (responseFormRec is not null)
+ {
+ var json = JsonSerializer.Serialize(responseFormRec, ElementSerializer.CreateOptions());
+ return JsonSerializer.Deserialize(json);
+ }
+
+ return null;
+ }
+#pragma warning restore IL3050 // Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.
+#pragma warning restore IL2026 // Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code
+
+ internal static object? ToObject(this DataValue? value)
+ {
+ if (value is null)
+ {
+ return null;
+ }
+ return value switch
+ {
+ StringDataValue s => s.Value,
+ NumberDataValue n => n.Value,
+ BooleanDataValue b => b.Value,
+ TableDataValue t => t.Values.Select(v => v.ToObject()).ToList(),
+ RecordDataValue r => r.Properties.ToDictionary(kvp => kvp.Key, kvp => kvp.Value?.ToObject()),
+ _ => throw new NotSupportedException($"Unsupported DataValue type: {value.GetType().FullName}"),
+ };
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/StringExpressionExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/StringExpressionExtensions.cs
new file mode 100644
index 00000000000..40c1b7c9c8a
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/StringExpressionExtensions.cs
@@ -0,0 +1,48 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.PowerFx;
+using Microsoft.PowerFx.Types;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+public static class StringExpressionExtensions
+{
+ ///
+ /// Evaluates the given using the provided .
+ ///
+ /// Expression to evaluate.
+ /// Recalc engine to use for evaluation.
+ /// The evaluated string value, or null if the expression is null or cannot be evaluated.
+ public static string? Eval(this StringExpression? expression, RecalcEngine? engine)
+ {
+ if (expression is null)
+ {
+ return null;
+ }
+
+ if (expression.IsLiteral)
+ {
+ return expression.LiteralValue?.ToString();
+ }
+
+ if (engine is null)
+ {
+ return null;
+ }
+
+ if (expression.IsExpression)
+ {
+ return engine.Eval(expression.ExpressionText!).ToString();
+ }
+ else if (expression.IsVariableReference)
+ {
+ var stringValue = engine.Eval(expression.VariableReference!.VariableName) as StringValue;
+ return stringValue?.Value;
+ }
+
+ return null;
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/WebSearchToolExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/WebSearchToolExtensions.cs
new file mode 100644
index 00000000000..e6ee360308e
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/WebSearchToolExtensions.cs
@@ -0,0 +1,23 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Extensions.AI;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Bot.ObjectModel;
+
+///
+/// Extension methods for .
+///
+internal static class WebSearchToolExtensions
+{
+ ///
+ /// Create a from a .
+ ///
+ /// Instance of
+ internal static HostedWebSearchTool CreateWebSearchTool(this WebSearchTool tool)
+ {
+ Throw.IfNull(tool);
+
+ return new HostedWebSearchTool();
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/YamlAgentFactoryExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/YamlAgentFactoryExtensions.cs
new file mode 100644
index 00000000000..1cc24055d90
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Extensions/YamlAgentFactoryExtensions.cs
@@ -0,0 +1,33 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Diagnostics.CodeAnalysis;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Extension methods for to support YAML based agent definitions.
+///
+public static class YamlAgentFactoryExtensions
+{
+ ///
+ /// Create a from the given agent YAML.
+ ///
+ /// which will be used to create the agent.
+ /// Text string containing the YAML representation of an .
+ /// Optional cancellation token
+ [RequiresDynamicCode("Calls YamlDotNet.Serialization.DeserializerBuilder.DeserializerBuilder()")]
+ public static Task CreateFromYamlAsync(this PromptAgentFactory agentFactory, string agentYaml, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(agentFactory);
+ Throw.IfNullOrEmpty(agentYaml);
+
+ var agentDefinition = AgentBotElementYaml.FromYaml(agentYaml);
+
+ return agentFactory.CreateAsync(
+ agentDefinition,
+ cancellationToken);
+ }
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj b/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj
new file mode 100644
index 00000000000..306ba27e97b
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj
@@ -0,0 +1,45 @@
+
+
+
+ preview
+ $(NoWarn);MEAI001
+ false
+
+
+
+ true
+ true
+ true
+
+
+
+
+
+
+ Microsoft Agent Framework Declarative Agents
+ Provides Microsoft Agent Framework support for declarative agents.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Declarative/PromptAgentFactory.cs b/dotnet/src/Microsoft.Agents.AI.Declarative/PromptAgentFactory.cs
new file mode 100644
index 00000000000..cb277b06da4
--- /dev/null
+++ b/dotnet/src/Microsoft.Agents.AI.Declarative/PromptAgentFactory.cs
@@ -0,0 +1,63 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerFx;
+using Microsoft.Shared.Diagnostics;
+
+namespace Microsoft.Agents.AI;
+
+///
+/// Represents a factory for creating instances.
+///
+public abstract class PromptAgentFactory
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Optional , if none is provided a default instance will be created.
+ /// Optional configuration to be added as variables to the .
+ protected PromptAgentFactory(RecalcEngine? engine = null, IConfiguration? configuration = null)
+ {
+ this.Engine = engine ?? new RecalcEngine();
+
+ if (configuration is not null)
+ {
+ foreach (var kvp in configuration.AsEnumerable())
+ {
+ this.Engine.UpdateVariable(kvp.Key, kvp.Value ?? string.Empty);
+ }
+ }
+ }
+
+ ///
+ /// Gets the Power Fx recalculation engine used to evaluate expressions in agent definitions.
+ /// This engine is configured with variables from the provided during construction.
+ ///
+ protected RecalcEngine Engine { get; }
+
+ ///
+ /// Create a from the specified .
+ ///
+ /// Definition of the agent to create.
+ /// Optional cancellation token.
+ /// The created , if null the agent type is not supported.
+ public async Task CreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ Throw.IfNull(promptAgent);
+
+ var agent = await this.TryCreateAsync(promptAgent, cancellationToken).ConfigureAwait(false);
+ return agent ?? throw new NotSupportedException($"Agent type {promptAgent.Kind} is not supported.");
+ }
+
+ ///
+ /// Tries to create a from the specified .
+ ///
+ /// Definition of the agent to create.
+ /// Optional cancellation token.
+ /// The created , if null the agent type is not supported.
+ public abstract Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default);
+}
diff --git a/dotnet/src/Microsoft.Agents.AI.DevUI/DevUIMiddleware.cs b/dotnet/src/Microsoft.Agents.AI.DevUI/DevUIMiddleware.cs
index fc6dd512ecd..a2b210ca4d4 100644
--- a/dotnet/src/Microsoft.Agents.AI.DevUI/DevUIMiddleware.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DevUI/DevUIMiddleware.cs
@@ -4,6 +4,7 @@
using System.IO.Compression;
using System.Reflection;
using System.Security.Cryptography;
+using System.Text.RegularExpressions;
using Microsoft.AspNetCore.StaticFiles;
using Microsoft.Extensions.Primitives;
using Microsoft.Net.Http.Headers;
@@ -13,8 +14,11 @@ namespace Microsoft.Agents.AI.DevUI;
///
/// Handler that serves embedded DevUI resource files from the 'resources' directory.
///
-internal sealed class DevUIMiddleware
+internal sealed partial class DevUIMiddleware
{
+ [GeneratedRegex(@"[\r\n]+")]
+ private static partial Regex NewlineRegex();
+
private const string GZipEncodingValue = "gzip";
private static readonly StringValues s_gzipEncodingHeader = new(GZipEncodingValue);
private static readonly Assembly s_assembly = typeof(DevUIMiddleware).Assembly;
@@ -70,7 +74,7 @@ public async Task HandleRequestAsync(HttpContext context)
// This ensures relative URLs in the HTML work correctly
if (string.Equals(path, this._basePath, StringComparison.OrdinalIgnoreCase) && !path.EndsWith('/'))
{
- var redirectUrl = $"{path}/";
+ var redirectUrl = this._basePath + "/";
if (context.Request.QueryString.HasValue)
{
redirectUrl += context.Request.QueryString.Value;
@@ -78,7 +82,8 @@ public async Task HandleRequestAsync(HttpContext context)
context.Response.StatusCode = StatusCodes.Status301MovedPermanently;
context.Response.Headers.Location = redirectUrl;
- this._logger.LogDebug("Redirecting {OriginalPath} to {RedirectUrl}", path, redirectUrl);
+
+ this._logger.LogDebug("Redirecting {OriginalPath} to {RedirectUrl}", NewlineRegex().Replace(path, ""), NewlineRegex().Replace(redirectUrl, ""));
return;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.DevUI/Entities/MetaResponse.cs b/dotnet/src/Microsoft.Agents.AI.DevUI/Entities/MetaResponse.cs
index 6e1260cdc74..df717c6952e 100644
--- a/dotnet/src/Microsoft.Agents.AI.DevUI/Entities/MetaResponse.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DevUI/Entities/MetaResponse.cs
@@ -55,7 +55,7 @@ internal sealed record MetaResponse
/// - "openai_proxy": Whether the server can proxy requests to OpenAI
///
[JsonPropertyName("capabilities")]
- public Dictionary Capabilities { get; init; } = new();
+ public Dictionary Capabilities { get; init; } = [];
///
/// Gets a value indicating whether Bearer token authentication is required for API access.
diff --git a/dotnet/src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj b/dotnet/src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj
index 6c9c5bd9e35..30943cb5c42 100644
--- a/dotnet/src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
enable
Microsoft.Agents.AI.DevUI
@@ -28,10 +27,6 @@
-
-
-
-
Microsoft Agent Framework Developer UI
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs
index 1a117aff143..49c55be5dac 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/DurableAIAgent.cs
@@ -99,6 +99,8 @@ public override async Task RunAsync(
}
RunRequest request = new([.. messages], responseFormat, enableToolCalls, enableToolNames);
+ request.OrchestrationId = this._context.InstanceId;
+
try
{
return await this._context.Entities.CallEntityAsync(
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj b/dotnet/src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj
index 85f790d17b6..41284e10856 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
@@ -29,6 +28,7 @@
+
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/RunRequest.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/RunRequest.cs
index 60e5a7f83ce..0fc7ffc7b45 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/RunRequest.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/RunRequest.cs
@@ -36,6 +36,13 @@ public record RunRequest
[JsonInclude]
internal string CorrelationId { get; set; } = Guid.NewGuid().ToString("N");
+ ///
+ /// Gets or sets the ID of the orchestration that initiated this request (if any).
+ ///
+ [JsonInclude]
+ [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)]
+ internal string? OrchestrationId { get; set; }
+
///
/// Initializes a new instance of the class for a single message.
///
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentState.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentState.cs
index f0a12e4099a..35aef335444 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentState.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentState.cs
@@ -23,5 +23,5 @@ internal sealed class DurableAgentState
/// The version is specified in semver (i.e. "major.minor.patch") format.
///
[JsonPropertyName("schemaVersion")]
- public string SchemaVersion { get; init; } = "1.0.0";
+ public string SchemaVersion { get; init; } = "1.1.0";
}
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateJsonContext.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateJsonContext.cs
index 2684fcd3e15..4ad9a62835c 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateJsonContext.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateJsonContext.cs
@@ -37,6 +37,4 @@ namespace Microsoft.Agents.AI.DurableTask.State;
[JsonSerializable(typeof(TimeSpan))]
[JsonSerializable(typeof(DateTime))]
[JsonSerializable(typeof(DateTimeOffset))]
-internal sealed partial class DurableAgentStateJsonContext : JsonSerializerContext
-{
-}
+internal sealed partial class DurableAgentStateJsonContext : JsonSerializerContext;
diff --git a/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateRequest.cs b/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateRequest.cs
index cb8f3c137ce..6349b97c615 100644
--- a/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateRequest.cs
+++ b/dotnet/src/Microsoft.Agents.AI.DurableTask/State/DurableAgentStateRequest.cs
@@ -11,6 +11,12 @@ namespace Microsoft.Agents.AI.DurableTask.State;
///
internal sealed class DurableAgentStateRequest : DurableAgentStateEntry
{
+ ///
+ /// Gets the ID of the orchestration that initiated this request (if any).
+ ///
+ [JsonPropertyName("orchestrationId")]
+ public string? OrchestrationId { get; init; }
+
///
/// Gets the expected response type for this request (e.g. "json" or "text").
///
@@ -41,6 +47,7 @@ public static DurableAgentStateRequest FromRunRequest(RunRequest request)
return new DurableAgentStateRequest()
{
CorrelationId = request.CorrelationId,
+ OrchestrationId = request.OrchestrationId,
Messages = request.Messages.Select(DurableAgentStateMessage.FromChatMessage).ToList(),
CreatedAt = request.Messages.Min(m => m.CreatedAt) ?? DateTimeOffset.UtcNow,
ResponseType = request.ResponseFormat is ChatResponseFormatJson ? "json" : "text",
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj
index c23796ad56e..093c5e0cfb6 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
Microsoft.Agents.AI.Hosting.A2A.AspNetCore
preview
@@ -11,11 +10,12 @@
-
-
-
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj
index f300483f633..a0d66cc1d57 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
Microsoft.Agents.AI.Hosting.A2A
preview
Microsoft Agent Framework Hosting A2A
@@ -17,9 +16,6 @@
-
-
-
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj
index 869b931a20f..8f6ac4de240 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
Microsoft.Agents.AI.Hosting.AGUI.AspNetCore
preview
$(DefineConstants);ASPNETCORE
@@ -24,9 +23,6 @@
-
-
-
@@ -34,6 +30,10 @@
+
+
+
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs
index 291f042db56..10b1bc54ff6 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/BuiltInFunctionExecutor.cs
@@ -22,11 +22,8 @@ public async ValueTask ExecuteAsync(FunctionContext context)
ArgumentNullException.ThrowIfNull(context);
// Acquire the input binding feature (fail fast if missing rather than null-forgiving operator).
- IFunctionInputBindingFeature? functionInputBindingFeature = context.Features.Get();
- if (functionInputBindingFeature == null)
- {
+ IFunctionInputBindingFeature? functionInputBindingFeature = context.Features.Get() ??
throw new InvalidOperationException("Function input binding feature is not available on the current context.");
- }
FunctionInputBindingResult? inputBindingResults = await functionInputBindingFeature.BindFunctionInputAsync(context);
if (inputBindingResults is not { Values: { } values })
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj
index bb9ccc6ca0b..ce67c9621ec 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
$(NoWarn);CA2007
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs
index 5f50251f745..3158d87848c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/ChatCompletions/Converters/ChatClientAgentRunOptionsConverter.cs
@@ -10,7 +10,7 @@ namespace Microsoft.Agents.AI.Hosting.OpenAI.ChatCompletions.Converters;
internal static class ChatClientAgentRunOptionsConverter
{
- private static readonly JsonElement s_emptyJson = JsonDocument.Parse("{}").RootElement;
+ private static readonly JsonElement s_emptyJson = JsonElement.Parse("{}");
public static ChatClientAgentRunOptions BuildOptions(this CreateChatCompletion request)
{
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Conversations/InMemoryConversationStorage.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Conversations/InMemoryConversationStorage.cs
index 11b9dd9f0a5..d537f33eb9c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Conversations/InMemoryConversationStorage.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Conversations/InMemoryConversationStorage.cs
@@ -210,11 +210,10 @@ private sealed class ConversationState
#if NET9_0_OR_GREATER
private readonly OrderedDictionary _items = [];
private readonly object _lock = new();
- private Conversation _conversation;
public ConversationState(Conversation conversation)
{
- this._conversation = conversation;
+ this.Conversation = conversation;
}
public Conversation Conversation
@@ -223,16 +222,18 @@ public Conversation Conversation
{
lock (this._lock)
{
- return this._conversation;
+ return field;
}
}
+
+ private set;
}
public void UpdateConversation(Conversation conversation)
{
lock (this._lock)
{
- this._conversation = conversation;
+ this.Conversation = conversation;
}
}
@@ -274,11 +275,10 @@ public bool RemoveItem(string itemId)
#else
private readonly List _items = [];
private readonly object _lock = new();
- private Conversation _conversation;
public ConversationState(Conversation conversation)
{
- this._conversation = conversation;
+ this.Conversation = conversation;
}
public Conversation Conversation
@@ -287,16 +287,18 @@ public Conversation Conversation
{
lock (this._lock)
{
- return this._conversation;
+ return field;
}
}
+
+ private set;
}
public void UpdateConversation(Conversation conversation)
{
lock (this._lock)
{
- this._conversation = conversation;
+ this.Conversation = conversation;
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/IdGenerator.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/IdGenerator.cs
index bd35fa83083..5741e8d161c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/IdGenerator.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/IdGenerator.cs
@@ -146,6 +146,9 @@ private static string GetRandomString(int stringLength, Random? random)
const string Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
if (random is not null)
{
+#if NET10_0_OR_GREATER
+ return random.GetString(Chars, stringLength);
+#else
// Use deterministic random generation when seed is provided
return string.Create(stringLength, random, static (destination, random) =>
{
@@ -154,6 +157,7 @@ private static string GetRandomString(int stringLength, Random? random)
destination[i] = Chars[random.Next(Chars.Length)];
}
});
+#endif
}
// Use cryptographically secure random generation when no seed is provided
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj
index 707cc4fe68e..923f8e3eb61 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
$(NoWarn);OPENAI001;MEAI001
Microsoft.Agents.AI.Hosting.OpenAI
alpha
@@ -22,12 +21,13 @@
-
+
+
+
-
-
+
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs
index 32262d2e2c6..2476ce2fbd3 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Converters/ItemContentConverter.cs
@@ -140,10 +140,7 @@ DataContent audioData when audioData.HasTopLevelMediaType("audio") =>
_ => null
};
- if (result is not null)
- {
- result.RawRepresentation = content;
- }
+ result?.RawRepresentation = content;
return result;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/InputMessage.cs b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/InputMessage.cs
index 029be0752a1..c1ede61188c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/InputMessage.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting.OpenAI/Responses/Models/InputMessage.cs
@@ -40,7 +40,7 @@ public ChatMessage ToChatMessage()
{
if (this.Content.IsText)
{
- return new ChatMessage(this.Role, this.Content.Text!);
+ return new ChatMessage(this.Role, this.Content.Text);
}
else if (this.Content.IsContents)
{
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs
index e8a55b3baae..d3a437663aa 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting/HostedAgentBuilderExtensions.cs
@@ -52,13 +52,8 @@ public static IHostedAgentBuilder WithThreadStore(this IHostedAgentBuilder build
Throw.IfNull(key);
var keyString = key as string;
Throw.IfNullOrEmpty(keyString);
- var store = createAgentThreadStore(sp, keyString);
- if (store is null)
- {
+ return createAgentThreadStore(sp, keyString) ??
throw new InvalidOperationException($"The agent thread store factory did not return a valid {nameof(AgentThreadStore)} instance for key '{keyString}'.");
- }
-
- return store;
});
return builder;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/LocalAgentToolRegistry.cs b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/LocalAgentToolRegistry.cs
index ea8d8ad74e8..8c87803db39 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting/Local/LocalAgentToolRegistry.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting/Local/LocalAgentToolRegistry.cs
@@ -7,7 +7,7 @@ namespace Microsoft.Agents.AI.Hosting.Local;
internal sealed class LocalAgentToolRegistry
{
- private readonly Dictionary> _toolsByAgentName = new();
+ private readonly Dictionary> _toolsByAgentName = [];
public void AddTool(string agentName, AITool tool)
{
diff --git a/dotnet/src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj b/dotnet/src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj
index 86f709877d4..70c690bfdf5 100644
--- a/dotnet/src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
diff --git a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Client.cs b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Client.cs
index ad8120c4026..c3be7c6262d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Client.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Client.cs
@@ -71,7 +71,7 @@ public async Task> SearchAsync(string? applicationId, string
var response = await responseMessage.Content.ReadAsStringAsync().ConfigureAwait(false);
#endif
var searchResponseItems = JsonSerializer.Deserialize(response, Mem0SourceGenerationContext.Default.SearchResponseItemArray);
- return searchResponseItems?.Select(item => item.Memory) ?? Array.Empty();
+ return searchResponseItems?.Select(item => item.Memory) ?? [];
}
///
@@ -94,14 +94,14 @@ public async Task CreateMemoryAsync(string? applicationId, string? agentId, stri
AgentId = agentId,
RunId = threadId,
UserId = userId,
- Messages = new[]
- {
+ Messages =
+ [
new CreateMemoryMessage
{
Content = messageContent,
Role = messageRole.ToLowerInvariant()
}
- }
+ ]
};
#pragma warning restore CA1308
diff --git a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs
index d18ed2b4603..98bed507d5c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0Provider.cs
@@ -28,6 +28,7 @@ public sealed class Mem0Provider : AIContextProvider
private const string DefaultContextPrompt = "## Memories\nConsider the following memories when answering user questions:";
private readonly string _contextPrompt;
+ private readonly bool _enableSensitiveTelemetryData;
private readonly Mem0Client _client;
private readonly ILogger? _logger;
@@ -64,6 +65,7 @@ public Mem0Provider(HttpClient httpClient, Mem0ProviderScope storageScope, Mem0P
this._client = new Mem0Client(httpClient);
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
+ this._enableSensitiveTelemetryData = options?.EnableSensitiveTelemetryData ?? false;
this._storageScope = new Mem0ProviderScope(Throw.IfNull(storageScope));
this._searchScope = searchScope ?? storageScope;
@@ -114,6 +116,7 @@ public Mem0Provider(HttpClient httpClient, JsonElement serializedState, JsonSeri
this._client = new Mem0Client(httpClient);
this._contextPrompt = options?.ContextPrompt ?? DefaultContextPrompt;
+ this._enableSensitiveTelemetryData = options?.EnableSensitiveTelemetryData ?? false;
var jso = jsonSerializerOptions ?? Mem0JsonUtilities.DefaultOptions;
var state = serializedState.Deserialize(jso.GetTypeInfo(typeof(Mem0State))) as Mem0State;
@@ -158,17 +161,17 @@ public override async ValueTask InvokingAsync(InvokingContext context
this._searchScope.ApplicationId,
this._searchScope.AgentId,
this._searchScope.ThreadId,
- this._searchScope.UserId);
+ this.SanitizeLogData(this._searchScope.UserId));
if (outputMessageText is not null)
{
this._logger.LogTrace(
"Mem0AIContextProvider: Search Results\nInput:{Input}\nOutput:{MessageText}\nApplicationId: '{ApplicationId}', AgentId: '{AgentId}', ThreadId: '{ThreadId}', UserId: '{UserId}'.",
- queryText,
- outputMessageText,
+ this.SanitizeLogData(queryText),
+ this.SanitizeLogData(outputMessageText),
this._searchScope.ApplicationId,
this._searchScope.AgentId,
this._searchScope.ThreadId,
- this._searchScope.UserId);
+ this.SanitizeLogData(this._searchScope.UserId));
}
}
@@ -189,7 +192,7 @@ public override async ValueTask InvokingAsync(InvokingContext context
this._searchScope.ApplicationId,
this._searchScope.AgentId,
this._searchScope.ThreadId,
- this._searchScope.UserId);
+ this.SanitizeLogData(this._searchScope.UserId));
return new AIContext();
}
}
@@ -215,7 +218,7 @@ public override async ValueTask InvokedAsync(InvokedContext context, Cancellatio
this._storageScope.ApplicationId,
this._storageScope.AgentId,
this._storageScope.ThreadId,
- this._storageScope.UserId);
+ this.SanitizeLogData(this._storageScope.UserId));
}
}
@@ -282,4 +285,6 @@ public Mem0State(Mem0ProviderScope storageScope, Mem0ProviderScope searchScope)
public Mem0ProviderScope StorageScope { get; set; }
public Mem0ProviderScope SearchScope { get; set; }
}
+
+ private string? SanitizeLogData(string? data) => this._enableSensitiveTelemetryData ? data : "";
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0ProviderOptions.cs b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0ProviderOptions.cs
index 34b0392bec2..f2d3d89e166 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0ProviderOptions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Mem0/Mem0ProviderOptions.cs
@@ -12,4 +12,10 @@ public sealed class Mem0ProviderOptions
///
/// Defaults to "## Memories\nConsider the following memories when answering user questions:".
public string? ContextPrompt { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether sensitive data such as user ids and user messages may appear in logs.
+ ///
+ /// Defaults to .
+ public bool EnableSensitiveTelemetryData { get; set; }
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj b/dotnet/src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj
index e78e93c9555..19a5019843a 100644
--- a/dotnet/src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs
index 71f9b5436bb..fb464fdc398 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIAssistantClientExtensions.cs
@@ -28,19 +28,21 @@ public static class OpenAIAssistantClientExtensions
/// The client result containing the assistant.
/// Optional chat options.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the assistant.
public static ChatClientAgent GetAIAgent(
this AssistantClient assistantClient,
ClientResult assistantClientResult,
ChatOptions? chatOptions = null,
- Func? clientFactory = null)
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (assistantClientResult is null)
{
throw new ArgumentNullException(nameof(assistantClientResult));
}
- return assistantClient.GetAIAgent(assistantClientResult.Value, chatOptions, clientFactory);
+ return assistantClient.GetAIAgent(assistantClientResult.Value, chatOptions, clientFactory, services);
}
///
@@ -50,12 +52,14 @@ public static ChatClientAgent GetAIAgent(
/// The assistant metadata.
/// Optional chat options.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the assistant.
public static ChatClientAgent GetAIAgent(
this AssistantClient assistantClient,
Assistant assistantMetadata,
ChatOptions? chatOptions = null,
- Func? clientFactory = null)
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (assistantMetadata is null)
{
@@ -73,14 +77,19 @@ public static ChatClientAgent GetAIAgent(
chatClient = clientFactory(chatClient);
}
+ if (!string.IsNullOrWhiteSpace(assistantMetadata.Instructions) && chatOptions?.Instructions is null)
+ {
+ chatOptions ??= new ChatOptions();
+ chatOptions.Instructions = assistantMetadata.Instructions;
+ }
+
return new ChatClientAgent(chatClient, options: new()
{
Id = assistantMetadata.Id,
Name = assistantMetadata.Name,
Description = assistantMetadata.Description,
- Instructions = assistantMetadata.Instructions,
ChatOptions = chatOptions
- });
+ }, services: services);
}
///
@@ -90,6 +99,7 @@ public static ChatClientAgent GetAIAgent(
/// The ID of the server side agent to create a for.
/// Options that should apply to all runs of the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the assistant agent.
public static ChatClientAgent GetAIAgent(
@@ -97,6 +107,7 @@ public static ChatClientAgent GetAIAgent(
string agentId,
ChatOptions? chatOptions = null,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (assistantClient is null)
@@ -110,7 +121,7 @@ public static ChatClientAgent GetAIAgent(
}
var assistant = assistantClient.GetAssistant(agentId, cancellationToken);
- return assistantClient.GetAIAgent(assistant, chatOptions, clientFactory);
+ return assistantClient.GetAIAgent(assistant, chatOptions, clientFactory, services);
}
///
@@ -120,6 +131,7 @@ public static ChatClientAgent GetAIAgent(
/// The ID of the server side agent to create a for.
/// Options that should apply to all runs of the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the assistant agent.
public static async Task GetAIAgentAsync(
@@ -127,6 +139,7 @@ public static async Task GetAIAgentAsync(
string agentId,
ChatOptions? chatOptions = null,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (assistantClient is null)
@@ -140,7 +153,7 @@ public static async Task GetAIAgentAsync(
}
var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false);
- return assistantClient.GetAIAgent(assistantResponse, chatOptions, clientFactory);
+ return assistantClient.GetAIAgent(assistantResponse, chatOptions, clientFactory, services);
}
///
@@ -150,20 +163,22 @@ public static async Task GetAIAgentAsync(
/// The client result containing the assistant.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the assistant.
/// or is .
public static ChatClientAgent GetAIAgent(
this AssistantClient assistantClient,
ClientResult assistantClientResult,
ChatClientAgentOptions options,
- Func? clientFactory = null)
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (assistantClientResult is null)
{
throw new ArgumentNullException(nameof(assistantClientResult));
}
- return assistantClient.GetAIAgent(assistantClientResult.Value, options, clientFactory);
+ return assistantClient.GetAIAgent(assistantClientResult.Value, options, clientFactory, services);
}
///
@@ -173,13 +188,15 @@ public static ChatClientAgent GetAIAgent(
/// The assistant metadata.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// A instance that can be used to perform operations on the assistant.
/// or is .
public static ChatClientAgent GetAIAgent(
this AssistantClient assistantClient,
Assistant assistantMetadata,
ChatClientAgentOptions options,
- Func? clientFactory = null)
+ Func? clientFactory = null,
+ IServiceProvider? services = null)
{
if (assistantMetadata is null)
{
@@ -203,19 +220,24 @@ public static ChatClientAgent GetAIAgent(
chatClient = clientFactory(chatClient);
}
+ if (string.IsNullOrWhiteSpace(options.ChatOptions?.Instructions) && !string.IsNullOrWhiteSpace(assistantMetadata.Instructions))
+ {
+ options.ChatOptions ??= new ChatOptions();
+ options.ChatOptions.Instructions = assistantMetadata.Instructions;
+ }
+
var mergedOptions = new ChatClientAgentOptions()
{
Id = assistantMetadata.Id,
Name = options.Name ?? assistantMetadata.Name,
Description = options.Description ?? assistantMetadata.Description,
- Instructions = options.Instructions ?? assistantMetadata.Instructions,
ChatOptions = options.ChatOptions,
AIContextProviderFactory = options.AIContextProviderFactory,
ChatMessageStoreFactory = options.ChatMessageStoreFactory,
UseProvidedChatClientAsIs = options.UseProvidedChatClientAsIs
};
- return new ChatClientAgent(chatClient, mergedOptions);
+ return new ChatClientAgent(chatClient, mergedOptions, services: services);
}
///
@@ -225,6 +247,7 @@ public static ChatClientAgent GetAIAgent(
/// The ID of the server side agent to create a for.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the assistant agent.
/// or is .
@@ -234,6 +257,7 @@ public static ChatClientAgent GetAIAgent(
string agentId,
ChatClientAgentOptions options,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (assistantClient is null)
@@ -252,7 +276,7 @@ public static ChatClientAgent GetAIAgent(
}
var assistant = assistantClient.GetAssistant(agentId, cancellationToken);
- return assistantClient.GetAIAgent(assistant, options, clientFactory);
+ return assistantClient.GetAIAgent(assistant, options, clientFactory, services);
}
///
@@ -262,6 +286,7 @@ public static ChatClientAgent GetAIAgent(
/// The ID of the server side agent to create a for.
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// The to monitor for cancellation requests. The default is .
/// A instance that can be used to perform operations on the assistant agent.
/// or is .
@@ -271,6 +296,7 @@ public static async Task GetAIAgentAsync(
string agentId,
ChatClientAgentOptions options,
Func? clientFactory = null,
+ IServiceProvider? services = null,
CancellationToken cancellationToken = default)
{
if (assistantClient is null)
@@ -289,7 +315,7 @@ public static async Task GetAIAgentAsync(
}
var assistantResponse = await assistantClient.GetAssistantAsync(agentId, cancellationToken).ConfigureAwait(false);
- return assistantClient.GetAIAgent(assistantResponse, options, clientFactory);
+ return assistantClient.GetAIAgent(assistantResponse, options, clientFactory, services);
}
///
@@ -303,6 +329,7 @@ public static async Task GetAIAgentAsync(
/// Optional collection of AI tools that the agent can use during conversations.
/// Provides a way to customize the creation of the underlying used by the agent.
/// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// An instance backed by the OpenAI Assistant service.
/// Thrown when or is .
/// Thrown when is empty or whitespace.
@@ -314,21 +341,23 @@ public static ChatClientAgent CreateAIAgent(
string? description = null,
IList? tools = null,
Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null) =>
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null) =>
client.CreateAIAgent(
model,
new ChatClientAgentOptions()
{
Name = name,
Description = description,
- Instructions = instructions,
- ChatOptions = tools is null ? null : new ChatOptions()
+ ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
{
Tools = tools,
+ Instructions = instructions
}
},
clientFactory,
- loggerFactory);
+ loggerFactory,
+ services);
///
/// Creates an AI agent from an using the OpenAI Assistant API.
@@ -338,6 +367,7 @@ public static ChatClientAgent CreateAIAgent(
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
/// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// An instance backed by the OpenAI Assistant service.
/// Thrown when or or is .
/// Thrown when is empty or whitespace.
@@ -346,7 +376,8 @@ public static ChatClientAgent CreateAIAgent(
string model,
ChatClientAgentOptions options,
Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null)
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
{
Throw.IfNull(client);
Throw.IfNullOrEmpty(model);
@@ -356,7 +387,7 @@ public static ChatClientAgent CreateAIAgent(
{
Name = options.Name,
Description = options.Description,
- Instructions = options.Instructions,
+ Instructions = options.ChatOptions?.Instructions,
};
// Convert AITools to ToolDefinitions and ToolResources
@@ -387,7 +418,7 @@ public static ChatClientAgent CreateAIAgent(
options.ChatOptions ??= new ChatOptions();
options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools;
- return new ChatClientAgent(chatClient, agentOptions, loggerFactory);
+ return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
}
///
@@ -401,6 +432,8 @@ public static ChatClientAgent CreateAIAgent(
/// Optional collection of AI tools that the agent can use during conversations.
/// Provides a way to customize the creation of the underlying used by the agent.
/// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// The to monitor for cancellation requests. The default is .
/// An instance backed by the OpenAI Assistant service.
/// Thrown when or is .
/// Thrown when is empty or whitespace.
@@ -412,20 +445,24 @@ public static async Task CreateAIAgentAsync(
string? description = null,
IList? tools = null,
Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null) =>
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null,
+ CancellationToken cancellationToken = default) =>
await client.CreateAIAgentAsync(model,
new ChatClientAgentOptions()
{
Name = name,
Description = description,
- Instructions = instructions,
- ChatOptions = tools is null ? null : new ChatOptions()
+ ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
{
Tools = tools,
+ Instructions = instructions,
}
},
clientFactory,
- loggerFactory).ConfigureAwait(false);
+ loggerFactory,
+ services,
+ cancellationToken).ConfigureAwait(false);
///
/// Creates an AI agent from an using the OpenAI Assistant API.
@@ -435,6 +472,8 @@ await client.CreateAIAgentAsync(model,
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
/// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
+ /// The to monitor for cancellation requests. The default is .
/// An instance backed by the OpenAI Assistant service.
/// Thrown when or is .
/// Thrown when is empty or whitespace.
@@ -443,7 +482,9 @@ public static async Task CreateAIAgentAsync(
string model,
ChatClientAgentOptions options,
Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null)
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null,
+ CancellationToken cancellationToken = default)
{
Throw.IfNull(client);
Throw.IfNull(model);
@@ -453,7 +494,7 @@ public static async Task CreateAIAgentAsync(
{
Name = options.Name,
Description = options.Description,
- Instructions = options.Instructions,
+ Instructions = options.ChatOptions?.Instructions,
};
// Convert AITools to ToolDefinitions and ToolResources
@@ -468,7 +509,7 @@ public static async Task CreateAIAgentAsync(
}
// Create the assistant in the assistant service.
- var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions).ConfigureAwait(false);
+ var assistantCreateResult = await client.CreateAssistantAsync(model, assistantOptions, cancellationToken).ConfigureAwait(false);
var assistantId = assistantCreateResult.Value.Id;
// Build the local agent object.
@@ -483,7 +524,7 @@ public static async Task CreateAIAgentAsync(
options.ChatOptions ??= new ChatOptions();
options.ChatOptions!.Tools = toolDefinitionsAndResources.FunctionToolsAndOtherTools;
- return new ChatClientAgent(chatClient, agentOptions, loggerFactory);
+ return new ChatClientAgent(chatClient, agentOptions, loggerFactory, services);
}
private static (List? ToolDefinitions, ToolResources? ToolResources, List? FunctionToolsAndOtherTools) ConvertAIToolsToToolDefinitions(IList? tools)
@@ -500,7 +541,7 @@ private static (List? ToolDefinitions, ToolResources? ToolResour
{
case HostedCodeInterpreterTool codeTool:
- toolDefinitions ??= new();
+ toolDefinitions ??= [];
toolDefinitions.Add(new CodeInterpreterToolDefinition());
if (codeTool.Inputs is { Count: > 0 })
@@ -521,7 +562,7 @@ private static (List? ToolDefinitions, ToolResources? ToolResour
break;
case HostedFileSearchTool fileSearchTool:
- toolDefinitions ??= new();
+ toolDefinitions ??= [];
toolDefinitions.Add(new FileSearchToolDefinition
{
MaxResults = fileSearchTool.MaximumResultCount,
@@ -544,7 +585,7 @@ private static (List? ToolDefinitions, ToolResources? ToolResour
break;
default:
- functionToolsAndOtherTools ??= new();
+ functionToolsAndOtherTools ??= [];
functionToolsAndOtherTools.Add(tool);
break;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs
index 36114d009c7..b51679e42ef 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIChatClientExtensions.cs
@@ -47,9 +47,9 @@ public static ChatClientAgent CreateAIAgent(
{
Name = name,
Description = description,
- Instructions = instructions,
- ChatOptions = tools is null ? null : new ChatOptions()
+ ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
{
+ Instructions = instructions,
Tools = tools,
}
},
diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs
index c9f27432290..dd25d460477 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Extensions/OpenAIResponseClientExtensions.cs
@@ -30,6 +30,7 @@ public static class OpenAIResponseClientExtensions
/// Optional collection of AI tools that the agent can use during conversations.
/// Provides a way to customize the creation of the underlying used by the agent.
/// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// An instance backed by the OpenAI Response service.
/// Thrown when is .
public static ChatClientAgent CreateAIAgent(
@@ -39,7 +40,8 @@ public static ChatClientAgent CreateAIAgent(
string? description = null,
IList? tools = null,
Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null)
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
{
Throw.IfNull(client);
@@ -48,14 +50,15 @@ public static ChatClientAgent CreateAIAgent(
{
Name = name,
Description = description,
- Instructions = instructions,
- ChatOptions = tools is null ? null : new ChatOptions()
+ ChatOptions = tools is null && string.IsNullOrWhiteSpace(instructions) ? null : new ChatOptions()
{
+ Instructions = instructions,
Tools = tools,
}
},
clientFactory,
- loggerFactory);
+ loggerFactory,
+ services);
}
///
@@ -65,13 +68,15 @@ public static ChatClientAgent CreateAIAgent(
/// Full set of options to configure the agent.
/// Provides a way to customize the creation of the underlying used by the agent.
/// Optional logger factory for enabling logging within the agent.
+ /// An optional to use for resolving services required by the instances being invoked.
/// An instance backed by the OpenAI Response service.
/// Thrown when or is .
public static ChatClientAgent CreateAIAgent(
this OpenAIResponseClient client,
ChatClientAgentOptions options,
Func? clientFactory = null,
- ILoggerFactory? loggerFactory = null)
+ ILoggerFactory? loggerFactory = null,
+ IServiceProvider? services = null)
{
Throw.IfNull(client);
Throw.IfNull(options);
@@ -83,6 +88,6 @@ public static ChatClientAgent CreateAIAgent(
chatClient = clientFactory(chatClient);
}
- return new ChatClientAgent(chatClient, options, loggerFactory);
+ return new ChatClientAgent(chatClient, options, loggerFactory, services);
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj b/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj
index 3c79bb3071d..bfcf6e5263e 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
$(NoWarn);OPENAI001;
enable
diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs
index b529e1151b6..5870e2fdcc0 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIChatClientAgent.cs
@@ -32,7 +32,7 @@ public OpenAIChatClientAgent(
{
Name = name,
Description = description,
- Instructions = instructions,
+ ChatOptions = new ChatOptions() { Instructions = instructions },
}, loggerFactory)
{
}
diff --git a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs
index 8c5603fb05b..9d554e6a841 100644
--- a/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs
+++ b/dotnet/src/Microsoft.Agents.AI.OpenAI/OpenAIResponseClientAgent.cs
@@ -32,7 +32,7 @@ public OpenAIResponseClientAgent(
{
Name = name,
Description = description,
- Instructions = instructions,
+ ChatOptions = new ChatOptions() { Instructions = instructions },
}, loggerFactory)
{
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/BackgroundJobRunner.cs b/dotnet/src/Microsoft.Agents.AI.Purview/BackgroundJobRunner.cs
index d55c5a6a661..54690790152 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/BackgroundJobRunner.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/BackgroundJobRunner.cs
@@ -41,9 +41,7 @@ public BackgroundJobRunner(IChannelHandler channelHandler, IPurviewClient purvie
{
await this.RunJobAsync(job).ConfigureAwait(false);
}
- catch (Exception e) when (
- !(e is OperationCanceledException) &&
- !(e is SystemException))
+ catch (Exception e) when (e is not OperationCanceledException and not SystemException)
{
this._logger.LogError(e, "Error running background job {BackgroundJobError}.", e.Message);
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/ChannelHandler.cs b/dotnet/src/Microsoft.Agents.AI.Purview/ChannelHandler.cs
index ed3111fb3f3..746014b7006 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/ChannelHandler.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/ChannelHandler.cs
@@ -71,16 +71,9 @@ public void QueueJob(BackgroundJobBase job)
}
}
}
- catch (Exception e)
+ catch (Exception e) when (this._purviewSettings.IgnoreExceptions)
{
- if (this._purviewSettings.IgnoreExceptions)
- {
- this._logger.LogError(e, "Error queuing job: {ExceptionMessage}", e.Message);
- }
- else
- {
- throw;
- }
+ this._logger.LogError(e, "Error queuing job: {ExceptionMessage}", e.Message);
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj b/dotnet/src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj
index 20eca86359b..75c19ad7c99 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
alpha
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ContentBase.cs b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ContentBase.cs
index 9619d27fc8d..6a2a92226d7 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ContentBase.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ContentBase.cs
@@ -15,7 +15,7 @@ internal abstract class ContentBase : GraphDataTypeBase
/// Creates a new instance of the class.
///
/// The graph data type of the content.
- public ContentBase(string dataType) : base(dataType)
+ protected ContentBase(string dataType) : base(dataType)
{
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/GraphDataTypeBase.cs b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/GraphDataTypeBase.cs
index b4334fdb439..df54240662d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/GraphDataTypeBase.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/GraphDataTypeBase.cs
@@ -13,7 +13,7 @@ internal abstract class GraphDataTypeBase
/// Create a new instance of the class.
///
/// The data type of the graph object.
- public GraphDataTypeBase(string dataType)
+ protected GraphDataTypeBase(string dataType)
{
this.DataType = dataType;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ProcessContentMetadataBase.cs b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ProcessContentMetadataBase.cs
index 51f4936e828..a401288127b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ProcessContentMetadataBase.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Common/ProcessContentMetadataBase.cs
@@ -21,7 +21,7 @@ internal abstract class ProcessContentMetadataBase : GraphDataTypeBase
/// The unique identifier for the content.
/// Indicates if the content is truncated.
/// The name of the content.
- public ProcessContentMetadataBase(ContentBase content, string identifier, bool isTruncated, string name) : base(ProcessConversationMetadataDataType)
+ protected ProcessContentMetadataBase(ContentBase content, string identifier, bool isTruncated, string name) : base(ProcessConversationMetadataDataType)
{
this.Identifier = identifier;
this.IsTruncated = isTruncated;
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Jobs/BackgroundJobBase.cs b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Jobs/BackgroundJobBase.cs
index ab8cc8a5887..d3c93176284 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/Models/Jobs/BackgroundJobBase.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/Models/Jobs/BackgroundJobBase.cs
@@ -5,6 +5,4 @@ namespace Microsoft.Agents.AI.Purview.Models.Jobs;
///
/// Abstract base class for background jobs.
///
-internal abstract class BackgroundJobBase
-{
-}
+internal abstract class BackgroundJobBase;
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewExtensions.cs
index cdeb395d671..4095345d998 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/PurviewExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/PurviewExtensions.cs
@@ -112,11 +112,7 @@ public static Func PurviewAgentMiddleware(TokenCredential toke
/// The id of the owner of the message.
public static void SetUserId(this ChatMessage message, Guid userId)
{
- if (message.AdditionalProperties == null)
- {
- message.AdditionalProperties = new AdditionalPropertiesDictionary();
- }
-
+ message.AdditionalProperties ??= [];
message.AdditionalProperties[Constants.UserId] = userId.ToString();
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Purview/ScopedContentProcessor.cs b/dotnet/src/Microsoft.Agents.AI.Purview/ScopedContentProcessor.cs
index da9e61a22e0..d094ec2c312 100644
--- a/dotnet/src/Microsoft.Agents.AI.Purview/ScopedContentProcessor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Purview/ScopedContentProcessor.cs
@@ -101,7 +101,7 @@ private static bool TryGetUserIdFromPayload(IEnumerable messages, o
/// A list of process content requests.
private async Task> MapMessageToPCRequestsAsync(IEnumerable messages, string? threadId, Activity activity, PurviewSettings settings, string? userId, CancellationToken cancellationToken)
{
- List pcRequests = new();
+ List pcRequests = [];
TokenInfo? tokenInfo = null;
bool needUserId = userId == null && TryGetUserIdFromPayload(messages, out userId);
@@ -162,7 +162,7 @@ private async Task> MapMessageToPCRequestsAsync(IEnu
OperatingSystemVersion = "Unknown"
}
};
- ContentToProcess contentToProcess = new(new List { conversationmetadata }, activityMetadata, deviceMetadata, integratedAppMetadata, protectedAppMetadata);
+ ContentToProcess contentToProcess = new([conversationmetadata], activityMetadata, deviceMetadata, integratedAppMetadata, protectedAppMetadata);
if (userId == null &&
tokenInfo?.UserId != null)
@@ -279,7 +279,7 @@ private static (bool shouldProcess, List dlpActions, ExecutionMod
string locationType = locationSegments.Length > 0 ? locationSegments[locationSegments.Length - 1] : pcRequest.ContentToProcess.ProtectedAppMetadata.ApplicationLocation.Value;
string locationValue = pcRequest.ContentToProcess.ProtectedAppMetadata.ApplicationLocation.Value;
- List dlpActions = new();
+ List dlpActions = [];
bool shouldProcess = false;
ExecutionMode executionMode = ExecutionMode.EvaluateOffline;
@@ -325,7 +325,7 @@ private static ProtectionScopesRequest CreateProtectionScopesRequest(ProcessCont
return new ProtectionScopesRequest(userId, tenantId)
{
Activities = TranslateActivity(pcRequest.ContentToProcess.ActivityMetadata.Activity),
- Locations = new List { pcRequest.ContentToProcess.ProtectedAppMetadata.ApplicationLocation },
+ Locations = [pcRequest.ContentToProcess.ProtectedAppMetadata.ApplicationLocation],
DeviceMetadata = pcRequest.ContentToProcess.DeviceMetadata,
IntegratedAppMetadata = pcRequest.ContentToProcess.IntegratedAppMetadata,
CorrelationId = correlationId
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj
index c43c28aaf48..1370b6fdcad 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
$(NoWarn);MEAI001;OPENAI001
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/CodeGen/CodeTemplate.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/CodeGen/CodeTemplate.cs
index 87d9ab748bf..af201deb4f2 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/CodeGen/CodeTemplate.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/CodeGen/CodeTemplate.cs
@@ -13,9 +13,6 @@ namespace Microsoft.Agents.AI.Workflows.Declarative.CodeGen;
internal abstract class CodeTemplate
{
- private StringBuilder? _generationEnvironmentField;
- private CompilerErrorCollection? _errorsField;
- private List? _indentLengthsField;
private bool _endsWithNewline;
private string CurrentIndentField { get; set; } = string.Empty;
@@ -146,22 +143,19 @@ public StringBuilder GenerationEnvironment
{
get
{
- return this._generationEnvironmentField ??= new StringBuilder();
- }
- set
- {
- this._generationEnvironmentField = value;
+ return field ??= new StringBuilder();
}
+ set;
}
///
/// The error collection for the generation process
///
- public CompilerErrorCollection Errors => this._errorsField ??= [];
+ public CompilerErrorCollection Errors => field ??= [];
///
/// A list of the lengths of each indent that was added with PushIndent
///
- private List indentLengths => this._indentLengthsField ??= [];
+ private List IndentLengths { get => field ??= []; }
///
/// Gets the current indent we use when adding lines to the output
@@ -288,7 +282,7 @@ public void PushIndent(string indent)
throw new ArgumentNullException(nameof(indent));
}
this.CurrentIndentField += indent;
- this.indentLengths.Add(indent.Length);
+ this.IndentLengths.Add(indent.Length);
}
///
@@ -297,10 +291,10 @@ public void PushIndent(string indent)
public string PopIndent()
{
string returnValue = string.Empty;
- if (this.indentLengths.Count > 0)
+ if (this.IndentLengths.Count > 0)
{
- int indentLength = this.indentLengths[this.indentLengths.Count - 1];
- this.indentLengths.RemoveAt(this.indentLengths.Count - 1);
+ int indentLength = this.IndentLengths[this.IndentLengths.Count - 1];
+ this.IndentLengths.RemoveAt(this.IndentLengths.Count - 1);
if (indentLength > 0)
{
returnValue = this.CurrentIndentField.Substring(this.CurrentIndentField.Length - indentLength);
@@ -315,7 +309,7 @@ public string PopIndent()
///
public void ClearIndent()
{
- this.indentLengths.Clear();
+ this.IndentLengths.Clear();
this.CurrentIndentField = string.Empty;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs
index 279fec3e6de..1aa9a6ef716 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/ChatMessageExtensions.cs
@@ -20,7 +20,7 @@ public static TableValue ToTable(this IEnumerable messages) =>
public static IEnumerable? ToChatMessages(this DataValue? messages)
{
- if (messages is null || messages is BlankDataValue)
+ if (messages is null or BlankDataValue)
{
return null;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/DataValueExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/DataValueExtensions.cs
index a520593144e..9d4d18db732 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/DataValueExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Extensions/DataValueExtensions.cs
@@ -117,7 +117,7 @@ public static Type ToClrType(this DataType type) =>
public static IList? AsList(this DataValue? value)
{
- if (value is null || value is BlankDataValue)
+ if (value is null or BlankDataValue)
{
return null;
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs
index 704a555159a..2ad605803e6 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Interpreter/DeclarativeActionExecutor.cs
@@ -24,7 +24,6 @@ internal abstract class DeclarativeActionExecutor(TAction model, Workfl
internal abstract class DeclarativeActionExecutor : Executor, IResettableExecutor, IModeledAction
{
- private string? _parentId;
private readonly WorkflowFormulaState _state;
protected DeclarativeActionExecutor(DialogAction model, WorkflowFormulaState state)
@@ -42,7 +41,7 @@ protected DeclarativeActionExecutor(DialogAction model, WorkflowFormulaState sta
public DialogAction Model { get; }
- public string ParentId => this._parentId ??= this.Model.GetParentId() ?? WorkflowActionVisitor.Steps.Root();
+ public string ParentId { get => field ??= this.Model.GetParentId() ?? WorkflowActionVisitor.Steps.Root(); }
public RecalcEngine Engine => this._state.Engine;
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj
index 1f466aac4ef..0b3f41ec9b1 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
$(NoWarn);MEAI001;OPENAI001
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/AgentWorkflowBuilder.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/AgentWorkflowBuilder.cs
index 41f0d834f00..c5272e39eac 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/AgentWorkflowBuilder.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/AgentWorkflowBuilder.cs
@@ -139,7 +139,7 @@ private static Workflow BuildConcurrentCore(
aggregator ??= static lists => (from list in lists where list.Count > 0 select list.Last()).ToList();
Func> endFactory =
- (string _, string __) => new(new ConcurrentEndExecutor(agentExecutors.Length, aggregator));
+ (_, __) => new(new ConcurrentEndExecutor(agentExecutors.Length, aggregator));
ExecutorBinding end = endFactory.BindExecutor(ConcurrentEndExecutor.ExecutorId);
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs
index 56fb326338b..238734b598b 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/ChatProtocolExecutor.cs
@@ -26,7 +26,7 @@ public class ChatProtocolExecutorOptions
///
public abstract class ChatProtocolExecutor : StatefulExecutor>
{
- private readonly static Func> s_initFunction = () => [];
+ private static readonly Func> s_initFunction = () => [];
private readonly ChatRole? _stringMessageChatRole;
///
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandleExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandleExtensions.cs
index c7ac339a0c3..c9936ce6837 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandleExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/AsyncRunHandleExtensions.cs
@@ -8,7 +8,7 @@ namespace Microsoft.Agents.AI.Workflows.Execution;
internal static class AsyncRunHandleExtensions
{
- public async static ValueTask> WithCheckpointingAsync(this AsyncRunHandle runHandle, Func> prepareFunc)
+ public static async ValueTask> WithCheckpointingAsync(this AsyncRunHandle runHandle, Func> prepareFunc)
{
TRunType run = await prepareFunc().ConfigureAwait(false);
return new Checkpointed(run, runHandle);
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/NonThrowingChannelReaderAsyncEnumerable.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/NonThrowingChannelReaderAsyncEnumerable.cs
index aaae42f2f1e..306373f4b7f 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/NonThrowingChannelReaderAsyncEnumerable.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/NonThrowingChannelReaderAsyncEnumerable.cs
@@ -16,8 +16,7 @@ internal sealed class NonThrowingChannelReaderAsyncEnumerable(ChannelReader reader, CancellationToken cancellationToken) : IAsyncEnumerator
{
- private T? _current;
- public T Current => this._current ?? throw new InvalidOperationException("Enumeration not started.");
+ public T Current { get => field ?? throw new InvalidOperationException("Enumeration not started."); private set; }
public ValueTask DisposeAsync()
{
@@ -36,7 +35,7 @@ public async ValueTask MoveNextAsync()
bool hasData = await reader.WaitToReadAsync(cancellationToken).ConfigureAwait(false);
if (hasData)
{
- this._current = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
+ this.Current = await reader.ReadAsync(cancellationToken).ConfigureAwait(false);
return true;
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StateScope.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StateScope.cs
index e1c50ab1a3f..93960f0f9a3 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StateScope.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Execution/StateScope.cs
@@ -51,7 +51,7 @@ public bool ContainsKey(string key)
Throw.IfNullOrEmpty(key);
if (this._stateData.TryGetValue(key, out PortableValue? value))
{
- if (typeof(T) == typeof(PortableValue) && !value.TypeId.IsMatch(typeof(PortableValue)))
+ if (typeof(T) == typeof(PortableValue) && !value.TypeId.IsMatch())
{
// value is PortableValue, and we do not need to unwrap a PortableValue instance inside of it
// Unfortunately we need to cast through object here.
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs
index e0b53429f93..647dbcd852c 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Executor.cs
@@ -93,18 +93,17 @@ protected virtual ISet ConfigureYieldTypes()
return new HashSet();
}
- private MessageRouter? _router;
internal MessageRouter Router
{
get
{
- if (this._router is null)
+ if (field is null)
{
RouteBuilder routeBuilder = this.ConfigureRoutes(new RouteBuilder());
- this._router = routeBuilder.Build();
+ field = routeBuilder.Build();
}
- return this._router;
+ return field;
}
}
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatManager.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatManager.cs
index 9d3d55b33fd..d16a4b5b43d 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatManager.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatManager.cs
@@ -13,8 +13,6 @@ namespace Microsoft.Agents.AI.Workflows;
///
public abstract class GroupChatManager
{
- private int _maximumIterationCount = 40;
-
///
/// Initializes a new instance of the class.
///
@@ -34,9 +32,9 @@ protected GroupChatManager() { }
///
public int MaximumIterationCount
{
- get => this._maximumIterationCount;
- set => this._maximumIterationCount = Throw.IfLessThan(value, 1);
- }
+ get;
+ set => field = Throw.IfLessThan(value, 1);
+ } = 40;
///
/// Selects the next agent to participate in the group chat based on the provided chat history and team.
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatWorkflowBuilder.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatWorkflowBuilder.cs
index c02a609f75c..12b0f9c7072 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatWorkflowBuilder.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/GroupChatWorkflowBuilder.cs
@@ -53,7 +53,7 @@ public Workflow Build()
Dictionary agentMap = agents.ToDictionary(a => a, a => (ExecutorBinding)new AgentRunStreamingExecutor(a, includeInputInOutput: true));
Func> groupChatHostFactory =
- (string id, string runId) => new(new GroupChatHost(id, agents, agentMap, this._managerFactory));
+ (id, runId) => new(new GroupChatHost(id, agents, agentMap, this._managerFactory));
ExecutorBinding host = groupChatHostFactory.BindExecutor(nameof(GroupChatHost));
WorkflowBuilder builder = new(host);
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs
index 9c100ecbbf2..8c7149b0be5 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/InProc/InProcessRunner.cs
@@ -225,7 +225,7 @@ private async ValueTask RunSuperstepAsync(StepContext currentStep, CancellationT
// subworkflow's input queue. In order to actually process the message and align the supersteps correctly,
// we need to drive the superstep of the subworkflow here.
// TODO: Investigate if we can fully pull in the subworkflow execution into the WorkflowHostExecutor itself.
- List subworkflowTasks = new();
+ List subworkflowTasks = [];
foreach (ISuperStepRunner subworkflowRunner in this.RunContext.JoinedSubworkflowRunners)
{
subworkflowTasks.Add(subworkflowRunner.RunSuperStepAsync(cancellationToken).AsTask());
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj b/dotnet/src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj
index ff2e9dee649..7379d9a6ac6 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
preview
diff --git a/dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/RouteBuilderExtensions.cs b/dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/RouteBuilderExtensions.cs
index c0c6b8c8caa..f25f896db93 100644
--- a/dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/RouteBuilderExtensions.cs
+++ b/dotnet/src/Microsoft.Agents.AI.Workflows/Reflection/RouteBuilderExtensions.cs
@@ -11,7 +11,7 @@ namespace Microsoft.Agents.AI.Workflows.Reflection;
internal static class IMessageHandlerReflection
{
- private const string Nameof_HandleAsync = nameof(IMessageHandler
diff --git a/dotnet/src/Shared/IntegrationTests/AnthropicConfiguration.cs b/dotnet/src/Shared/IntegrationTests/AnthropicConfiguration.cs
new file mode 100644
index 00000000000..2230be95ed4
--- /dev/null
+++ b/dotnet/src/Shared/IntegrationTests/AnthropicConfiguration.cs
@@ -0,0 +1,17 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+namespace Shared.IntegrationTests;
+
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable.
+#pragma warning disable CA1812 // Internal class that is apparently never instantiated.
+
+internal sealed class AnthropicConfiguration
+{
+ public string? ServiceId { get; set; }
+
+ public string ChatModelId { get; set; }
+
+ public string ChatReasoningModelId { get; set; }
+
+ public string ApiKey { get; set; }
+}
diff --git a/dotnet/tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj b/dotnet/tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj
index 90347f3ce89..5ac895d63cf 100644
--- a/dotnet/tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj
+++ b/dotnet/tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj
@@ -1,7 +1,6 @@
- $(ProjectsTargetFrameworks)
false
@@ -11,7 +10,10 @@
-
+
+
+
+
diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj
new file mode 100644
index 00000000000..929eafe998c
--- /dev/null
+++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj
@@ -0,0 +1,20 @@
+
+
+
+ True
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionChatClientAgentRunStreamingTests.cs b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionChatClientAgentRunStreamingTests.cs
new file mode 100644
index 00000000000..992db5380b7
--- /dev/null
+++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionChatClientAgentRunStreamingTests.cs
@@ -0,0 +1,26 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+
+namespace AnthropicChatCompletion.IntegrationTests;
+
+public abstract class SkipAllChatClientRunStreaming(Func func) : ChatClientAgentRunStreamingTests(func)
+{
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync()
+ => base.RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
+ => base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
+}
+
+public class AnthropicBetaChatCompletionChatClientAgentReasoningRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: true, useBeta: true));
+
+public class AnthropicBetaChatCompletionChatClientAgentRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: false, useBeta: true));
+
+public class AnthropicChatCompletionChatClientAgentRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: false, useBeta: false));
+
+public class AnthropicChatCompletionChatClientAgentReasoningRunStreamingTests() : SkipAllChatClientRunStreaming(() => new(useReasoningChatModel: true, useBeta: false));
diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionChatClientAgentRunTests.cs b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionChatClientAgentRunTests.cs
new file mode 100644
index 00000000000..e2ce6e5d041
--- /dev/null
+++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionChatClientAgentRunTests.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+
+namespace AnthropicChatCompletion.IntegrationTests;
+
+public abstract class SkipAllChatClientAgentRun(Func func) : ChatClientAgentRunTests(func)
+{
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync()
+ => base.RunWithFunctionsInvokesFunctionsAndReturnsExpectedResultsAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithInstructionsAndNoMessageReturnsExpectedResultAsync()
+ => base.RunWithInstructionsAndNoMessageReturnsExpectedResultAsync();
+}
+
+public class AnthropicBetaChatCompletionChatClientAgentRunTests()
+ : SkipAllChatClientAgentRun(() => new(useReasoningChatModel: false, useBeta: true));
+
+public class AnthropicBetaChatCompletionChatClientAgentReasoningRunTests()
+ : SkipAllChatClientAgentRun(() => new(useReasoningChatModel: true, useBeta: true));
+
+public class AnthropicChatCompletionChatClientAgentRunTests()
+ : SkipAllChatClientAgentRun(() => new(useReasoningChatModel: false, useBeta: false));
+
+public class AnthropicChatCompletionChatClientAgentReasoningRunTests()
+ : SkipAllChatClientAgentRun(() => new(useReasoningChatModel: true, useBeta: false));
diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs
new file mode 100644
index 00000000000..72c0b14ae2e
--- /dev/null
+++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionFixture.cs
@@ -0,0 +1,104 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Linq;
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+using AgentConformance.IntegrationTests.Support;
+using Anthropic;
+using Anthropic.Models.Beta.Messages;
+using Anthropic.Models.Messages;
+using Microsoft.Agents.AI;
+using Microsoft.Extensions.AI;
+using Shared.IntegrationTests;
+
+namespace AnthropicChatCompletion.IntegrationTests;
+
+public class AnthropicChatCompletionFixture : IChatClientAgentFixture
+{
+ // All tests for Anthropic are intended to be ran locally as the CI pipeline for Anthropic is not setup.
+ internal const string SkipReason = "Integrations tests for local execution only";
+
+ private static readonly AnthropicConfiguration s_config = TestConfiguration.LoadSection();
+ private readonly bool _useReasoningModel;
+ private readonly bool _useBeta;
+
+ private ChatClientAgent _agent = null!;
+
+ public AnthropicChatCompletionFixture(bool useReasoningChatModel, bool useBeta)
+ {
+ this._useReasoningModel = useReasoningChatModel;
+ this._useBeta = useBeta;
+ }
+
+ public AIAgent Agent => this._agent;
+
+ public IChatClient ChatClient => this._agent.ChatClient;
+
+ public async Task> GetChatHistoryAsync(AgentThread thread)
+ {
+ var typedThread = (ChatClientAgentThread)thread;
+
+ return typedThread.MessageStore is null ? [] : (await typedThread.MessageStore.GetMessagesAsync()).ToList();
+ }
+
+ public Task CreateChatClientAgentAsync(
+ string name = "HelpfulAssistant",
+ string instructions = "You are a helpful assistant.",
+ IList? aiTools = null)
+ {
+ var anthropicClient = new AnthropicClient() { APIKey = s_config.ApiKey };
+
+ IChatClient? chatClient = this._useBeta
+ ? anthropicClient
+ .Beta
+ .AsIChatClient()
+ .AsBuilder()
+ .ConfigureOptions(options
+ => options.RawRepresentationFactory = _
+ => new Anthropic.Models.Beta.Messages.MessageCreateParams()
+ {
+ Model = options.ModelId ?? (this._useReasoningModel ? s_config.ChatReasoningModelId : s_config.ChatModelId),
+ MaxTokens = options.MaxOutputTokens ?? 4096,
+ Messages = [],
+ Thinking = this._useReasoningModel
+ ? new BetaThinkingConfigParam(new BetaThinkingConfigEnabled(2048))
+ : new BetaThinkingConfigParam(new BetaThinkingConfigDisabled())
+ }).Build()
+
+ : anthropicClient
+ .AsIChatClient()
+ .AsBuilder()
+ .ConfigureOptions(options
+ => options.RawRepresentationFactory = _
+ => new Anthropic.Models.Messages.MessageCreateParams()
+ {
+ Model = options.ModelId ?? (this._useReasoningModel ? s_config.ChatReasoningModelId : s_config.ChatModelId),
+ MaxTokens = options.MaxOutputTokens ?? 4096,
+ Messages = [],
+ Thinking = this._useReasoningModel
+ ? new ThinkingConfigParam(new ThinkingConfigEnabled(2048))
+ : new ThinkingConfigParam(new ThinkingConfigDisabled())
+ }).Build();
+
+ return Task.FromResult(new ChatClientAgent(chatClient, options: new()
+ {
+ Name = name,
+ ChatOptions = new() { Instructions = instructions, Tools = aiTools }
+ }));
+ }
+
+ public Task DeleteAgentAsync(ChatClientAgent agent) =>
+ // Chat Completion does not require/support deleting agents, so this is a no-op.
+ Task.CompletedTask;
+
+ public Task DeleteThreadAsync(AgentThread thread) =>
+ // Chat Completion does not require/support deleting threads, so this is a no-op.
+ Task.CompletedTask;
+
+ public async Task InitializeAsync() =>
+ this._agent = await this.CreateChatClientAgentAsync();
+
+ public Task DisposeAsync() =>
+ Task.CompletedTask;
+}
diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionRunStreamingTests.cs b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionRunStreamingTests.cs
new file mode 100644
index 00000000000..f1bbbe47e91
--- /dev/null
+++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionRunStreamingTests.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+
+namespace AnthropicChatCompletion.IntegrationTests;
+
+public abstract class SkipAllRunStreaming(Func func) : RunStreamingTests(func)
+{
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithChatMessageReturnsExpectedResultAsync() => base.RunWithChatMessageReturnsExpectedResultAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithNoMessageDoesNotFailAsync() => base.RunWithNoMessageDoesNotFailAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithChatMessagesReturnsExpectedResultAsync() => base.RunWithChatMessagesReturnsExpectedResultAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithStringReturnsExpectedResultAsync() => base.RunWithStringReturnsExpectedResultAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task ThreadMaintainsHistoryAsync() => base.ThreadMaintainsHistoryAsync();
+}
+
+public class AnthropicBetaChatCompletionRunStreamingTests()
+ : SkipAllRunStreaming(() => new(useReasoningChatModel: false, useBeta: true));
+
+public class AnthropicBetaChatCompletionReasoningRunStreamingTests()
+ : SkipAllRunStreaming(() => new(useReasoningChatModel: true, useBeta: true));
+
+public class AnthropicChatCompletionRunStreamingTests()
+ : SkipAllRunStreaming(() => new(useReasoningChatModel: false, useBeta: false));
+
+public class AnthropicChatCompletionReasoningRunStreamingTests()
+ : SkipAllRunStreaming(() => new(useReasoningChatModel: true, useBeta: false));
diff --git a/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionRunTests.cs b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionRunTests.cs
new file mode 100644
index 00000000000..aadbf747c24
--- /dev/null
+++ b/dotnet/tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletionRunTests.cs
@@ -0,0 +1,37 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Threading.Tasks;
+using AgentConformance.IntegrationTests;
+
+namespace AnthropicChatCompletion.IntegrationTests;
+
+public abstract class SkipAllRun(Func func) : RunTests(func)
+{
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithChatMessageReturnsExpectedResultAsync() => base.RunWithChatMessageReturnsExpectedResultAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithNoMessageDoesNotFailAsync() => base.RunWithNoMessageDoesNotFailAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithChatMessagesReturnsExpectedResultAsync() => base.RunWithChatMessagesReturnsExpectedResultAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task RunWithStringReturnsExpectedResultAsync() => base.RunWithStringReturnsExpectedResultAsync();
+
+ [Fact(Skip = AnthropicChatCompletionFixture.SkipReason)]
+ public override Task ThreadMaintainsHistoryAsync() => base.ThreadMaintainsHistoryAsync();
+}
+
+public class AnthropicBetaChatCompletionRunTests()
+ : SkipAllRun(() => new(useReasoningChatModel: false, useBeta: true));
+
+public class AnthropicBetaChatCompletionReasoningRunTests()
+ : SkipAllRun(() => new(useReasoningChatModel: true, useBeta: true));
+
+public class AnthropicChatCompletionRunTests()
+ : SkipAllRun(() => new(useReasoningChatModel: false, useBeta: false));
+
+public class AnthropicChatCompletionReasoningRunTests()
+ : SkipAllRun(() => new(useReasoningChatModel: true, useBeta: false));
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
index bb74c18ba61..f6267364187 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
+++ b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientCreateTests.cs
@@ -28,7 +28,7 @@ public class AIProjectClientCreateTests
public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string createMechanism)
{
// Arrange.
- const string AgentName = "IntegrationTestAgent";
+ string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("IntegrationTestAgent");
const string AgentDescription = "An agent created during integration tests";
const string AgentInstructions = "You are an integration test agent";
@@ -37,16 +37,20 @@ public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string create
{
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
model: s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- name: AgentName,
- description: AgentDescription)),
+ options: new ChatClientAgentOptions()
+ {
+ Name = AgentName,
+ Description = AgentDescription,
+ ChatOptions = new() { Instructions = AgentInstructions }
+ }),
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
model: s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- name: AgentName,
- description: AgentDescription)),
+ options: new ChatClientAgentOptions()
+ {
+ Name = AgentName,
+ Description = AgentDescription,
+ ChatOptions = new() { Instructions = AgentInstructions }
+ }),
"CreateWithFoundryOptionsAsync" => await this._client.CreateAIAgentAsync(
name: AgentName,
creationOptions: new AgentVersionCreationOptions(new PromptAgentDefinition(s_config.DeploymentName) { Instructions = AgentInstructions }) { Description = AgentDescription }),
@@ -86,7 +90,7 @@ public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string create
public async Task CreateAgent_CreatesAgentWithVectorStoresAsync(string createMechanism)
{
// Arrange.
- const string AgentName = "VectorStoreAgent";
+ string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("VectorStoreAgent");
const string AgentInstructions = """
You are a helpful agent that can help fetch data from files you know about.
Use the File Search Tool to look up codes for words.
@@ -159,7 +163,7 @@ You are a helpful agent that can help fetch data from files you know about.
public async Task CreateAgent_CreatesAgentWithCodeInterpreterAsync(string createMechanism)
{
// Arrange.
- const string AgentName = "CodeInterpreterAgent";
+ string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("CodeInterpreterAgent");
const string AgentInstructions = """
You are a helpful coding agent. A Python file is provided. Use the Code Interpreter Tool to run the file
and report the SECRET_NUMBER value it prints. Respond only with the number.
@@ -229,7 +233,7 @@ and report the SECRET_NUMBER value it prints. Respond only with the number.
public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string createMechanism)
{
// Arrange.
- const string AgentName = "WeatherAgent";
+ string AgentName = AIProjectClientFixture.GenerateUniqueAgentName("WeatherAgent");
const string AgentInstructions = "You are a helpful weather assistant. Always call the GetWeather function to answer questions about weather.";
static string GetWeather(string location) => $"The weather in {location} is sunny with a high of 23C.";
@@ -239,16 +243,18 @@ public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string create
{
"CreateWithChatClientAgentOptionsAsync" => await this._client.CreateAIAgentAsync(
model: s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- name: AgentName,
- instructions: AgentInstructions,
- tools: [weatherFunction])),
+ options: new ChatClientAgentOptions()
+ {
+ Name = AgentName,
+ ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
+ }),
"CreateWithChatClientAgentOptionsSync" => this._client.CreateAIAgent(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- name: AgentName,
- instructions: AgentInstructions,
- tools: [weatherFunction])),
+ options: new ChatClientAgentOptions()
+ {
+ Name = AgentName,
+ ChatOptions = new() { Instructions = AgentInstructions, Tools = [weatherFunction] }
+ }),
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
};
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
index f8d6d14b91a..e982c8081f8 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
+++ b/dotnet/tests/AzureAI.IntegrationTests/AIProjectClientFixture.cs
@@ -114,7 +114,7 @@ public async Task CreateChatClientAgentAsync(
return await this._client.CreateAIAgentAsync(GenerateUniqueAgentName(name), model: s_config.DeploymentName, instructions: instructions, tools: aiTools);
}
- private static string GenerateUniqueAgentName(string baseName) =>
+ public static string GenerateUniqueAgentName(string baseName) =>
$"{baseName}-{Guid.NewGuid().ToString("N").Substring(0, 8)}";
public Task DeleteAgentAsync(ChatClientAgent agent) =>
diff --git a/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj b/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
index 8da1981f51e..83f65051d2e 100644
--- a/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
+++ b/dotnet/tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
True
diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj
index 966ea64020e..40783424104 100644
--- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj
+++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
True
diff --git a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs
index e3e9969a439..a10cc11d79c 100644
--- a/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs
+++ b/dotnet/tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistentCreateTests.cs
@@ -1,6 +1,7 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
+using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;
using AgentConformance.IntegrationTests.Support;
@@ -34,16 +35,20 @@ public async Task CreateAgent_CreatesAgentWithCorrectMetadataAsync(string create
{
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- name: AgentName,
- description: AgentDescription)),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new() { Instructions = AgentInstructions },
+ Name = AgentName,
+ Description = AgentDescription
+ }),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- name: AgentName,
- description: AgentDescription)),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new() { Instructions = AgentInstructions },
+ Name = AgentName,
+ Description = AgentDescription
+ }),
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
instructions: AgentInstructions,
@@ -104,19 +109,32 @@ You are a helpful agent that can help fetch data from files you know about.
);
var vectorStoreMetadata = await this._persistentAgentsClient.VectorStores.CreateVectorStoreAsync([uploadedAgentFile.Id], name: "WordCodeLookup_VectorStore");
+ // Wait for vector store indexing to complete before using it
+ await this.WaitForVectorStoreReadyAsync(this._persistentAgentsClient, vectorStoreMetadata.Value.Id);
+
// Act.
var agent = createMechanism switch
{
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new()
+ {
+ Instructions = AgentInstructions,
+ Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]
+ }
+ }),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- tools: [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }])),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new()
+ {
+ Instructions = AgentInstructions,
+ Tools = [new HostedFileSearchTool() { Inputs = [new HostedVectorStoreContent(vectorStoreMetadata.Value.Id)] }]
+ }
+ }),
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
instructions: AgentInstructions,
@@ -179,15 +197,24 @@ and report the SECRET_NUMBER value it prints. Respond only with the number.
// Hosted tool path (tools supplied via ChatClientAgentOptions)
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new()
+ {
+ Instructions = AgentInstructions,
+ Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]
+ }
+ }),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- tools: [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }])),
- // Foundry (definitions + resources provided directly)
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new()
+ {
+ Instructions = AgentInstructions,
+ Tools = [new HostedCodeInterpreterTool() { Inputs = [new HostedFileContent(uploadedCodeFile.Id)] }]
+ }
+ }),
"CreateWithFoundryOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
instructions: AgentInstructions,
@@ -232,14 +259,24 @@ public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string create
{
"CreateWithChatClientAgentOptionsAsync" => await this._persistentAgentsClient.CreateAIAgentAsync(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- tools: [weatherFunction])),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new()
+ {
+ Instructions = AgentInstructions,
+ Tools = [weatherFunction]
+ }
+ }),
"CreateWithChatClientAgentOptionsSync" => this._persistentAgentsClient.CreateAIAgent(
s_config.DeploymentName,
- options: new ChatClientAgentOptions(
- instructions: AgentInstructions,
- tools: [weatherFunction])),
+ options: new ChatClientAgentOptions()
+ {
+ ChatOptions = new()
+ {
+ Instructions = AgentInstructions,
+ Tools = [weatherFunction]
+ }
+ }),
_ => throw new InvalidOperationException($"Unknown create mechanism: {createMechanism}")
};
@@ -259,4 +296,42 @@ public async Task CreateAgent_CreatesAgentWithAIFunctionToolsAsync(string create
await this._persistentAgentsClient.Administration.DeleteAgentAsync(agent.Id);
}
}
+
+ ///
+ /// Waits for a vector store to complete indexing by polling its status.
+ ///
+ /// The persistent agents client.
+ /// The ID of the vector store.
+ /// Maximum time to wait in seconds (default: 30).
+ /// A task that completes when the vector store is ready or throws on timeout/failure.
+ private async Task WaitForVectorStoreReadyAsync(
+ PersistentAgentsClient client,
+ string vectorStoreId,
+ int maxWaitSeconds = 30)
+ {
+ Stopwatch sw = Stopwatch.StartNew();
+ while (sw.Elapsed.TotalSeconds < maxWaitSeconds)
+ {
+ PersistentAgentsVectorStore vectorStore = await client.VectorStores.GetVectorStoreAsync(vectorStoreId);
+
+ if (vectorStore.Status == VectorStoreStatus.Completed)
+ {
+ if (vectorStore.FileCounts.Failed > 0)
+ {
+ throw new InvalidOperationException("Vector store indexing failed for some files");
+ }
+
+ return;
+ }
+
+ if (vectorStore.Status == VectorStoreStatus.Expired)
+ {
+ throw new InvalidOperationException("Vector store has expired");
+ }
+
+ await Task.Delay(1000);
+ }
+
+ throw new TimeoutException($"Vector store did not complete indexing within {maxWaitSeconds}s");
+ }
}
diff --git a/dotnet/tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj b/dotnet/tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj
index afbcc54f014..5f535eb7bdc 100644
--- a/dotnet/tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj
+++ b/dotnet/tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
True
true
diff --git a/dotnet/tests/Directory.Build.props b/dotnet/tests/Directory.Build.props
index 6c5a318e863..e6c285595e8 100644
--- a/dotnet/tests/Directory.Build.props
+++ b/dotnet/tests/Directory.Build.props
@@ -6,7 +6,7 @@
false
true
false
- net472;net9.0
+ net10.0;net472
b7762d10-e29b-4bb1-8b74-b6d69a667dd4
$(NoWarn);Moq1410;xUnit2023
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs
index 9399d99528b..f079fc5ed89 100644
--- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentTests.cs
@@ -367,6 +367,7 @@ public async Task RunStreamingAsync_AllowsNonUserRoleMessagesAsync()
// Act & Assert
await foreach (var _ in this._agent.RunStreamingAsync(inputMessages))
{
+ // Just iterate through to trigger the logic
}
}
@@ -396,15 +397,490 @@ public async Task RunAsync_WithHostedFileContent_ConvertsToFilePartAsync()
Assert.Equal("https://example.com/file.pdf", ((FilePart)message.Parts[1]).File.Uri?.ToString());
}
+ [Fact]
+ public async Task RunAsync_WithContinuationTokenAndMessages_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ var inputMessages = new List
+ {
+ new(ChatRole.User, "Test message")
+ };
+
+ var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") };
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => this._agent.RunAsync(inputMessages, null, options));
+ }
+
+ [Fact]
+ public async Task RunAsync_WithContinuationToken_CallsGetTaskAsyncAsync()
+ {
+ // Arrange
+ this._handler.ResponseToReturn = new AgentTask
+ {
+ Id = "task-123",
+ ContextId = "context-123"
+ };
+
+ var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") };
+
+ // Act
+ await this._agent.RunAsync([], options: options);
+
+ // Assert
+ Assert.Equal("tasks/get", this._handler.CapturedJsonRpcRequest?.Method);
+ Assert.Equal("task-123", this._handler.CapturedTaskIdParams?.Id);
+ }
+
+ [Fact]
+ public async Task RunAsync_WithTaskInThreadAndMessage_AddTaskAsReferencesToMessageAsync()
+ {
+ // Arrange
+ this._handler.ResponseToReturn = new AgentMessage
+ {
+ MessageId = "response-123",
+ Role = MessageRole.Agent,
+ Parts = [new TextPart { Text = "Response to task" }]
+ };
+
+ var thread = (A2AAgentThread)this._agent.GetNewThread();
+ thread.TaskId = "task-123";
+
+ var inputMessage = new ChatMessage(ChatRole.User, "Please make the background transparent");
+
+ // Act
+ await this._agent.RunAsync(inputMessage, thread);
+
+ // Assert
+ var message = this._handler.CapturedMessageSendParams?.Message;
+ Assert.Null(message?.TaskId);
+ Assert.NotNull(message?.ReferenceTaskIds);
+ Assert.Contains("task-123", message.ReferenceTaskIds);
+ }
+
+ [Fact]
+ public async Task RunAsync_WithAgentTask_UpdatesThreadTaskIdAsync()
+ {
+ // Arrange
+ this._handler.ResponseToReturn = new AgentTask
+ {
+ Id = "task-456",
+ ContextId = "context-789",
+ Status = new() { State = TaskState.Submitted }
+ };
+
+ var thread = this._agent.GetNewThread();
+
+ // Act
+ await this._agent.RunAsync("Start a task", thread);
+
+ // Assert
+ var a2aThread = (A2AAgentThread)thread;
+ Assert.Equal("task-456", a2aThread.TaskId);
+ }
+
+ [Fact]
+ public async Task RunAsync_WithAgentTaskResponse_ReturnsTaskResponseCorrectlyAsync()
+ {
+ // Arrange
+ this._handler.ResponseToReturn = new AgentTask
+ {
+ Id = "task-789",
+ ContextId = "context-456",
+ Status = new() { State = TaskState.Submitted },
+ Metadata = new Dictionary
+ {
+ { "key1", JsonSerializer.SerializeToElement("value1") },
+ { "count", JsonSerializer.SerializeToElement(42) }
+ }
+ };
+
+ var thread = this._agent.GetNewThread();
+
+ // Act
+ var result = await this._agent.RunAsync("Start a long-running task", thread);
+
+ // Assert - verify task is converted correctly
+ Assert.NotNull(result);
+ Assert.Equal(this._agent.Id, result.AgentId);
+ Assert.Equal("task-789", result.ResponseId);
+
+ Assert.NotNull(result.RawRepresentation);
+ Assert.IsType(result.RawRepresentation);
+ Assert.Equal("task-789", ((AgentTask)result.RawRepresentation).Id);
+
+ // Assert - verify continuation token is set for submitted task
+ Assert.NotNull(result.ContinuationToken);
+ Assert.IsType(result.ContinuationToken);
+ Assert.Equal("task-789", ((A2AContinuationToken)result.ContinuationToken).TaskId);
+
+ // Assert - verify thread is updated with context and task IDs
+ var a2aThread = (A2AAgentThread)thread;
+ Assert.Equal("context-456", a2aThread.ContextId);
+ Assert.Equal("task-789", a2aThread.TaskId);
+
+ // Assert - verify metadata is preserved
+ Assert.NotNull(result.AdditionalProperties);
+ Assert.NotNull(result.AdditionalProperties["key1"]);
+ Assert.Equal("value1", ((JsonElement)result.AdditionalProperties["key1"]!).GetString());
+ Assert.NotNull(result.AdditionalProperties["count"]);
+ Assert.Equal(42, ((JsonElement)result.AdditionalProperties["count"]!).GetInt32());
+ }
+
+ [Theory]
+ [InlineData(TaskState.Submitted)]
+ [InlineData(TaskState.Working)]
+ [InlineData(TaskState.Completed)]
+ [InlineData(TaskState.Failed)]
+ [InlineData(TaskState.Canceled)]
+ public async Task RunAsync_WithVariousTaskStates_ReturnsCorrectTokenAsync(TaskState taskState)
+ {
+ // Arrange
+ this._handler.ResponseToReturn = new AgentTask
+ {
+ Id = "task-123",
+ ContextId = "context-123",
+ Status = new() { State = taskState }
+ };
+
+ // Act
+ var result = await this._agent.RunAsync("Test message");
+
+ // Assert
+ if (taskState == TaskState.Submitted || taskState == TaskState.Working)
+ {
+ Assert.NotNull(result.ContinuationToken);
+ }
+ else
+ {
+ Assert.Null(result.ContinuationToken);
+ }
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithContinuationTokenAndMessages_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ var inputMessages = new List
+ {
+ new(ChatRole.User, "Test message")
+ };
+
+ var options = new AgentRunOptions { ContinuationToken = new A2AContinuationToken("task-123") };
+
+ // Act & Assert
+ await Assert.ThrowsAsync(async () =>
+ {
+ await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options))
+ {
+ // Just iterate through to trigger the exception
+ }
+ });
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithTaskInThreadAndMessage_AddTaskAsReferencesToMessageAsync()
+ {
+ // Arrange
+ this._handler.StreamingResponseToReturn = new AgentMessage
+ {
+ MessageId = "response-123",
+ Role = MessageRole.Agent,
+ Parts = [new TextPart { Text = "Response to task" }]
+ };
+
+ var thread = (A2AAgentThread)this._agent.GetNewThread();
+ thread.TaskId = "task-123";
+
+ // Act
+ await foreach (var _ in this._agent.RunStreamingAsync("Please make the background transparent", thread))
+ {
+ // Just iterate through to trigger the logic
+ }
+
+ // Assert
+ var message = this._handler.CapturedMessageSendParams?.Message;
+ Assert.Null(message?.TaskId);
+ Assert.NotNull(message?.ReferenceTaskIds);
+ Assert.Contains("task-123", message.ReferenceTaskIds);
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithAgentTask_UpdatesThreadTaskIdAsync()
+ {
+ // Arrange
+ this._handler.StreamingResponseToReturn = new AgentTask
+ {
+ Id = "task-456",
+ ContextId = "context-789",
+ Status = new() { State = TaskState.Submitted }
+ };
+
+ var thread = this._agent.GetNewThread();
+
+ // Act
+ await foreach (var _ in this._agent.RunStreamingAsync("Start a task", thread))
+ {
+ // Just iterate through to trigger the logic
+ }
+
+ // Assert
+ var a2aThread = (A2AAgentThread)thread;
+ Assert.Equal("task-456", a2aThread.TaskId);
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithAgentMessage_YieldsResponseUpdateAsync()
+ {
+ // Arrange
+ const string MessageId = "msg-123";
+ const string ContextId = "ctx-456";
+ const string MessageText = "Hello from agent!";
+
+ this._handler.StreamingResponseToReturn = new AgentMessage
+ {
+ MessageId = MessageId,
+ Role = MessageRole.Agent,
+ ContextId = ContextId,
+ Parts =
+ [
+ new TextPart { Text = MessageText }
+ ]
+ };
+
+ // Act
+ var updates = new List();
+ await foreach (var update in this._agent.RunStreamingAsync("Test message"))
+ {
+ updates.Add(update);
+ }
+
+ // Assert - one update should be yielded
+ Assert.Single(updates);
+
+ var update0 = updates[0];
+ Assert.Equal(ChatRole.Assistant, update0.Role);
+ Assert.Equal(MessageId, update0.MessageId);
+ Assert.Equal(MessageId, update0.ResponseId);
+ Assert.Equal(this._agent.Id, update0.AgentId);
+ Assert.Equal(MessageText, update0.Text);
+ Assert.IsType(update0.RawRepresentation);
+ Assert.Equal(MessageId, ((AgentMessage)update0.RawRepresentation!).MessageId);
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithAgentTask_YieldsResponseUpdateAsync()
+ {
+ // Arrange
+ const string TaskId = "task-789";
+ const string ContextId = "ctx-012";
+
+ this._handler.StreamingResponseToReturn = new AgentTask
+ {
+ Id = TaskId,
+ ContextId = ContextId,
+ Status = new() { State = TaskState.Submitted },
+ Artifacts = [
+ new()
+ {
+ ArtifactId = "art-123",
+ Parts = [new TextPart { Text = "Task artifact content" }]
+ }
+ ]
+ };
+
+ var thread = this._agent.GetNewThread();
+
+ // Act
+ var updates = new List();
+ await foreach (var update in this._agent.RunStreamingAsync("Start long-running task", thread))
+ {
+ updates.Add(update);
+ }
+
+ // Assert - one update should be yielded from artifact
+ Assert.Single(updates);
+
+ var update0 = updates[0];
+ Assert.Equal(ChatRole.Assistant, update0.Role);
+ Assert.Equal(TaskId, update0.ResponseId);
+ Assert.Equal(this._agent.Id, update0.AgentId);
+ Assert.IsType(update0.RawRepresentation);
+ Assert.Equal(TaskId, ((AgentTask)update0.RawRepresentation!).Id);
+
+ // Assert - thread should be updated with context and task IDs
+ var a2aThread = (A2AAgentThread)thread;
+ Assert.Equal(ContextId, a2aThread.ContextId);
+ Assert.Equal(TaskId, a2aThread.TaskId);
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithTaskStatusUpdateEvent_YieldsResponseUpdateAsync()
+ {
+ // Arrange
+ const string TaskId = "task-status-123";
+ const string ContextId = "ctx-status-456";
+
+ this._handler.StreamingResponseToReturn = new TaskStatusUpdateEvent
+ {
+ TaskId = TaskId,
+ ContextId = ContextId,
+ Status = new() { State = TaskState.Working }
+ };
+
+ var thread = this._agent.GetNewThread();
+
+ // Act
+ var updates = new List();
+ await foreach (var update in this._agent.RunStreamingAsync("Check task status", thread))
+ {
+ updates.Add(update);
+ }
+
+ // Assert - one update should be yielded
+ Assert.Single(updates);
+
+ var update0 = updates[0];
+ Assert.Equal(ChatRole.Assistant, update0.Role);
+ Assert.Equal(TaskId, update0.ResponseId);
+ Assert.Equal(this._agent.Id, update0.AgentId);
+ Assert.IsType(update0.RawRepresentation);
+
+ // Assert - thread should be updated with context and task IDs
+ var a2aThread = (A2AAgentThread)thread;
+ Assert.Equal(ContextId, a2aThread.ContextId);
+ Assert.Equal(TaskId, a2aThread.TaskId);
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithTaskArtifactUpdateEvent_YieldsResponseUpdateAsync()
+ {
+ // Arrange
+ const string TaskId = "task-artifact-123";
+ const string ContextId = "ctx-artifact-456";
+ const string ArtifactContent = "Task artifact data";
+
+ this._handler.StreamingResponseToReturn = new TaskArtifactUpdateEvent
+ {
+ TaskId = TaskId,
+ ContextId = ContextId,
+ Artifact = new()
+ {
+ ArtifactId = "artifact-789",
+ Parts = [new TextPart { Text = ArtifactContent }]
+ }
+ };
+
+ var thread = this._agent.GetNewThread();
+
+ // Act
+ var updates = new List();
+ await foreach (var update in this._agent.RunStreamingAsync("Process artifact", thread))
+ {
+ updates.Add(update);
+ }
+
+ // Assert - one update should be yielded
+ Assert.Single(updates);
+
+ var update0 = updates[0];
+ Assert.Equal(ChatRole.Assistant, update0.Role);
+ Assert.Equal(TaskId, update0.ResponseId);
+ Assert.Equal(this._agent.Id, update0.AgentId);
+ Assert.IsType(update0.RawRepresentation);
+
+ // Assert - artifact content should be in the update
+ Assert.NotEmpty(update0.Contents);
+ Assert.Equal(ArtifactContent, update0.Text);
+
+ // Assert - thread should be updated with context and task IDs
+ var a2aThread = (A2AAgentThread)thread;
+ Assert.Equal(ContextId, a2aThread.ContextId);
+ Assert.Equal(TaskId, a2aThread.TaskId);
+ }
+
+ [Fact]
+ public async Task RunAsync_WithAllowBackgroundResponsesAndNoThread_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ var inputMessages = new List
+ {
+ new(ChatRole.User, "Test message")
+ };
+
+ var options = new AgentRunOptions { AllowBackgroundResponses = true };
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => this._agent.RunAsync(inputMessages, null, options));
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithAllowBackgroundResponsesAndNoThread_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ var inputMessages = new List
+ {
+ new(ChatRole.User, "Test message")
+ };
+
+ var options = new AgentRunOptions { AllowBackgroundResponses = true };
+
+ // Act & Assert
+ await Assert.ThrowsAsync(async () =>
+ {
+ await foreach (var _ in this._agent.RunStreamingAsync(inputMessages, null, options))
+ {
+ // Just iterate through to trigger the exception
+ }
+ });
+ }
+
+ [Fact]
+ public async Task RunAsync_WithInvalidThreadType_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ // Create a thread from a different agent type
+ var invalidThread = new CustomAgentThread();
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() => this._agent.RunAsync(invalidThread));
+ }
+
+ [Fact]
+ public async Task RunStreamingAsync_WithInvalidThreadType_ThrowsInvalidOperationExceptionAsync()
+ {
+ // Arrange
+ var inputMessages = new List
+ {
+ new(ChatRole.User, "Test message")
+ };
+
+ // Create a thread from a different agent type
+ var invalidThread = new CustomAgentThread();
+
+ // Act & Assert
+ await Assert.ThrowsAsync(async () => await this._agent.RunStreamingAsync(inputMessages, invalidThread).ToListAsync());
+ }
+
public void Dispose()
{
this._handler.Dispose();
this._httpClient.Dispose();
}
+
+ ///
+ /// Custom agent thread class for testing invalid thread type scenario.
+ ///
+ private sealed class CustomAgentThread : AgentThread;
+
internal sealed class A2AClientHttpMessageHandlerStub : HttpMessageHandler
{
+ public JsonRpcRequest? CapturedJsonRpcRequest { get; set; }
+
public MessageSendParams? CapturedMessageSendParams { get; set; }
+ public TaskIdParams? CapturedTaskIdParams { get; set; }
+
public A2AEvent? ResponseToReturn { get; set; }
public A2AEvent? StreamingResponseToReturn { get; set; }
@@ -416,9 +892,19 @@ protected override async Task SendAsync(HttpRequestMessage
var content = await request.Content!.ReadAsStringAsync();
#pragma warning restore CA2016
- var jsonRpcRequest = JsonSerializer.Deserialize(content)!;
+ this.CapturedJsonRpcRequest = JsonSerializer.Deserialize(content);
- this.CapturedMessageSendParams = jsonRpcRequest.Params?.Deserialize();
+ try
+ {
+ this.CapturedMessageSendParams = this.CapturedJsonRpcRequest?.Params?.Deserialize();
+ }
+ catch { /* Ignore deserialization errors for non-MessageSendParams requests */ }
+
+ try
+ {
+ this.CapturedTaskIdParams = this.CapturedJsonRpcRequest?.Params?.Deserialize();
+ }
+ catch { /* Ignore deserialization errors for non-TaskIdParams requests */ }
// Return the pre-configured non-streaming response
if (this.ResponseToReturn is not null)
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentThreadTests.cs
new file mode 100644
index 00000000000..90b65aa5ac5
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AAgentThreadTests.cs
@@ -0,0 +1,30 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+
+namespace Microsoft.Agents.AI.A2A.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class A2AAgentThreadTests
+{
+ [Fact]
+ public void Constructor_RoundTrip_SerializationPreservesState()
+ {
+ // Arrange
+ const string ContextId = "context-rt-001";
+ const string TaskId = "task-rt-002";
+
+ A2AAgentThread originalThread = new() { ContextId = ContextId, TaskId = TaskId };
+
+ // Act
+ JsonElement serialized = originalThread.Serialize();
+
+ A2AAgentThread deserializedThread = new(serialized);
+
+ // Assert
+ Assert.Equal(originalThread.ContextId, deserializedThread.ContextId);
+ Assert.Equal(originalThread.TaskId, deserializedThread.TaskId);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AContinuationTokenTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AContinuationTokenTests.cs
new file mode 100644
index 00000000000..1bb0d99e00e
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/A2AContinuationTokenTests.cs
@@ -0,0 +1,152 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Text.Json;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.A2A.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class A2AContinuationTokenTests
+{
+ [Fact]
+ public void Constructor_WithValidTaskId_InitializesTaskIdProperty()
+ {
+ // Arrange
+ const string TaskId = "task-123";
+
+ // Act
+ var token = new A2AContinuationToken(TaskId);
+
+ // Assert
+ Assert.Equal(TaskId, token.TaskId);
+ }
+
+ [Fact]
+ public void ToBytes_WithValidToken_SerializesToJsonBytes()
+ {
+ // Arrange
+ const string TaskId = "task-456";
+ var token = new A2AContinuationToken(TaskId);
+
+ // Act
+ var bytes = token.ToBytes();
+
+ // Assert
+ Assert.NotEqual(0, bytes.Length);
+ var jsonString = System.Text.Encoding.UTF8.GetString(bytes.ToArray());
+ using var jsonDoc = JsonDocument.Parse(jsonString);
+ var root = jsonDoc.RootElement;
+ Assert.True(root.TryGetProperty("taskId", out var taskIdElement));
+ Assert.Equal(TaskId, taskIdElement.GetString());
+ }
+
+ [Fact]
+ public void FromToken_WithA2AContinuationToken_ReturnsSameInstance()
+ {
+ // Arrange
+ const string TaskId = "task-direct";
+ var originalToken = new A2AContinuationToken(TaskId);
+
+ // Act
+ var resultToken = A2AContinuationToken.FromToken(originalToken);
+
+ // Assert
+ Assert.Same(originalToken, resultToken);
+ Assert.Equal(TaskId, resultToken.TaskId);
+ }
+
+ [Fact]
+ public void FromToken_WithSerializedToken_DeserializesCorrectly()
+ {
+ // Arrange
+ const string TaskId = "task-deserialized";
+ var originalToken = new A2AContinuationToken(TaskId);
+ var serialized = originalToken.ToBytes();
+
+ // Create a mock token wrapper to pass to FromToken
+ var mockToken = new MockResponseContinuationToken(serialized);
+
+ // Act
+ var resultToken = A2AContinuationToken.FromToken(mockToken);
+
+ // Assert
+ Assert.Equal(TaskId, resultToken.TaskId);
+ Assert.IsType(resultToken);
+ }
+
+ [Fact]
+ public void FromToken_RoundTrip_PreservesTaskId()
+ {
+ // Arrange
+ const string TaskId = "task-roundtrip-123";
+ var originalToken = new A2AContinuationToken(TaskId);
+ var serialized = originalToken.ToBytes();
+ var mockToken = new MockResponseContinuationToken(serialized);
+
+ // Act
+ var deserializedToken = A2AContinuationToken.FromToken(mockToken);
+ var reserialized = deserializedToken.ToBytes();
+ var mockToken2 = new MockResponseContinuationToken(reserialized);
+ var deserializedAgain = A2AContinuationToken.FromToken(mockToken2);
+
+ // Assert
+ Assert.Equal(TaskId, deserializedAgain.TaskId);
+ }
+
+ [Fact]
+ public void FromToken_WithEmptyData_ThrowsArgumentException()
+ {
+ // Arrange
+ var emptyToken = new MockResponseContinuationToken(ReadOnlyMemory.Empty);
+
+ // Act & Assert
+ Assert.Throws(() => A2AContinuationToken.FromToken(emptyToken));
+ }
+
+ [Fact]
+ public void FromToken_WithMissingTaskIdProperty_ThrowsException()
+ {
+ // Arrange
+ var jsonWithoutTaskId = System.Text.Encoding.UTF8.GetBytes("{ \"someOtherProperty\": \"value\" }").AsMemory();
+ var mockToken = new MockResponseContinuationToken(jsonWithoutTaskId);
+
+ // Act & Assert
+ Assert.Throws(() => A2AContinuationToken.FromToken(mockToken));
+ }
+
+ [Fact]
+ public void FromToken_WithValidTaskId_ParsesTaskIdCorrectly()
+ {
+ // Arrange
+ const string TaskId = "task-multi-prop";
+ var json = System.Text.Encoding.UTF8.GetBytes($"{{ \"taskId\": \"{TaskId}\" }}").AsMemory();
+ var mockToken = new MockResponseContinuationToken(json);
+
+ // Act
+ var resultToken = A2AContinuationToken.FromToken(mockToken);
+
+ // Assert
+ Assert.Equal(TaskId, resultToken.TaskId);
+ }
+
+ ///
+ /// Mock implementation of ResponseContinuationToken for testing.
+ ///
+ private sealed class MockResponseContinuationToken : ResponseContinuationToken
+ {
+ private readonly ReadOnlyMemory _data;
+
+ public MockResponseContinuationToken(ReadOnlyMemory data)
+ {
+ this._data = data;
+ }
+
+ public override ReadOnlyMemory ToBytes()
+ {
+ return this._data;
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAgentTaskExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAgentTaskExtensionsTests.cs
new file mode 100644
index 00000000000..97c9ca7c058
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AAgentTaskExtensionsTests.cs
@@ -0,0 +1,169 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using A2A;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.A2A.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class A2AAgentTaskExtensionsTests
+{
+ [Fact]
+ public void ToChatMessages_WithNullAgentTask_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AgentTask agentTask = null!;
+
+ // Act & Assert
+ Assert.Throws(() => agentTask.ToChatMessages());
+ }
+
+ [Fact]
+ public void ToAIContents_WithNullAgentTask_ThrowsArgumentNullException()
+ {
+ // Arrange
+ AgentTask agentTask = null!;
+
+ // Act & Assert
+ Assert.Throws(() => agentTask.ToAIContents());
+ }
+
+ [Fact]
+ public void ToChatMessages_WithEmptyArtifactsAndNoUserInputRequests_ReturnsNull()
+ {
+ // Arrange
+ var agentTask = new AgentTask
+ {
+ Id = "task1",
+ Artifacts = [],
+ Status = new AgentTaskStatus { State = TaskState.Completed },
+ };
+
+ // Act
+ IList? result = agentTask.ToChatMessages();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ToChatMessages_WithNullArtifactsAndNoUserInputRequests_ReturnsNull()
+ {
+ // Arrange
+ var agentTask = new AgentTask
+ {
+ Id = "task1",
+ Artifacts = null,
+ Status = new AgentTaskStatus { State = TaskState.Completed },
+ };
+
+ // Act
+ IList? result = agentTask.ToChatMessages();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ToAIContents_WithEmptyArtifactsAndNoUserInputRequests_ReturnsNull()
+ {
+ // Arrange
+ var agentTask = new AgentTask
+ {
+ Id = "task1",
+ Artifacts = [],
+ Status = new AgentTaskStatus { State = TaskState.Completed },
+ };
+
+ // Act
+ IList? result = agentTask.ToAIContents();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ToAIContents_WithNullArtifactsAndNoUserInputRequests_ReturnsNull()
+ {
+ // Arrange
+ var agentTask = new AgentTask
+ {
+ Id = "task1",
+ Artifacts = null,
+ Status = new AgentTaskStatus { State = TaskState.Completed },
+ };
+
+ // Act
+ IList? result = agentTask.ToAIContents();
+
+ // Assert
+ Assert.Null(result);
+ }
+
+ [Fact]
+ public void ToChatMessages_WithValidArtifact_ReturnsChatMessages()
+ {
+ // Arrange
+ var artifact = new Artifact
+ {
+ Parts = [new TextPart { Text = "response" }],
+ };
+
+ var agentTask = new AgentTask
+ {
+ Id = "task1",
+ Artifacts = [artifact],
+ Status = new AgentTaskStatus { State = TaskState.Completed },
+ };
+
+ // Act
+ IList? result = agentTask.ToChatMessages();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotEmpty(result);
+ Assert.All(result, msg => Assert.Equal(ChatRole.Assistant, msg.Role));
+ Assert.Equal("response", result[0].Contents[0].ToString());
+ }
+
+ [Fact]
+ public void ToAIContents_WithMultipleArtifacts_FlattenAllContents()
+ {
+ // Arrange
+ var artifact1 = new Artifact
+ {
+ Parts = [new TextPart { Text = "content1" }],
+ };
+
+ var artifact2 = new Artifact
+ {
+ Parts =
+ [
+ new TextPart { Text = "content2" },
+ new TextPart { Text = "content3" }
+ ],
+ };
+
+ var agentTask = new AgentTask
+ {
+ Id = "task1",
+ Artifacts = [artifact1, artifact2],
+ Status = new AgentTaskStatus { State = TaskState.Completed },
+ };
+
+ // Act
+ IList? result = agentTask.ToAIContents();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.NotEmpty(result);
+ Assert.Equal(3, result.Count);
+ Assert.Equal("content1", result[0].ToString());
+ Assert.Equal("content2", result[1].ToString());
+ Assert.Equal("content3", result[2].ToString());
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AArtifactExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AArtifactExtensionsTests.cs
new file mode 100644
index 00000000000..659c6780343
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Extensions/A2AArtifactExtensionsTests.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Collections.Generic;
+using System.Text.Json;
+using A2A;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.A2A.UnitTests;
+
+///
+/// Unit tests for the class.
+///
+public sealed class A2AArtifactExtensionsTests
+{
+ [Fact]
+ public void ToChatMessage_WithMultiplePartsMetadataAndRawRepresentation_ReturnsCorrectChatMessage()
+ {
+ // Arrange
+ var artifact = new Artifact
+ {
+ ArtifactId = "artifact-comprehensive",
+ Name = "comprehensive-artifact",
+ Parts =
+ [
+ new TextPart { Text = "First part" },
+ new TextPart { Text = "Second part" },
+ new TextPart { Text = "Third part" }
+ ],
+ Metadata = new Dictionary
+ {
+ { "key1", JsonSerializer.SerializeToElement("value1") },
+ { "key2", JsonSerializer.SerializeToElement(42) }
+ }
+ };
+
+ // Act
+ var result = artifact.ToChatMessage();
+
+ // Assert - Verify multiple parts
+ Assert.NotNull(result);
+ Assert.Equal(ChatRole.Assistant, result.Role);
+ Assert.Equal(3, result.Contents.Count);
+ Assert.All(result.Contents, content => Assert.IsType(content));
+ Assert.Equal("First part", ((TextContent)result.Contents[0]).Text);
+ Assert.Equal("Second part", ((TextContent)result.Contents[1]).Text);
+ Assert.Equal("Third part", ((TextContent)result.Contents[2]).Text);
+
+ // Assert - Verify metadata conversion to AdditionalProperties
+ Assert.NotNull(result.AdditionalProperties);
+ Assert.Equal(2, result.AdditionalProperties.Count);
+ Assert.True(result.AdditionalProperties.ContainsKey("key1"));
+ Assert.True(result.AdditionalProperties.ContainsKey("key2"));
+
+ // Assert - Verify RawRepresentation is set to artifact
+ Assert.NotNull(result.RawRepresentation);
+ Assert.Same(artifact, result.RawRepresentation);
+ }
+
+ [Fact]
+ public void ToAIContents_WithMultipleParts_ReturnsCorrectList()
+ {
+ // Arrange
+ var artifact = new Artifact
+ {
+ ArtifactId = "artifact-ai-multi",
+ Name = "test",
+ Parts = new List
+ {
+ new TextPart { Text = "Part 1" },
+ new TextPart { Text = "Part 2" },
+ new TextPart { Text = "Part 3" }
+ },
+ Metadata = null
+ };
+
+ // Act
+ var result = artifact.ToAIContents();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Equal(3, result.Count);
+ Assert.All(result, content => Assert.IsType(content));
+ Assert.Equal("Part 1", ((TextContent)result[0]).Text);
+ Assert.Equal("Part 2", ((TextContent)result[1]).Text);
+ Assert.Equal("Part 3", ((TextContent)result[2]).Text);
+ }
+
+ [Fact]
+ public void ToAIContents_WithEmptyParts_ReturnsEmptyList()
+ {
+ // Arrange
+ var artifact = new Artifact
+ {
+ ArtifactId = "artifact-empty",
+ Name = "test",
+ Parts = new List(),
+ Metadata = null
+ };
+
+ // Act
+ var result = artifact.ToAIContents();
+
+ // Assert
+ Assert.NotNull(result);
+ Assert.Empty(result);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj
index f654f3eeecb..d33de0613bd 100644
--- a/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj
@@ -1,14 +1,5 @@
-
- $(ProjectsTargetFrameworks)
-
-
-
-
-
-
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs
index 6ce89101a0d..0eeacaf1611 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatClientTests.cs
@@ -19,15 +19,15 @@ public sealed class AGUIAgentTests
public async Task RunAsync_AggregatesStreamingUpdates_ReturnsCompleteMessagesAsync()
{
// Arrange
- using HttpClient httpClient = this.CreateMockHttpClient(new BaseEvent[]
- {
+ using HttpClient httpClient = this.CreateMockHttpClient(
+ [
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
new TextMessageStartEvent { MessageId = "msg1", Role = AGUIRoles.Assistant },
new TextMessageContentEvent { MessageId = "msg1", Delta = "Hello" },
new TextMessageContentEvent { MessageId = "msg1", Delta = " World" },
new TextMessageEndEvent { MessageId = "msg1" },
new RunFinishedEvent { ThreadId = "thread1", RunId = "run1" }
- });
+ ]);
var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options);
AIAgent agent = chatClient.CreateAIAgent(instructions: null, name: "agent1", description: "Test agent", tools: []);
@@ -182,16 +182,16 @@ public async Task RunStreamingAsync_GeneratesUniqueRunId_ForEachInvocationAsync(
{
// Arrange
var handler = new TestDelegatingHandler();
- handler.AddResponseWithCapture(new BaseEvent[]
- {
+ handler.AddResponseWithCapture(
+ [
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
new RunFinishedEvent { ThreadId = "thread1", RunId = "run1" }
- });
- handler.AddResponseWithCapture(new BaseEvent[]
- {
+ ]);
+ handler.AddResponseWithCapture(
+ [
new RunStartedEvent { ThreadId = "thread1", RunId = "run2" },
new RunFinishedEvent { ThreadId = "thread1", RunId = "run2" }
- });
+ ]);
using HttpClient httpClient = new(handler);
var chatClient = new AGUIChatClient(httpClient, "http://localhost/agent", null, AGUIJsonSerializerContext.Default.Options);
@@ -1584,7 +1584,7 @@ public async Task GetStreamingResponseAsync_ReceivesStateSnapshot_AsDataContentW
Assert.Equal("application/json", dataContent.MediaType);
string jsonText = System.Text.Encoding.UTF8.GetString(dataContent.Data.ToArray());
- JsonElement deserializedState = JsonSerializer.Deserialize(jsonText);
+ JsonElement deserializedState = JsonElement.Parse(jsonText);
Assert.Equal("abc123", deserializedState.GetProperty("sessionId").GetString());
Assert.Equal(5, deserializedState.GetProperty("step").GetInt32());
}
@@ -1593,7 +1593,7 @@ public async Task GetStreamingResponseAsync_ReceivesStateSnapshot_AsDataContentW
internal sealed class TestDelegatingHandler : DelegatingHandler
{
private readonly Queue>> _responseFactories = new();
- private readonly List _capturedRunIds = new();
+ private readonly List _capturedRunIds = [];
public IReadOnlyList CapturedRunIds => this._capturedRunIds;
@@ -1701,7 +1701,7 @@ protected override async Task SendAsync(HttpRequestMessage
this.RequestWasMade = true;
// Capture the state and message count from the request
-#if NET472 || NETSTANDARD2_0
+#if !NET
string requestBody = await request.Content!.ReadAsStringAsync().ConfigureAwait(false);
#else
string requestBody = await request.Content!.ReadAsStringAsync(cancellationToken).ConfigureAwait(false);
@@ -1709,7 +1709,7 @@ protected override async Task SendAsync(HttpRequestMessage
RunAgentInput? input = JsonSerializer.Deserialize(requestBody, AGUIJsonSerializerContext.Default.RunAgentInput);
if (input != null)
{
- if (input.State.ValueKind != JsonValueKind.Undefined && input.State.ValueKind != JsonValueKind.Null)
+ if (input.State.ValueKind is not JsonValueKind.Undefined and not JsonValueKind.Null)
{
this.CapturedState = input.State;
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.cs
index 4a8d7908e94..bc3a73fb4cf 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIChatMessageExtensionsTests.cs
@@ -29,9 +29,7 @@ public sealed class WeatherResponse
[JsonSerializable(typeof(WeatherRequest))]
[JsonSerializable(typeof(WeatherResponse))]
[JsonSerializable(typeof(Dictionary))]
-internal sealed partial class CustomTypesContext : JsonSerializerContext
-{
-}
+internal sealed partial class CustomTypesContext : JsonSerializerContext;
///
/// Unit tests for the class.
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIHttpServiceTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIHttpServiceTests.cs
index ec4f34db149..b06913c8373 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIHttpServiceTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIHttpServiceTests.cs
@@ -22,16 +22,16 @@ public sealed class AGUIHttpServiceTests
public async Task PostRunAsync_SendsRequestAndParsesSSEStream_SuccessfullyAsync()
{
// Arrange
- BaseEvent[] events = new BaseEvent[]
- {
+ BaseEvent[] events =
+ [
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
new TextMessageStartEvent { MessageId = "msg1", Role = AGUIRoles.Assistant },
new TextMessageContentEvent { MessageId = "msg1", Delta = "Hello" },
new TextMessageEndEvent { MessageId = "msg1" },
new RunFinishedEvent { ThreadId = "thread1", RunId = "run1" }
- };
+ ];
- HttpClient httpClient = this.CreateMockHttpClient(events, HttpStatusCode.OK);
+ HttpClient httpClient = CreateMockHttpClient(events, HttpStatusCode.OK);
AGUIHttpService service = new(httpClient, "http://localhost/agent");
RunAgentInput input = new()
{
@@ -60,7 +60,7 @@ public async Task PostRunAsync_SendsRequestAndParsesSSEStream_SuccessfullyAsync(
public async Task PostRunAsync_WithNonSuccessStatusCode_ThrowsHttpRequestExceptionAsync()
{
// Arrange
- HttpClient httpClient = this.CreateMockHttpClient([], HttpStatusCode.InternalServerError);
+ HttpClient httpClient = CreateMockHttpClient([], HttpStatusCode.InternalServerError);
AGUIHttpService service = new(httpClient, "http://localhost/agent");
RunAgentInput input = new()
{
@@ -83,14 +83,14 @@ await Assert.ThrowsAsync(async () =>
public async Task PostRunAsync_DeserializesMultipleEventTypes_CorrectlyAsync()
{
// Arrange
- BaseEvent[] events = new BaseEvent[]
- {
+ BaseEvent[] events =
+ [
new RunStartedEvent { ThreadId = "thread1", RunId = "run1" },
new RunErrorEvent { Message = "Error occurred", Code = "ERR001" },
- new RunFinishedEvent { ThreadId = "thread1", RunId = "run1", Result = JsonDocument.Parse("\"Success\"").RootElement.Clone() }
- };
+ new RunFinishedEvent { ThreadId = "thread1", RunId = "run1", Result = JsonElement.Parse("\"Success\"") }
+ ];
- HttpClient httpClient = this.CreateMockHttpClient(events, HttpStatusCode.OK);
+ HttpClient httpClient = CreateMockHttpClient(events, HttpStatusCode.OK);
AGUIHttpService service = new(httpClient, "http://localhost/agent");
RunAgentInput input = new()
{
@@ -120,7 +120,7 @@ public async Task PostRunAsync_DeserializesMultipleEventTypes_CorrectlyAsync()
public async Task PostRunAsync_WithEmptyEventStream_CompletesSuccessfullyAsync()
{
// Arrange
- HttpClient httpClient = this.CreateMockHttpClient([], HttpStatusCode.OK);
+ HttpClient httpClient = CreateMockHttpClient([], HttpStatusCode.OK);
AGUIHttpService service = new(httpClient, "http://localhost/agent");
RunAgentInput input = new()
{
@@ -175,9 +175,9 @@ await Assert.ThrowsAsync(async () =>
});
}
- private HttpClient CreateMockHttpClient(BaseEvent[] events, HttpStatusCode statusCode)
+ private static HttpClient CreateMockHttpClient(BaseEvent[] events, HttpStatusCode statusCode)
{
- string sseContent = string.Join("", events.Select(e =>
+ string sseContent = string.Concat(events.Select(e =>
$"data: {JsonSerializer.Serialize(e, AGUIJsonSerializerContext.Default.BaseEvent)}\n\n"));
Mock handlerMock = new(MockBehavior.Strict);
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIJsonSerializerContextTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIJsonSerializerContextTests.cs
index 566e69d992f..33f259a681f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIJsonSerializerContextTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AGUIJsonSerializerContextTests.cs
@@ -25,7 +25,7 @@ public void RunAgentInput_Serializes_WithAllRequiredFields()
// Act
string json = JsonSerializer.Serialize(input, AGUIJsonSerializerContext.Default.RunAgentInput);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("threadId", out JsonElement threadIdProp));
@@ -150,7 +150,7 @@ public void RunStartedEvent_Serializes_WithCorrectEventType()
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunStartedEvent);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
Assert.Equal(AGUIEventTypes.RunStarted, jsonElement.GetProperty("type").GetString());
}
@@ -162,7 +162,7 @@ public void RunStartedEvent_Includes_ThreadIdAndRunIdInOutput()
// Act
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunStartedEvent);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("threadId", out JsonElement threadIdProp));
@@ -219,7 +219,7 @@ public void RunFinishedEvent_Serializes_WithCorrectEventType()
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunFinishedEvent);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
Assert.Equal(AGUIEventTypes.RunFinished, jsonElement.GetProperty("type").GetString());
}
@@ -227,11 +227,11 @@ public void RunFinishedEvent_Serializes_WithCorrectEventType()
public void RunFinishedEvent_Includes_ThreadIdRunIdAndOptionalResult()
{
// Arrange
- RunFinishedEvent evt = new() { ThreadId = "thread1", RunId = "run1", Result = JsonDocument.Parse("\"Success\"").RootElement.Clone() };
+ RunFinishedEvent evt = new() { ThreadId = "thread1", RunId = "run1", Result = JsonElement.Parse("\"Success\"") };
// Act
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunFinishedEvent);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("threadId", out JsonElement threadIdProp));
@@ -269,7 +269,7 @@ public void RunFinishedEvent_Deserializes_FromJsonCorrectly()
public void RunFinishedEvent_RoundTrip_PreservesData()
{
// Arrange
- RunFinishedEvent original = new() { ThreadId = "thread1", RunId = "run1", Result = JsonDocument.Parse("\"Done\"").RootElement.Clone() };
+ RunFinishedEvent original = new() { ThreadId = "thread1", RunId = "run1", Result = JsonElement.Parse("\"Done\"") };
// Act
string json = JsonSerializer.Serialize(original, AGUIJsonSerializerContext.Default.RunFinishedEvent);
@@ -292,7 +292,7 @@ public void RunErrorEvent_Serializes_WithCorrectEventType()
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunErrorEvent);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
Assert.Equal(AGUIEventTypes.RunError, jsonElement.GetProperty("type").GetString());
}
@@ -304,7 +304,7 @@ public void RunErrorEvent_Includes_MessageAndOptionalCode()
// Act
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.RunErrorEvent);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("message", out JsonElement messageProp));
@@ -360,7 +360,7 @@ public void TextMessageStartEvent_Serializes_WithCorrectEventType()
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.TextMessageStartEvent);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
Assert.Equal(AGUIEventTypes.TextMessageStart, jsonElement.GetProperty("type").GetString());
}
@@ -372,7 +372,7 @@ public void TextMessageStartEvent_Includes_MessageIdAndRole()
// Act
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.TextMessageStartEvent);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("messageId", out JsonElement msgIdProp));
@@ -428,7 +428,7 @@ public void TextMessageContentEvent_Serializes_WithCorrectEventType()
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.TextMessageContentEvent);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
Assert.Equal(AGUIEventTypes.TextMessageContent, jsonElement.GetProperty("type").GetString());
}
@@ -440,7 +440,7 @@ public void TextMessageContentEvent_Includes_MessageIdAndDelta()
// Act
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.TextMessageContentEvent);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("messageId", out JsonElement msgIdProp));
@@ -496,7 +496,7 @@ public void TextMessageEndEvent_Serializes_WithCorrectEventType()
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.TextMessageEndEvent);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
Assert.Equal(AGUIEventTypes.TextMessageEnd, jsonElement.GetProperty("type").GetString());
}
@@ -508,7 +508,7 @@ public void TextMessageEndEvent_Includes_MessageId()
// Act
string json = JsonSerializer.Serialize(evt, AGUIJsonSerializerContext.Default.TextMessageEndEvent);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("messageId", out JsonElement msgIdProp));
@@ -557,7 +557,7 @@ public void AGUIMessage_Serializes_WithIdRoleAndContent()
// Act
string json = JsonSerializer.Serialize(message, AGUIJsonSerializerContext.Default.AGUIMessage);
- JsonElement jsonElement = JsonSerializer.Deserialize(json);
+ JsonElement jsonElement = JsonElement.Parse(json);
// Assert
Assert.True(jsonElement.TryGetProperty("id", out JsonElement idProp));
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AIToolExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AIToolExtensionsTests.cs
index 515695a8a6c..ebedd68f334 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AIToolExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/AIToolExtensionsTests.cs
@@ -87,7 +87,7 @@ public void AsAGUITools_FiltersOutNonAIFunctionTools()
// Arrange - mix of AIFunction and non-function tools
AIFunction function = AIFunctionFactory.Create(() => "Result", "TestTool");
// Create a custom AITool that's not an AIFunction
- var declaration = AIFunctionFactory.CreateDeclaration("DeclarationOnly", "Description", JsonDocument.Parse("{}").RootElement);
+ var declaration = AIFunctionFactory.CreateDeclaration("DeclarationOnly", "Description", JsonElement.Parse("{}"));
List tools = [function, declaration];
@@ -107,7 +107,7 @@ public void AsAITools_WithAGUITool_ConvertsToAIFunctionDeclarationCorrectly()
{
Name = "TestTool",
Description = "Test description",
- Parameters = JsonDocument.Parse("{\"type\":\"object\",\"properties\":{}}").RootElement
+ Parameters = JsonElement.Parse("""{"type":"object","properties":{}}""")
};
List aguiTools = [aguiTool];
@@ -116,7 +116,7 @@ public void AsAITools_WithAGUITool_ConvertsToAIFunctionDeclarationCorrectly()
// Assert
AITool tool = Assert.Single(tools);
- Assert.IsAssignableFrom(tool);
+ Assert.IsType(tool, exactMatch: false);
var declaration = (AIFunctionDeclaration)tool;
Assert.Equal("TestTool", declaration.Name);
Assert.Equal("Test description", declaration.Description);
@@ -128,9 +128,9 @@ public void AsAITools_WithMultipleAGUITools_ConvertsAllCorrectly()
// Arrange
List aguiTools =
[
- new AGUITool { Name = "Tool1", Description = "Desc1", Parameters = JsonDocument.Parse("{}").RootElement },
- new AGUITool { Name = "Tool2", Description = "Desc2", Parameters = JsonDocument.Parse("{}").RootElement },
- new AGUITool { Name = "Tool3", Description = "Desc3", Parameters = JsonDocument.Parse("{}").RootElement }
+ new AGUITool { Name = "Tool1", Description = "Desc1", Parameters = JsonElement.Parse("{}") },
+ new AGUITool { Name = "Tool2", Description = "Desc2", Parameters = JsonElement.Parse("{}") },
+ new AGUITool { Name = "Tool3", Description = "Desc3", Parameters = JsonElement.Parse("{}") }
];
// Act
@@ -138,7 +138,7 @@ public void AsAITools_WithMultipleAGUITools_ConvertsAllCorrectly()
// Assert
Assert.Equal(3, tools.Count);
- Assert.All(tools, t => Assert.IsAssignableFrom(t));
+ Assert.All(tools, t => Assert.IsType(t, exactMatch: false));
}
[Fact]
@@ -176,7 +176,7 @@ public void AsAITools_CreatesDeclarationsOnly_NotInvokableFunctions()
{
Name = "RemoteTool",
Description = "Tool implemented on server",
- Parameters = JsonDocument.Parse("{\"type\":\"object\"}").RootElement
+ Parameters = JsonElement.Parse("""{"type":"object"}""")
};
// Act
@@ -185,7 +185,7 @@ public void AsAITools_CreatesDeclarationsOnly_NotInvokableFunctions()
// Assert
// The tool should be a declaration, not an executable function
- Assert.IsAssignableFrom(tool);
+ Assert.IsType(tool, exactMatch: false);
// AIFunctionDeclaration cannot be invoked (no implementation)
// This is correct since the actual implementation exists on the client side
}
@@ -206,7 +206,7 @@ public void RoundTrip_AIFunctionToAGUIToolBackToDeclaration_PreservesMetadata()
AITool reconstructed = aguiToolsList.AsAITools().Single();
// Assert
- Assert.IsAssignableFrom(reconstructed);
+ Assert.IsType(reconstructed, exactMatch: false);
var declaration = (AIFunctionDeclaration)reconstructed;
Assert.Equal("FormatPerson", declaration.Name);
Assert.Equal("Formats person information", declaration.Description);
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/ChatResponseUpdateAGUIExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/ChatResponseUpdateAGUIExtensionsTests.cs
index 3f6df1eeebd..7d40cc014db 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/ChatResponseUpdateAGUIExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/ChatResponseUpdateAGUIExtensionsTests.cs
@@ -400,7 +400,7 @@ public async Task AsChatResponseUpdatesAsync_ConvertsStateSnapshotEvent_ToDataCo
// Verify the JSON content
string jsonText = System.Text.Encoding.UTF8.GetString(dataContent.Data.ToArray());
- JsonElement deserializedState = JsonSerializer.Deserialize(jsonText);
+ JsonElement deserializedState = JsonElement.Parse(jsonText);
Assert.Equal(42, deserializedState.GetProperty("counter").GetInt32());
Assert.Equal("active", deserializedState.GetProperty("status").GetString());
@@ -484,7 +484,7 @@ public async Task AsChatResponseUpdatesAsync_WithComplexStateSnapshot_PreservesJ
ChatResponseUpdate stateUpdate = updates.First();
DataContent dataContent = Assert.IsType(stateUpdate.Contents[0]);
string jsonText = System.Text.Encoding.UTF8.GetString(dataContent.Data.ToArray());
- JsonElement roundTrippedState = JsonSerializer.Deserialize(jsonText);
+ JsonElement roundTrippedState = JsonElement.Parse(jsonText);
Assert.Equal("Alice", roundTrippedState.GetProperty("user").GetProperty("name").GetString());
Assert.Equal(30, roundTrippedState.GetProperty("user").GetProperty("age").GetInt32());
@@ -555,7 +555,7 @@ public async Task AsChatResponseUpdatesAsync_ConvertsStateDeltaEvent_ToDataConte
// Verify the JSON Patch content
string jsonText = System.Text.Encoding.UTF8.GetString(dataContent.Data.ToArray());
- JsonElement deserializedDelta = JsonSerializer.Deserialize(jsonText);
+ JsonElement deserializedDelta = JsonElement.Parse(jsonText);
Assert.Equal(JsonValueKind.Array, deserializedDelta.ValueKind);
Assert.Equal(2, deserializedDelta.GetArrayLength());
diff --git a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj
index 96eff596881..0dab0aa9e41 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj
@@ -1,13 +1,6 @@
-
- $(ProjectsTargetFrameworks)
-
-
-
-
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs
index bfa14a89d44..5111a97ad1b 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIAgentTests.cs
@@ -8,7 +8,6 @@
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
using Moq;
-using Moq.Protected;
namespace Microsoft.Agents.AI.Abstractions.UnitTests;
@@ -222,21 +221,6 @@ public void ValidateAgentIDIsIdempotent()
Assert.Equal(id, agent.Id);
}
- [Fact]
- public async Task NotifyThreadOfNewMessagesNotifiesThreadAsync()
- {
- var cancellationToken = default(CancellationToken);
-
- var messages = new[] { new ChatMessage(ChatRole.User, "msg1"), new ChatMessage(ChatRole.User, "msg2") };
-
- var threadMock = new Mock { CallBase = true };
- threadMock.SetupAllProperties();
-
- await MockAgent.NotifyThreadOfNewMessagesAsync(threadMock.Object, messages, cancellationToken);
-
- threadMock.Protected().Verify("MessagesReceivedAsync", Times.Once(), messages, cancellationToken);
- }
-
#region GetService Method Tests
///
@@ -360,9 +344,6 @@ public abstract class TestAgentThread : AgentThread;
private sealed class MockAgent : AIAgent
{
- public static new Task NotifyThreadOfNewMessagesAsync(AgentThread thread, IEnumerable messages, CancellationToken cancellationToken) =>
- AIAgent.NotifyThreadOfNewMessagesAsync(thread, messages, cancellationToken);
-
public override AgentThread GetNewThread()
=> throw new NotImplementedException();
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs
index 0b8f41f1bb5..b287c8b304e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AIContextProviderTests.cs
@@ -2,7 +2,6 @@
using System;
using System.Collections.ObjectModel;
-using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
@@ -162,10 +161,5 @@ public override ValueTask InvokingAsync(InvokingContext context, Canc
{
return default;
}
-
- public override JsonElement Serialize(JsonSerializerOptions? jsonSerializerOptions = null)
- {
- return base.Serialize(jsonSerializerOptions);
- }
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs
index 32560949fb6..7460ea4623d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunOptionsTests.cs
@@ -17,7 +17,7 @@ public void CloningConstructorCopiesProperties()
// Arrange
var options = new AgentRunOptions
{
- ContinuationToken = new object(),
+ ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
AllowBackgroundResponses = true,
AdditionalProperties = new AdditionalPropertiesDictionary
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseUpdateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseUpdateTests.cs
index 42d3fdf199f..32b7acd6734 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseUpdateTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentRunResponseUpdateTests.cs
@@ -42,7 +42,7 @@ public void ConstructorWithChatResponseUpdateRoundtrips()
RawRepresentation = new object(),
ResponseId = "responseId",
Role = ChatRole.Assistant,
- ContinuationToken = new object(),
+ ContinuationToken = ResponseContinuationToken.FromBytes(new byte[] { 1, 2, 3 }),
};
AgentRunResponseUpdate response = new(chatResponseUpdate);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentThreadTests.cs
index 4d7c4ad219c..e75cb4caa1f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentThreadTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/AgentThreadTests.cs
@@ -1,8 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
using System;
-using System.Collections.Generic;
-using Microsoft.Extensions.AI;
#pragma warning disable CA1861 // Avoid constant arrays as arguments
@@ -21,15 +19,6 @@ public void Serialize_ReturnsDefaultJsonElement()
Assert.Equal(default, result);
}
- [Fact]
- public void MessagesReceivedAsync_ReturnsCompletedTask()
- {
- var thread = new TestAgentThread();
- var messages = new List { new(ChatRole.User, "hello") };
- var result = thread.MessagesReceivedAsync(messages);
- Assert.True(result.IsCompleted);
- }
-
#region GetService Method Tests
///
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj
index b7c5412a53f..1e5db6ed299 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj
@@ -1,7 +1,6 @@
- $(ProjectsTargetFrameworks)
$(NoWarn);MEAI001
@@ -13,9 +12,8 @@
-
-
-
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ServiceIdAgentThreadTests.cs b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ServiceIdAgentThreadTests.cs
index e451359c233..1da79344d49 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ServiceIdAgentThreadTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Abstractions.UnitTests/ServiceIdAgentThreadTests.cs
@@ -115,7 +115,5 @@ public TestServiceIdAgentThread(JsonElement serializedThreadState) : base(serial
}
// Helper class to represent empty objects
- internal sealed class EmptyObject
- {
- }
+ internal sealed class EmptyObject;
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Extensions/AnthropicBetaServiceExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Extensions/AnthropicBetaServiceExtensionsTests.cs
new file mode 100644
index 00000000000..400bcf54565
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Extensions/AnthropicBetaServiceExtensionsTests.cs
@@ -0,0 +1,290 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+#pragma warning disable IDE0052 // Remove unread private members
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Anthropic;
+using Anthropic.Core;
+using Anthropic.Services;
+using Microsoft.Extensions.AI;
+using Moq;
+using IBetaMessageService = Anthropic.Services.Beta.IMessageService;
+using IMessageService = Anthropic.Services.IMessageService;
+
+namespace Microsoft.Agents.AI.Anthropic.UnitTests.Extensions;
+
+///
+/// Unit tests for the AnthropicClientExtensions class.
+///
+public sealed class AnthropicBetaServiceExtensionsTests
+{
+ ///
+ /// Verify that CreateAIAgent with clientFactory parameter correctly applies the factory.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactory_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+ var testChatClient = new TestChatClient(chatClient.Beta.AsIChatClient());
+
+ // Act
+ var agent = chatClient.Beta.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ name: "Test Agent",
+ description: "Test description",
+ clientFactory: (innerClient) => testChatClient);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Test description", agent.Description);
+
+ // Verify that the custom chat client can be retrieved from the agent's service collection
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with clientFactory using AsBuilder pattern works correctly.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactoryUsingAsBuilder_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+ TestChatClient? testChatClient = null;
+
+ // Act
+ var agent = chatClient.Beta.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ clientFactory: (innerClient) =>
+ innerClient.AsBuilder().Use((innerClient) => testChatClient = new TestChatClient(innerClient)).Build());
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify that the custom chat client can be retrieved from the agent's service collection
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options and clientFactory parameter correctly applies the factory.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+ var testChatClient = new TestChatClient(chatClient.Beta.AsIChatClient());
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ Description = "Test description",
+ ChatOptions = new() { Instructions = "Test instructions" }
+ };
+
+ // Act
+ var agent = chatClient.Beta.CreateAIAgent(
+ options,
+ clientFactory: (innerClient) => testChatClient);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Test description", agent.Description);
+
+ // Verify that the custom chat client can be retrieved from the agent's service collection
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent without clientFactory works normally.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithoutClientFactory_WorksNormally()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+
+ // Act
+ var agent = chatClient.Beta.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ name: "Test Agent");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+
+ // Verify that no TestChatClient is available since no factory was provided
+ var retrievedTestClient = agent.GetService();
+ Assert.Null(retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with null clientFactory works normally.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithNullClientFactory_WorksNormally()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+
+ // Act
+ var agent = chatClient.Beta.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ name: "Test Agent",
+ clientFactory: null);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+
+ // Verify that no TestChatClient is available since no factory was provided
+ var retrievedTestClient = agent.GetService();
+ Assert.Null(retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent throws ArgumentNullException when client is null.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithNullClient_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ ((IBetaService)null!).CreateAIAgent("test-model"));
+
+ Assert.Equal("betaService", exception.ParamName);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options throws ArgumentNullException when options is null.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithNullOptions_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ chatClient.Beta.CreateAIAgent((ChatClientAgentOptions)null!));
+
+ Assert.Equal("options", exception.ParamName);
+ }
+
+ ///
+ /// Test custom chat client that can be used to verify clientFactory functionality.
+ ///
+ private sealed class TestChatClient : IChatClient
+ {
+ private readonly IChatClient _innerClient;
+
+ public TestChatClient(IChatClient innerClient)
+ {
+ this._innerClient = innerClient;
+ }
+
+ public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ => this._innerClient.GetResponseAsync(messages, options, cancellationToken);
+
+ public async IAsyncEnumerable GetStreamingResponseAsync(
+ IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ await foreach (var update in this._innerClient.GetStreamingResponseAsync(messages, options, cancellationToken))
+ {
+ yield return update;
+ }
+ }
+
+ public object? GetService(Type serviceType, object? serviceKey = null)
+ {
+ // Return this instance when requested
+ if (serviceType == typeof(TestChatClient))
+ {
+ return this;
+ }
+
+ return this._innerClient.GetService(serviceType, serviceKey);
+ }
+
+ public void Dispose() => this._innerClient.Dispose();
+ }
+
+ ///
+ /// Creates a test ChatClient implementation for testing.
+ ///
+ private sealed class TestAnthropicChatClient : IAnthropicClient
+ {
+ public TestAnthropicChatClient()
+ {
+ this.BetaService = new TestBetaService(this);
+ }
+
+ public HttpClient HttpClient { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public Uri BaseUrl { get => new("http://localhost"); init => throw new NotImplementedException(); }
+ public bool ResponseValidation { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public int? MaxRetries { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public TimeSpan? Timeout { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public string? APIKey { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public string? AuthToken { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+
+ public IMessageService Messages => throw new NotImplementedException();
+
+ public IModelService Models => throw new NotImplementedException();
+
+ public IBetaService Beta => this.BetaService;
+
+ public IBetaService BetaService { get; }
+
+ IMessageService IAnthropicClient.Messages => new Mock().Object;
+
+ public Task Execute(HttpRequest request, CancellationToken cancellationToken = default) where T : ParamsBase
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAnthropicClient WithOptions(Func modifier)
+ {
+ throw new NotImplementedException();
+ }
+
+ private sealed class TestBetaService : IBetaService
+ {
+ private readonly IAnthropicClient _client;
+
+ public TestBetaService(IAnthropicClient client)
+ {
+ this._client = client;
+ }
+
+ public global::Anthropic.Services.Beta.IModelService Models => throw new NotImplementedException();
+
+ public global::Anthropic.Services.Beta.IFileService Files => throw new NotImplementedException();
+
+ public global::Anthropic.Services.Beta.ISkillService Skills => throw new NotImplementedException();
+
+ public IBetaMessageService Messages => new Mock().Object;
+
+ public IBetaService WithOptions(Func modifier)
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Extensions/AnthropicClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Extensions/AnthropicClientExtensionsTests.cs
new file mode 100644
index 00000000000..c8bf4d6a5ea
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Extensions/AnthropicClientExtensionsTests.cs
@@ -0,0 +1,257 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Net.Http;
+using System.Runtime.CompilerServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Anthropic;
+using Anthropic.Core;
+using Anthropic.Services;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Anthropic.UnitTests.Extensions;
+
+///
+/// Unit tests for the AnthropicClientExtensions class.
+///
+public sealed class AnthropicClientExtensionsTests
+{
+ ///
+ /// Test custom chat client that can be used to verify clientFactory functionality.
+ ///
+ private sealed class TestChatClient : IChatClient
+ {
+ private readonly IChatClient _innerClient;
+
+ public TestChatClient(IChatClient innerClient)
+ {
+ this._innerClient = innerClient;
+ }
+
+ public Task GetResponseAsync(IEnumerable messages, ChatOptions? options = null, CancellationToken cancellationToken = default)
+ => this._innerClient.GetResponseAsync(messages, options, cancellationToken);
+
+ public async IAsyncEnumerable GetStreamingResponseAsync(
+ IEnumerable messages, ChatOptions? options = null, [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ await foreach (var update in this._innerClient.GetStreamingResponseAsync(messages, options, cancellationToken))
+ {
+ yield return update;
+ }
+ }
+
+ public object? GetService(Type serviceType, object? serviceKey = null)
+ {
+ // Return this instance when requested
+ if (serviceType == typeof(TestChatClient))
+ {
+ return this;
+ }
+
+ return this._innerClient.GetService(serviceType, serviceKey);
+ }
+
+ public void Dispose() => this._innerClient.Dispose();
+ }
+
+ ///
+ /// Creates a test ChatClient implementation for testing.
+ ///
+ private sealed class TestAnthropicChatClient : IAnthropicClient
+ {
+ public TestAnthropicChatClient()
+ {
+ }
+
+ public HttpClient HttpClient { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public Uri BaseUrl { get => new("http://localhost"); init => throw new NotImplementedException(); }
+ public bool ResponseValidation { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public int? MaxRetries { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public TimeSpan? Timeout { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public string? APIKey { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+ public string? AuthToken { get => throw new NotImplementedException(); init => throw new NotImplementedException(); }
+
+ public IMessageService Messages => throw new NotImplementedException();
+
+ public IModelService Models => throw new NotImplementedException();
+
+ public IBetaService Beta => throw new NotImplementedException();
+
+ public Task Execute(HttpRequest request, CancellationToken cancellationToken = default) where T : ParamsBase
+ {
+ throw new NotImplementedException();
+ }
+
+ public IAnthropicClient WithOptions(Func modifier)
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ ///
+ /// Verify that CreateAIAgent with clientFactory parameter correctly applies the factory.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactory_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+ var testChatClient = new TestChatClient(chatClient.AsIChatClient());
+
+ // Act
+ var agent = chatClient.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ name: "Test Agent",
+ description: "Test description",
+ clientFactory: (innerClient) => testChatClient);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Test description", agent.Description);
+
+ // Verify that the custom chat client can be retrieved from the agent's service collection
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with clientFactory using AsBuilder pattern works correctly.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactoryUsingAsBuilder_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+ TestChatClient? testChatClient = null;
+
+ // Act
+ var agent = chatClient.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ clientFactory: (innerClient) =>
+ innerClient.AsBuilder().Use((innerClient) => testChatClient = new TestChatClient(innerClient)).Build());
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify that the custom chat client can be retrieved from the agent's service collection
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options and clientFactory parameter correctly applies the factory.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+ var testChatClient = new TestChatClient(chatClient.AsIChatClient());
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ Description = "Test description",
+ ChatOptions = new() { Instructions = "Test instructions" }
+ };
+
+ // Act
+ var agent = chatClient.CreateAIAgent(
+ options,
+ clientFactory: (innerClient) => testChatClient);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Test description", agent.Description);
+
+ // Verify that the custom chat client can be retrieved from the agent's service collection
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent without clientFactory works normally.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithoutClientFactory_WorksNormally()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+
+ // Act
+ var agent = chatClient.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ name: "Test Agent");
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+
+ // Verify that no TestChatClient is available since no factory was provided
+ var retrievedTestClient = agent.GetService();
+ Assert.Null(retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with null clientFactory works normally.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithNullClientFactory_WorksNormally()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+
+ // Act
+ var agent = chatClient.CreateAIAgent(
+ model: "test-model",
+ instructions: "Test instructions",
+ name: "Test Agent",
+ clientFactory: null);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+
+ // Verify that no TestChatClient is available since no factory was provided
+ var retrievedTestClient = agent.GetService();
+ Assert.Null(retrievedTestClient);
+ }
+
+ ///
+ /// Verify that CreateAIAgent throws ArgumentNullException when client is null.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithNullClient_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ ((TestAnthropicChatClient)null!).CreateAIAgent("test-model"));
+
+ Assert.Equal("client", exception.ParamName);
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options throws ArgumentNullException when options is null.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithNullOptions_ThrowsArgumentNullException()
+ {
+ // Arrange
+ var chatClient = new TestAnthropicChatClient();
+
+ // Act & Assert
+ var exception = Assert.Throws(() =>
+ chatClient.CreateAIAgent((ChatClientAgentOptions)null!));
+
+ Assert.Equal("options", exception.ParamName);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj
new file mode 100644
index 00000000000..291c56f8792
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj
@@ -0,0 +1,11 @@
+
+
+
+ true
+
+
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs
index 56b89d2df87..b661a392be3 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Extensions/PersistentAgentsClientExtensionsTests.cs
@@ -5,6 +5,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Azure;
@@ -309,7 +310,7 @@ public void GetAIAgent_WithResponseAndOptions_WorksCorrectly()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -336,7 +337,7 @@ public void GetAIAgent_WithPersistentAgentAndOptions_WorksCorrectly()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -385,7 +386,7 @@ public void GetAIAgent_WithAgentIdAndOptions_WorksCorrectly()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -412,7 +413,7 @@ public async Task GetAIAgentAsync_WithAgentIdAndOptions_WorksCorrectlyAsync()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -556,7 +557,7 @@ public void CreateAIAgent_WithOptions_WorksCorrectly()
{
Name = "Test Agent",
Description = "Test description",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -583,7 +584,7 @@ public async Task CreateAIAgentAsync_WithOptions_WorksCorrectlyAsync()
{
Name = "Test Agent",
Description = "Test description",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -726,6 +727,159 @@ public async Task CreateAIAgentAsync_WithEmptyModel_ThrowsArgumentExceptionAsync
Assert.Equal("model", exception.ParamName);
}
+ ///
+ /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var client = CreateFakePersistentAgentsClient();
+ var serviceProvider = new TestServiceProvider();
+ const string Model = "test-model";
+
+ // Act
+ var agent = client.CreateAIAgent(
+ Model,
+ instructions: "Test instructions",
+ name: "Test Agent",
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that CreateAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public async Task CreateAIAgentAsync_WithServices_PassesServicesToAgentAsync()
+ {
+ // Arrange
+ var client = CreateFakePersistentAgentsClient();
+ var serviceProvider = new TestServiceProvider();
+ const string Model = "test-model";
+
+ // Act
+ var agent = await client.CreateAIAgentAsync(
+ Model,
+ instructions: "Test instructions",
+ name: "Test Agent",
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that GetAIAgent with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void GetAIAgent_WithServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var client = CreateFakePersistentAgentsClient();
+ var serviceProvider = new TestServiceProvider();
+
+ // Act
+ var agent = client.GetAIAgent("agent_abc123", services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithServices_PassesServicesToAgentAsync()
+ {
+ // Arrange
+ var client = CreateFakePersistentAgentsClient();
+ var serviceProvider = new TestServiceProvider();
+
+ // Act
+ var agent = await client.GetAIAgentAsync("agent_abc123", services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that CreateAIAgent with both clientFactory and services works correctly.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactoryAndServices_AppliesBothCorrectly()
+ {
+ // Arrange
+ var client = CreateFakePersistentAgentsClient();
+ var serviceProvider = new TestServiceProvider();
+ TestChatClient? testChatClient = null;
+ const string Model = "test-model";
+
+ // Act
+ var agent = client.CreateAIAgent(
+ Model,
+ instructions: "Test instructions",
+ name: "Test Agent",
+ clientFactory: (innerClient) => testChatClient = new TestChatClient(innerClient),
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the custom chat client was applied
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+
+ // Verify the IServiceProvider was passed through
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Uses reflection to access the FunctionInvocationServices property which is not public.
+ ///
+ private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client)
+ {
+ var property = typeof(FunctionInvokingChatClient).GetProperty(
+ "FunctionInvocationServices",
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ return property?.GetValue(client) as IServiceProvider;
+ }
+
///
/// Test custom chat client that can be used to verify clientFactory functionality.
///
@@ -736,6 +890,14 @@ public TestChatClient(IChatClient innerClient) : base(innerClient)
}
}
+ ///
+ /// A simple test IServiceProvider implementation for testing.
+ ///
+ private sealed class TestServiceProvider : IServiceProvider
+ {
+ public object? GetService(Type serviceType) => null;
+ }
+
public sealed class FakePersistentAgentsAdministrationClient : PersistentAgentsAdministrationClient
{
public FakePersistentAgentsAdministrationClient()
@@ -761,7 +923,7 @@ private static PersistentAgentsClient CreateFakePersistentAgentsClient()
{
var client = new PersistentAgentsClient("https://any.com", DelegatedTokenCredential.Create((_, _) => new AccessToken()));
- ((System.Reflection.TypeInfo)typeof(PersistentAgentsClient)).DeclaredFields.First(f => f.Name == "_client")
+ ((TypeInfo)typeof(PersistentAgentsClient)).DeclaredFields.First(f => f.Name == "_client")
.SetValue(client, new FakePersistentAgentsAdministrationClient());
return client;
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj
index 80c00866750..ca33d52d6bc 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj
@@ -1,9 +1,5 @@
-
- $(ProjectsTargetFrameworks)
-
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
index ede9b379191..1136d2b1f48 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientExtensionsTests.cs
@@ -752,7 +752,7 @@ public void CreateAIAgent_WithModelAndOptions_CreatesValidAgent()
var options = new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -775,7 +775,7 @@ public void CreateAIAgent_WithModelAndOptions_WithClientFactory_AppliesFactoryCo
var options = new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
TestChatClient? testChatClient = null;
@@ -803,7 +803,7 @@ public async Task CreateAIAgentAsync_WithModelAndOptions_CreatesValidAgentAsync(
var options = new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -826,7 +826,7 @@ public async Task CreateAIAgentAsync_WithModelAndOptions_WithClientFactory_Appli
var options = new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
TestChatClient? testChatClient = null;
@@ -1575,8 +1575,8 @@ public void GetAIAgent_WithOptions_PreservesCustomProperties()
var options = new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Custom instructions",
- Description = "Custom description"
+ Description = "Custom description",
+ ChatOptions = new ChatOptions { Instructions = "Custom instructions" }
};
// Act
@@ -1610,8 +1610,7 @@ public void CreateAIAgent_WithOptionsAndTools_GeneratesCorrectOptions()
var options = new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test",
- ChatOptions = new ChatOptions { Tools = tools }
+ ChatOptions = new ChatOptions { Instructions = "Test", Tools = tools }
};
// Act
@@ -2736,7 +2735,7 @@ public override bool TryGetValues(string name, out IEnumerable? values)
{
if (this._headers.TryGetValue(name, out var value))
{
- values = new[] { value };
+ values = [value];
return true;
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs
index 647beb4451b..eee9f520b62 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/AzureAIProjectChatClientTests.cs
@@ -49,8 +49,7 @@ public async Task ChatClient_UsesDefaultConversationIdAsync()
new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions",
- ChatOptions = new() { ConversationId = "conv_12345" }
+ ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_12345" }
});
// Act
@@ -99,7 +98,7 @@ public async Task ChatClient_UsesPerRequestConversationId_WhenNoDefaultConversat
new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions",
+ ChatOptions = new() { Instructions = "Test instructions" },
});
// Act
@@ -148,8 +147,7 @@ public async Task ChatClient_UsesPerRequestConversationId_EvenWhenDefaultConvers
new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions",
- ChatOptions = new() { ConversationId = "conv_should_not_use_default" }
+ ChatOptions = new() { Instructions = "Test instructions", ConversationId = "conv_should_not_use_default" }
});
// Act
@@ -198,7 +196,7 @@ public async Task ChatClient_UsesPreviousResponseId_WhenConversationIsNotPrefixe
new ChatClientAgentOptions
{
Name = "test-agent",
- Instructions = "Test instructions",
+ ChatOptions = new() { Instructions = "Test instructions" },
});
// Act
diff --git a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj
index 79bc5776615..193a7d47daa 100644
--- a/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj
@@ -1,9 +1,5 @@
-
- $(ProjectsTargetFrameworks)
-
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/.editorconfig b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/.editorconfig
new file mode 100644
index 00000000000..83e05f582a6
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/.editorconfig
@@ -0,0 +1,9 @@
+# EditorConfig overrides for Cosmos DB Unit Tests
+# Multi-targeting (net472 + net9.0) causes false positives for IDE0005 (unnecessary using directives)
+
+root = false
+
+[*.cs]
+# Suppress IDE0005 for this project - multi-targeting causes false positives
+# These using directives ARE necessary but appear unnecessary in one target framework
+dotnet_diagnostic.IDE0005.severity = none
diff --git a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatMessageStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatMessageStoreTests.cs
new file mode 100644
index 00000000000..6f2a256206e
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosChatMessageStoreTests.cs
@@ -0,0 +1,760 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text.Json;
+using System.Text.Json.Serialization.Metadata;
+using System.Threading.Tasks;
+using Azure.Core;
+using Azure.Identity;
+using Microsoft.Agents.AI;
+using Microsoft.Azure.Cosmos;
+using Microsoft.Extensions.AI;
+using Xunit;
+
+namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests;
+
+///
+/// Contains tests for .
+///
+/// Test Modes:
+/// - Default Mode: Cleans up all test data after each test run (deletes database)
+/// - Preserve Mode: Keeps containers and data for inspection in Cosmos DB Emulator Data Explorer
+///
+/// To enable Preserve Mode, set environment variable: COSMOS_PRESERVE_CONTAINERS=true
+/// Example: $env:COSMOS_PRESERVE_CONTAINERS="true"; dotnet test
+///
+/// In Preserve Mode, you can view the data in Cosmos DB Emulator Data Explorer at:
+/// https://localhost:8081/_explorer/index.html
+/// Database: AgentFrameworkTests
+/// Container: ChatMessages
+///
+/// Environment Variable Reference:
+/// | Variable | Values | Description |
+/// |----------|--------|-------------|
+/// | COSMOS_PRESERVE_CONTAINERS | true / false | Controls whether to preserve test data after completion |
+///
+/// Usage Examples:
+/// - Run all tests in preserve mode: $env:COSMOS_PRESERVE_CONTAINERS="true"; dotnet test tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/
+/// - Run specific test category in preserve mode: $env:COSMOS_PRESERVE_CONTAINERS="true"; dotnet test tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/ --filter "Category=CosmosDB"
+/// - Reset to cleanup mode: $env:COSMOS_PRESERVE_CONTAINERS=""; dotnet test tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/
+///
+[Collection("CosmosDB")]
+public sealed class CosmosChatMessageStoreTests : IAsyncLifetime, IDisposable
+{
+ // Cosmos DB Emulator connection settings
+ private const string EmulatorEndpoint = "https://localhost:8081";
+ private const string EmulatorKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
+ private const string TestContainerId = "ChatMessages";
+ private const string HierarchicalTestContainerId = "HierarchicalChatMessages";
+ // Use unique database ID per test class instance to avoid conflicts
+#pragma warning disable CA1802 // Use literals where appropriate
+ private static readonly string s_testDatabaseId = $"AgentFrameworkTests-ChatStore-{Guid.NewGuid():N}";
+#pragma warning restore CA1802
+
+ private string _connectionString = string.Empty;
+ private bool _emulatorAvailable;
+ private bool _preserveContainer;
+ private CosmosClient? _setupClient; // Only used for test setup/cleanup
+
+ public async Task InitializeAsync()
+ {
+ // Check environment variable to determine if we should preserve containers
+ // Set COSMOS_PRESERVE_CONTAINERS=true to keep containers and data for inspection
+ this._preserveContainer = string.Equals(Environment.GetEnvironmentVariable("COSMOS_PRESERVE_CONTAINERS"), "true", StringComparison.OrdinalIgnoreCase);
+
+ this._connectionString = $"AccountEndpoint={EmulatorEndpoint};AccountKey={EmulatorKey}";
+
+ try
+ {
+ // Only create CosmosClient for test setup - the actual tests will use connection string constructors
+ this._setupClient = new CosmosClient(EmulatorEndpoint, EmulatorKey);
+
+ // Test connection by attempting to create database
+ var databaseResponse = await this._setupClient.CreateDatabaseIfNotExistsAsync(s_testDatabaseId);
+
+ // Create container for simple partitioning tests
+ await databaseResponse.Database.CreateContainerIfNotExistsAsync(
+ TestContainerId,
+ "/conversationId",
+ throughput: 400);
+
+ // Create container for hierarchical partitioning tests with hierarchical partition key
+ var hierarchicalContainerProperties = new ContainerProperties(HierarchicalTestContainerId, new List { "/tenantId", "/userId", "/sessionId" });
+ await databaseResponse.Database.CreateContainerIfNotExistsAsync(
+ hierarchicalContainerProperties,
+ throughput: 400);
+
+ this._emulatorAvailable = true;
+ }
+ catch (Exception)
+ {
+ // Emulator not available, tests will be skipped
+ this._emulatorAvailable = false;
+ this._setupClient?.Dispose();
+ this._setupClient = null;
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (this._setupClient != null && this._emulatorAvailable)
+ {
+ try
+ {
+ if (this._preserveContainer)
+ {
+ // Preserve mode: Don't delete the database/container, keep data for inspection
+ // This allows viewing data in the Cosmos DB Emulator Data Explorer
+ // No cleanup needed - data persists for debugging
+ }
+ else
+ {
+ // Clean mode: Delete the test database and all data
+ var database = this._setupClient.GetDatabase(s_testDatabaseId);
+ await database.DeleteAsync();
+ }
+ }
+ catch (Exception ex)
+ {
+ // Ignore cleanup errors during test teardown
+ Console.WriteLine($"Warning: Cleanup failed: {ex.Message}");
+ }
+ finally
+ {
+ this._setupClient.Dispose();
+ }
+ }
+ }
+
+ public void Dispose()
+ {
+ this._setupClient?.Dispose();
+ GC.SuppressFinalize(this);
+ }
+
+ private void SkipIfEmulatorNotAvailable()
+ {
+ // In CI: Skip if COSMOS_EMULATOR_AVAILABLE is not set to "true"
+ // Locally: Skip if emulator connection check failed
+ var ciEmulatorAvailable = string.Equals(Environment.GetEnvironmentVariable("COSMOS_EMULATOR_AVAILABLE"), "true", StringComparison.OrdinalIgnoreCase);
+
+ Xunit.Skip.If(!ciEmulatorAvailable && !this._emulatorAvailable, "Cosmos DB Emulator is not available");
+ }
+
+ #region Constructor Tests
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithConnectionString_ShouldCreateInstance()
+ {
+ // Arrange & Act
+ this.SkipIfEmulatorNotAvailable();
+
+ // Act
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, "test-conversation");
+
+ // Assert
+ Assert.NotNull(store);
+ Assert.Equal("test-conversation", store.ConversationId);
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(TestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithConnectionStringNoConversationId_ShouldCreateInstance()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+
+ // Act
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId);
+
+ // Assert
+ Assert.NotNull(store);
+ Assert.NotNull(store.ConversationId);
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(TestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithNullConnectionString_ShouldThrowArgumentException()
+ {
+ // Arrange & Act & Assert
+ Assert.Throws(() =>
+ new CosmosChatMessageStore((string)null!, s_testDatabaseId, TestContainerId, "test-conversation"));
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithEmptyConversationId_ShouldThrowArgumentException()
+ {
+ // Arrange & Act & Assert
+ this.SkipIfEmulatorNotAvailable();
+
+ Assert.Throws(() =>
+ new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, ""));
+ }
+
+ #endregion
+
+ #region AddMessagesAsync Tests
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task AddMessagesAsync_WithSingleMessage_ShouldAddMessageAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ var conversationId = Guid.NewGuid().ToString();
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId);
+ var message = new ChatMessage(ChatRole.User, "Hello, world!");
+
+ // Act
+ await store.AddMessagesAsync([message]);
+
+ // Wait a moment for eventual consistency
+ await Task.Delay(100);
+
+ // Assert
+ var messages = await store.GetMessagesAsync();
+ var messageList = messages.ToList();
+
+ // Simple assertion - if this fails, we know the deserialization is the issue
+ if (messageList.Count == 0)
+ {
+ // Let's check if we can find ANY items in the container for this conversation
+ var directQuery = new QueryDefinition("SELECT VALUE COUNT(1) FROM c WHERE c.conversationId = @conversationId")
+ .WithParameter("@conversationId", conversationId);
+ var countIterator = this._setupClient!.GetDatabase(s_testDatabaseId).GetContainer(TestContainerId)
+ .GetItemQueryIterator(directQuery, requestOptions: new QueryRequestOptions
+ {
+ PartitionKey = new PartitionKey(conversationId)
+ });
+
+ var countResponse = await countIterator.ReadNextAsync();
+ var count = countResponse.FirstOrDefault();
+
+ // Debug: Let's see what the raw query returns
+ var rawQuery = new QueryDefinition("SELECT * FROM c WHERE c.conversationId = @conversationId")
+ .WithParameter("@conversationId", conversationId);
+ var rawIterator = this._setupClient!.GetDatabase(s_testDatabaseId).GetContainer(TestContainerId)
+ .GetItemQueryIterator(rawQuery, requestOptions: new QueryRequestOptions
+ {
+ PartitionKey = new PartitionKey(conversationId)
+ });
+
+ List rawResults = new();
+ while (rawIterator.HasMoreResults)
+ {
+ var rawResponse = await rawIterator.ReadNextAsync();
+ rawResults.AddRange(rawResponse);
+ }
+
+ string rawJson = rawResults.Count > 0 ? Newtonsoft.Json.JsonConvert.SerializeObject(rawResults[0], Newtonsoft.Json.Formatting.Indented) : "null";
+ Assert.Fail($"GetMessagesAsync returned 0 messages, but direct count query found {count} items for conversation {conversationId}. Raw document: {rawJson}");
+ }
+
+ Assert.Single(messageList);
+ Assert.Equal("Hello, world!", messageList[0].Text);
+ Assert.Equal(ChatRole.User, messageList[0].Role);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task AddMessagesAsync_WithMultipleMessages_ShouldAddAllMessagesAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ var conversationId = Guid.NewGuid().ToString();
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId);
+ var messages = new[]
+ {
+ new ChatMessage(ChatRole.User, "First message"),
+ new ChatMessage(ChatRole.Assistant, "Second message"),
+ new ChatMessage(ChatRole.User, "Third message")
+ };
+
+ // Act
+ await store.AddMessagesAsync(messages);
+
+ // Assert
+ var retrievedMessages = await store.GetMessagesAsync();
+ var messageList = retrievedMessages.ToList();
+ Assert.Equal(3, messageList.Count);
+ Assert.Equal("First message", messageList[0].Text);
+ Assert.Equal("Second message", messageList[1].Text);
+ Assert.Equal("Third message", messageList[2].Text);
+ }
+
+ #endregion
+
+ #region GetMessagesAsync Tests
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task GetMessagesAsync_WithNoMessages_ShouldReturnEmptyAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString());
+
+ // Act
+ var messages = await store.GetMessagesAsync();
+
+ // Assert
+ Assert.Empty(messages);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task GetMessagesAsync_WithConversationIsolation_ShouldOnlyReturnMessagesForConversationAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ var conversation1 = Guid.NewGuid().ToString();
+ var conversation2 = Guid.NewGuid().ToString();
+
+ using var store1 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversation1);
+ using var store2 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversation2);
+
+ await store1.AddMessagesAsync([new ChatMessage(ChatRole.User, "Message for conversation 1")]);
+ await store2.AddMessagesAsync([new ChatMessage(ChatRole.User, "Message for conversation 2")]);
+
+ // Act
+ var messages1 = await store1.GetMessagesAsync();
+ var messages2 = await store2.GetMessagesAsync();
+
+ // Assert
+ var messageList1 = messages1.ToList();
+ var messageList2 = messages2.ToList();
+ Assert.Single(messageList1);
+ Assert.Single(messageList2);
+ Assert.Equal("Message for conversation 1", messageList1[0].Text);
+ Assert.Equal("Message for conversation 2", messageList2[0].Text);
+ }
+
+ #endregion
+
+ #region Integration Tests
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task FullWorkflow_AddAndGet_ShouldWorkCorrectlyAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ var conversationId = $"test-conversation-{Guid.NewGuid():N}"; // Use unique conversation ID
+ using var originalStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId);
+
+ var messages = new[]
+ {
+ new ChatMessage(ChatRole.System, "You are a helpful assistant."),
+ new ChatMessage(ChatRole.User, "Hello!"),
+ new ChatMessage(ChatRole.Assistant, "Hi there! How can I help you today?"),
+ new ChatMessage(ChatRole.User, "What's the weather like?"),
+ new ChatMessage(ChatRole.Assistant, "I'm sorry, I don't have access to current weather data.")
+ };
+
+ // Act 1: Add messages
+ await originalStore.AddMessagesAsync(messages);
+
+ // Act 2: Verify messages were added
+ var retrievedMessages = await originalStore.GetMessagesAsync();
+ var retrievedList = retrievedMessages.ToList();
+ Assert.Equal(5, retrievedList.Count);
+
+ // Act 3: Create new store instance for same conversation (test persistence)
+ using var newStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, conversationId);
+ var persistedMessages = await newStore.GetMessagesAsync();
+ var persistedList = persistedMessages.ToList();
+
+ // Assert final state
+ Assert.Equal(5, persistedList.Count);
+ Assert.Equal("You are a helpful assistant.", persistedList[0].Text);
+ Assert.Equal("Hello!", persistedList[1].Text);
+ Assert.Equal("Hi there! How can I help you today?", persistedList[2].Text);
+ Assert.Equal("What's the weather like?", persistedList[3].Text);
+ Assert.Equal("I'm sorry, I don't have access to current weather data.", persistedList[4].Text);
+ }
+
+ #endregion
+
+ #region Disposal Tests
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Dispose_AfterUse_ShouldNotThrow()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString());
+
+ // Act & Assert
+ store.Dispose(); // Should not throw
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Dispose_MultipleCalls_ShouldNotThrow()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, Guid.NewGuid().ToString());
+
+ // Act & Assert
+ store.Dispose(); // First call
+ store.Dispose(); // Second call - should not throw
+ }
+
+ #endregion
+
+ #region Hierarchical Partitioning Tests
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithHierarchicalConnectionString_ShouldCreateInstance()
+ {
+ // Arrange & Act
+ this.SkipIfEmulatorNotAvailable();
+
+ // Act
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789");
+
+ // Assert
+ Assert.NotNull(store);
+ Assert.Equal("session-789", store.ConversationId);
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(HierarchicalTestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithHierarchicalEndpoint_ShouldCreateInstance()
+ {
+ // Arrange & Act
+ this.SkipIfEmulatorNotAvailable();
+
+ // Act
+ TokenCredential credential = new DefaultAzureCredential();
+ using var store = new CosmosChatMessageStore(EmulatorEndpoint, credential, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789");
+
+ // Assert
+ Assert.NotNull(store);
+ Assert.Equal("session-789", store.ConversationId);
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(HierarchicalTestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithHierarchicalCosmosClient_ShouldCreateInstance()
+ {
+ // Arrange & Act
+ this.SkipIfEmulatorNotAvailable();
+
+ using var cosmosClient = new CosmosClient(EmulatorEndpoint, EmulatorKey);
+ using var store = new CosmosChatMessageStore(cosmosClient, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", "session-789");
+
+ // Assert
+ Assert.NotNull(store);
+ Assert.Equal("session-789", store.ConversationId);
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(HierarchicalTestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithHierarchicalNullTenantId_ShouldThrowArgumentException()
+ {
+ // Arrange & Act & Assert
+ this.SkipIfEmulatorNotAvailable();
+
+ Assert.Throws(() =>
+ new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, null!, "user-456", "session-789"));
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithHierarchicalEmptyUserId_ShouldThrowArgumentException()
+ {
+ // Arrange & Act & Assert
+ this.SkipIfEmulatorNotAvailable();
+
+ Assert.Throws(() =>
+ new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "", "session-789"));
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public void Constructor_WithHierarchicalWhitespaceSessionId_ShouldThrowArgumentException()
+ {
+ // Arrange & Act & Assert
+ this.SkipIfEmulatorNotAvailable();
+
+ Assert.Throws(() =>
+ new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-123", "user-456", " "));
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task AddMessagesAsync_WithHierarchicalPartitioning_ShouldAddMessageWithMetadataAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string TenantId = "tenant-123";
+ const string UserId = "user-456";
+ const string SessionId = "session-789";
+ // Test hierarchical partitioning constructor with connection string
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId);
+ var message = new ChatMessage(ChatRole.User, "Hello from hierarchical partitioning!");
+
+ // Act
+ await store.AddMessagesAsync([message]);
+
+ // Wait a moment for eventual consistency
+ await Task.Delay(100);
+
+ // Assert
+ var messages = await store.GetMessagesAsync();
+ var messageList = messages.ToList();
+
+ Assert.Single(messageList);
+ Assert.Equal("Hello from hierarchical partitioning!", messageList[0].Text);
+ Assert.Equal(ChatRole.User, messageList[0].Role);
+
+ // Verify that the document is stored with hierarchical partitioning metadata
+ var directQuery = new QueryDefinition("SELECT * FROM c WHERE c.conversationId = @conversationId AND c.type = @type")
+ .WithParameter("@conversationId", SessionId)
+ .WithParameter("@type", "ChatMessage");
+
+ var iterator = this._setupClient!.GetDatabase(s_testDatabaseId).GetContainer(HierarchicalTestContainerId)
+ .GetItemQueryIterator(directQuery, requestOptions: new QueryRequestOptions
+ {
+ PartitionKey = new PartitionKeyBuilder().Add(TenantId).Add(UserId).Add(SessionId).Build()
+ });
+
+ var response = await iterator.ReadNextAsync();
+ var document = response.FirstOrDefault();
+
+ Assert.NotNull(document);
+ // The document should have hierarchical metadata
+ Assert.Equal(SessionId, (string)document!.conversationId);
+ Assert.Equal(TenantId, (string)document!.tenantId);
+ Assert.Equal(UserId, (string)document!.userId);
+ Assert.Equal(SessionId, (string)document!.sessionId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task AddMessagesAsync_WithHierarchicalMultipleMessages_ShouldAddAllMessagesAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string TenantId = "tenant-batch";
+ const string UserId = "user-batch";
+ const string SessionId = "session-batch";
+ // Test hierarchical partitioning constructor with connection string
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId);
+ var messages = new[]
+ {
+ new ChatMessage(ChatRole.User, "First hierarchical message"),
+ new ChatMessage(ChatRole.Assistant, "Second hierarchical message"),
+ new ChatMessage(ChatRole.User, "Third hierarchical message")
+ };
+
+ // Act
+ await store.AddMessagesAsync(messages);
+
+ // Wait a moment for eventual consistency
+ await Task.Delay(100);
+
+ // Assert
+ var retrievedMessages = await store.GetMessagesAsync();
+ var messageList = retrievedMessages.ToList();
+
+ Assert.Equal(3, messageList.Count);
+ Assert.Equal("First hierarchical message", messageList[0].Text);
+ Assert.Equal("Second hierarchical message", messageList[1].Text);
+ Assert.Equal("Third hierarchical message", messageList[2].Text);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task GetMessagesAsync_WithHierarchicalPartitionIsolation_ShouldIsolateMessagesByUserIdAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string TenantId = "tenant-isolation";
+ const string UserId1 = "user-1";
+ const string UserId2 = "user-2";
+ const string SessionId = "session-isolation";
+
+ // Different userIds create different hierarchical partitions, providing proper isolation
+ using var store1 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId1, SessionId);
+ using var store2 = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId2, SessionId);
+
+ // Add messages to both stores
+ await store1.AddMessagesAsync([new ChatMessage(ChatRole.User, "Message from user 1")]);
+ await store2.AddMessagesAsync([new ChatMessage(ChatRole.User, "Message from user 2")]);
+
+ // Wait a moment for eventual consistency
+ await Task.Delay(100);
+
+ // Act & Assert
+ var messages1 = await store1.GetMessagesAsync();
+ var messageList1 = messages1.ToList();
+
+ var messages2 = await store2.GetMessagesAsync();
+ var messageList2 = messages2.ToList();
+
+ // With true hierarchical partitioning, each user sees only their own messages
+ Assert.Single(messageList1);
+ Assert.Single(messageList2);
+ Assert.Equal("Message from user 1", messageList1[0].Text);
+ Assert.Equal("Message from user 2", messageList2[0].Text);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task SerializeDeserialize_WithHierarchicalPartitioning_ShouldPreserveStateAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string TenantId = "tenant-serialize";
+ const string UserId = "user-serialize";
+ const string SessionId = "session-serialize";
+
+ using var originalStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, TenantId, UserId, SessionId);
+ await originalStore.AddMessagesAsync([new ChatMessage(ChatRole.User, "Test serialization message")]);
+
+ // Act - Serialize the store state
+ var serializedState = originalStore.Serialize();
+
+ // Create a new store from the serialized state
+ using var cosmosClient = new CosmosClient(EmulatorEndpoint, EmulatorKey);
+ var serializerOptions = new JsonSerializerOptions
+ {
+ TypeInfoResolver = new DefaultJsonTypeInfoResolver()
+ };
+ using var deserializedStore = CosmosChatMessageStore.CreateFromSerializedState(cosmosClient, serializedState, s_testDatabaseId, HierarchicalTestContainerId, serializerOptions);
+
+ // Wait a moment for eventual consistency
+ await Task.Delay(100);
+
+ // Assert - The deserialized store should have the same functionality
+ var messages = await deserializedStore.GetMessagesAsync();
+ var messageList = messages.ToList();
+
+ Assert.Single(messageList);
+ Assert.Equal("Test serialization message", messageList[0].Text);
+ Assert.Equal(SessionId, deserializedStore.ConversationId);
+ Assert.Equal(s_testDatabaseId, deserializedStore.DatabaseId);
+ Assert.Equal(HierarchicalTestContainerId, deserializedStore.ContainerId);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task HierarchicalAndSimplePartitioning_ShouldCoexistAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string SessionId = "coexist-session";
+
+ // Create simple store using simple partitioning container and hierarchical store using hierarchical container
+ using var simpleStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, SessionId);
+ using var hierarchicalStore = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, HierarchicalTestContainerId, "tenant-coexist", "user-coexist", SessionId);
+
+ // Add messages to both
+ await simpleStore.AddMessagesAsync([new ChatMessage(ChatRole.User, "Simple partitioning message")]);
+ await hierarchicalStore.AddMessagesAsync([new ChatMessage(ChatRole.User, "Hierarchical partitioning message")]);
+
+ // Wait a moment for eventual consistency
+ await Task.Delay(100);
+
+ // Act & Assert
+ var simpleMessages = await simpleStore.GetMessagesAsync();
+ var simpleMessageList = simpleMessages.ToList();
+
+ var hierarchicalMessages = await hierarchicalStore.GetMessagesAsync();
+ var hierarchicalMessageList = hierarchicalMessages.ToList();
+
+ // Each should only see its own messages since they use different containers
+ Assert.Single(simpleMessageList);
+ Assert.Single(hierarchicalMessageList);
+ Assert.Equal("Simple partitioning message", simpleMessageList[0].Text);
+ Assert.Equal("Hierarchical partitioning message", hierarchicalMessageList[0].Text);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task MaxMessagesToRetrieve_ShouldLimitAndReturnMostRecentAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string ConversationId = "max-messages-test";
+
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, ConversationId);
+
+ // Add 10 messages
+ var messages = new List();
+ for (int i = 1; i <= 10; i++)
+ {
+ messages.Add(new ChatMessage(ChatRole.User, $"Message {i}"));
+ await Task.Delay(10); // Small delay to ensure different timestamps
+ }
+ await store.AddMessagesAsync(messages);
+
+ // Wait for eventual consistency
+ await Task.Delay(100);
+
+ // Act - Set max to 5 and retrieve
+ store.MaxMessagesToRetrieve = 5;
+ var retrievedMessages = await store.GetMessagesAsync();
+ var messageList = retrievedMessages.ToList();
+
+ // Assert - Should get the 5 most recent messages (6-10) in ascending order
+ Assert.Equal(5, messageList.Count);
+ Assert.Equal("Message 6", messageList[0].Text);
+ Assert.Equal("Message 7", messageList[1].Text);
+ Assert.Equal("Message 8", messageList[2].Text);
+ Assert.Equal("Message 9", messageList[3].Text);
+ Assert.Equal("Message 10", messageList[4].Text);
+ }
+
+ [SkippableFact]
+ [Trait("Category", "CosmosDB")]
+ public async Task MaxMessagesToRetrieve_Null_ShouldReturnAllMessagesAsync()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+ const string ConversationId = "max-messages-null-test";
+
+ using var store = new CosmosChatMessageStore(this._connectionString, s_testDatabaseId, TestContainerId, ConversationId);
+
+ // Add 10 messages
+ var messages = new List();
+ for (int i = 1; i <= 10; i++)
+ {
+ messages.Add(new ChatMessage(ChatRole.User, $"Message {i}"));
+ }
+ await store.AddMessagesAsync(messages);
+
+ // Wait for eventual consistency
+ await Task.Delay(100);
+
+ // Act - No limit set (default null)
+ var retrievedMessages = await store.GetMessagesAsync();
+ var messageList = retrievedMessages.ToList();
+
+ // Assert - Should get all 10 messages
+ Assert.Equal(10, messageList.Count);
+ Assert.Equal("Message 1", messageList[0].Text);
+ Assert.Equal("Message 10", messageList[9].Text);
+ }
+
+ #endregion
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosCheckpointStoreTests.cs b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosCheckpointStoreTests.cs
new file mode 100644
index 00000000000..dfa1f142215
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosCheckpointStoreTests.cs
@@ -0,0 +1,454 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Linq;
+using System.Text.Json;
+using System.Threading.Tasks;
+using Microsoft.Agents.AI.Workflows;
+using Microsoft.Agents.AI.Workflows.Checkpointing;
+using Microsoft.Azure.Cosmos;
+using Xunit;
+
+namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests;
+
+///
+/// Contains tests for .
+///
+/// Test Modes:
+/// - Default Mode: Cleans up all test data after each test run (deletes database)
+/// - Preserve Mode: Keeps containers and data for inspection in Cosmos DB Emulator Data Explorer
+///
+/// To enable Preserve Mode, set environment variable: COSMOS_PRESERVE_CONTAINERS=true
+/// Example: $env:COSMOS_PRESERVE_CONTAINERS="true"; dotnet test
+///
+/// In Preserve Mode, you can view the data in Cosmos DB Emulator Data Explorer at:
+/// https://localhost:8081/_explorer/index.html
+/// Database: AgentFrameworkTests
+/// Container: Checkpoints
+///
+[Collection("CosmosDB")]
+public class CosmosCheckpointStoreTests : IAsyncLifetime, IDisposable
+{
+ // Cosmos DB Emulator connection settings
+ private const string EmulatorEndpoint = "https://localhost:8081";
+ private const string EmulatorKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==";
+ private const string TestContainerId = "Checkpoints";
+ // Use unique database ID per test class instance to avoid conflicts
+#pragma warning disable CA1802 // Use literals where appropriate
+ private static readonly string s_testDatabaseId = $"AgentFrameworkTests-CheckpointStore-{Guid.NewGuid():N}";
+#pragma warning restore CA1802
+
+ private string _connectionString = string.Empty;
+ private CosmosClient? _cosmosClient;
+ private Database? _database;
+ private bool _emulatorAvailable;
+ private bool _preserveContainer;
+
+ // JsonSerializerOptions configured for .NET 9+ compatibility
+ private static readonly JsonSerializerOptions s_jsonOptions = CreateJsonOptions();
+
+ private static JsonSerializerOptions CreateJsonOptions()
+ {
+ var options = new JsonSerializerOptions();
+#if NET9_0_OR_GREATER
+ options.TypeInfoResolver = new System.Text.Json.Serialization.Metadata.DefaultJsonTypeInfoResolver();
+#endif
+ return options;
+ }
+
+ public async Task InitializeAsync()
+ {
+ // Check environment variable to determine if we should preserve containers
+ // Set COSMOS_PRESERVE_CONTAINERS=true to keep containers and data for inspection
+ this._preserveContainer = string.Equals(Environment.GetEnvironmentVariable("COSMOS_PRESERVE_CONTAINERS"), "true", StringComparison.OrdinalIgnoreCase);
+
+ this._connectionString = $"AccountEndpoint={EmulatorEndpoint};AccountKey={EmulatorKey}";
+
+ try
+ {
+ this._cosmosClient = new CosmosClient(EmulatorEndpoint, EmulatorKey);
+
+ // Test connection by attempting to create database
+ this._database = await this._cosmosClient.CreateDatabaseIfNotExistsAsync(s_testDatabaseId);
+ await this._database.CreateContainerIfNotExistsAsync(
+ TestContainerId,
+ "/runId",
+ throughput: 400);
+
+ this._emulatorAvailable = true;
+ }
+ catch (Exception ex) when (!(ex is OutOfMemoryException || ex is StackOverflowException || ex is AccessViolationException))
+ {
+ // Emulator not available, tests will be skipped
+ this._emulatorAvailable = false;
+ this._cosmosClient?.Dispose();
+ this._cosmosClient = null;
+ }
+ }
+
+ public async Task DisposeAsync()
+ {
+ if (this._cosmosClient != null && this._emulatorAvailable)
+ {
+ try
+ {
+ if (this._preserveContainer)
+ {
+ // Preserve mode: Don't delete the database/container, keep data for inspection
+ // This allows viewing data in the Cosmos DB Emulator Data Explorer
+ // No cleanup needed - data persists for debugging
+ }
+ else
+ {
+ // Clean mode: Delete the test database and all data
+ await this._database!.DeleteAsync();
+ }
+ }
+ catch (Exception ex)
+ {
+ // Ignore cleanup errors, but log for diagnostics
+ Console.WriteLine($"[DisposeAsync] Cleanup error: {ex.Message}\n{ex.StackTrace}");
+ }
+ finally
+ {
+ this._cosmosClient.Dispose();
+ }
+ }
+ }
+
+ private void SkipIfEmulatorNotAvailable()
+ {
+ // In CI: Skip if COSMOS_EMULATOR_AVAILABLE is not set to "true"
+ // Locally: Skip if emulator connection check failed
+ var ciEmulatorAvailable = string.Equals(Environment.GetEnvironmentVariable("COSMOS_EMULATOR_AVAILABLE"), "true", StringComparison.OrdinalIgnoreCase);
+
+ Xunit.Skip.If(!ciEmulatorAvailable && !this._emulatorAvailable, "Cosmos DB Emulator is not available");
+ }
+
+ #region Constructor Tests
+
+ [SkippableFact]
+ public void Constructor_WithCosmosClient_SetsProperties()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+
+ // Act
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+
+ // Assert
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(TestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ public void Constructor_WithConnectionString_SetsProperties()
+ {
+ // Arrange
+ this.SkipIfEmulatorNotAvailable();
+
+ // Act
+ using var store = new CosmosCheckpointStore(this._connectionString, s_testDatabaseId, TestContainerId);
+
+ // Assert
+ Assert.Equal(s_testDatabaseId, store.DatabaseId);
+ Assert.Equal(TestContainerId, store.ContainerId);
+ }
+
+ [SkippableFact]
+ public void Constructor_WithNullCosmosClient_ThrowsArgumentNullException()
+ {
+ // Act & Assert
+ Assert.Throws(() =>
+ new CosmosCheckpointStore((CosmosClient)null!, s_testDatabaseId, TestContainerId));
+ }
+
+ [SkippableFact]
+ public void Constructor_WithNullConnectionString_ThrowsArgumentException()
+ {
+ // Act & Assert
+ Assert.Throws(() =>
+ new CosmosCheckpointStore((string)null!, s_testDatabaseId, TestContainerId));
+ }
+
+ #endregion
+
+ #region Checkpoint Operations Tests
+
+ [SkippableFact]
+ public async Task CreateCheckpointAsync_NewCheckpoint_CreatesSuccessfullyAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test checkpoint" }, s_jsonOptions);
+
+ // Act
+ var checkpointInfo = await store.CreateCheckpointAsync(runId, checkpointValue);
+
+ // Assert
+ Assert.NotNull(checkpointInfo);
+ Assert.Equal(runId, checkpointInfo.RunId);
+ Assert.NotNull(checkpointInfo.CheckpointId);
+ Assert.NotEmpty(checkpointInfo.CheckpointId);
+ }
+
+ [SkippableFact]
+ public async Task RetrieveCheckpointAsync_ExistingCheckpoint_ReturnsCorrectValueAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+ var originalData = new { message = "Hello, World!", timestamp = DateTimeOffset.UtcNow };
+ var checkpointValue = JsonSerializer.SerializeToElement(originalData, s_jsonOptions);
+
+ // Act
+ var checkpointInfo = await store.CreateCheckpointAsync(runId, checkpointValue);
+ var retrievedValue = await store.RetrieveCheckpointAsync(runId, checkpointInfo);
+
+ // Assert
+ Assert.Equal(JsonValueKind.Object, retrievedValue.ValueKind);
+ Assert.True(retrievedValue.TryGetProperty("message", out var messageProp));
+ Assert.Equal("Hello, World!", messageProp.GetString());
+ }
+
+ [SkippableFact]
+ public async Task RetrieveCheckpointAsync_NonExistentCheckpoint_ThrowsInvalidOperationExceptionAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+ var fakeCheckpointInfo = new CheckpointInfo(runId, "nonexistent-checkpoint");
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() =>
+ store.RetrieveCheckpointAsync(runId, fakeCheckpointInfo).AsTask());
+ }
+
+ [SkippableFact]
+ public async Task RetrieveIndexAsync_EmptyStore_ReturnsEmptyCollectionAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+
+ // Act
+ var index = await store.RetrieveIndexAsync(runId);
+
+ // Assert
+ Assert.NotNull(index);
+ Assert.Empty(index);
+ }
+
+ [SkippableFact]
+ public async Task RetrieveIndexAsync_WithCheckpoints_ReturnsAllCheckpointsAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Create multiple checkpoints
+ var checkpoint1 = await store.CreateCheckpointAsync(runId, checkpointValue);
+ var checkpoint2 = await store.CreateCheckpointAsync(runId, checkpointValue);
+ var checkpoint3 = await store.CreateCheckpointAsync(runId, checkpointValue);
+
+ // Act
+ var index = (await store.RetrieveIndexAsync(runId)).ToList();
+
+ // Assert
+ Assert.Equal(3, index.Count);
+ Assert.Contains(index, c => c.CheckpointId == checkpoint1.CheckpointId);
+ Assert.Contains(index, c => c.CheckpointId == checkpoint2.CheckpointId);
+ Assert.Contains(index, c => c.CheckpointId == checkpoint3.CheckpointId);
+ }
+
+ [SkippableFact]
+ public async Task CreateCheckpointAsync_WithParent_CreatesHierarchyAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Act
+ var parentCheckpoint = await store.CreateCheckpointAsync(runId, checkpointValue);
+ var childCheckpoint = await store.CreateCheckpointAsync(runId, checkpointValue, parentCheckpoint);
+
+ // Assert
+ Assert.NotEqual(parentCheckpoint.CheckpointId, childCheckpoint.CheckpointId);
+ Assert.Equal(runId, parentCheckpoint.RunId);
+ Assert.Equal(runId, childCheckpoint.RunId);
+ }
+
+ [SkippableFact]
+ public async Task RetrieveIndexAsync_WithParentFilter_ReturnsFilteredResultsAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Create parent and child checkpoints
+ var parent = await store.CreateCheckpointAsync(runId, checkpointValue);
+ var child1 = await store.CreateCheckpointAsync(runId, checkpointValue, parent);
+ var child2 = await store.CreateCheckpointAsync(runId, checkpointValue, parent);
+
+ // Create an orphan checkpoint
+ var orphan = await store.CreateCheckpointAsync(runId, checkpointValue);
+
+ // Act
+ var allCheckpoints = (await store.RetrieveIndexAsync(runId)).ToList();
+ var childrenOfParent = (await store.RetrieveIndexAsync(runId, parent)).ToList();
+
+ // Assert
+ Assert.Equal(4, allCheckpoints.Count); // parent + 2 children + orphan
+ Assert.Equal(2, childrenOfParent.Count); // only children
+
+ Assert.Contains(childrenOfParent, c => c.CheckpointId == child1.CheckpointId);
+ Assert.Contains(childrenOfParent, c => c.CheckpointId == child2.CheckpointId);
+ Assert.DoesNotContain(childrenOfParent, c => c.CheckpointId == parent.CheckpointId);
+ Assert.DoesNotContain(childrenOfParent, c => c.CheckpointId == orphan.CheckpointId);
+ }
+
+ #endregion
+
+ #region Run Isolation Tests
+
+ [SkippableFact]
+ public async Task CheckpointOperations_DifferentRuns_IsolatesDataAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId1 = Guid.NewGuid().ToString();
+ var runId2 = Guid.NewGuid().ToString();
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Act
+ var checkpoint1 = await store.CreateCheckpointAsync(runId1, checkpointValue);
+ var checkpoint2 = await store.CreateCheckpointAsync(runId2, checkpointValue);
+
+ var index1 = (await store.RetrieveIndexAsync(runId1)).ToList();
+ var index2 = (await store.RetrieveIndexAsync(runId2)).ToList();
+
+ // Assert
+ Assert.Single(index1);
+ Assert.Single(index2);
+ Assert.Equal(checkpoint1.CheckpointId, index1[0].CheckpointId);
+ Assert.Equal(checkpoint2.CheckpointId, index2[0].CheckpointId);
+ Assert.NotEqual(checkpoint1.CheckpointId, checkpoint2.CheckpointId);
+ }
+
+ #endregion
+
+ #region Error Handling Tests
+
+ [SkippableFact]
+ public async Task CreateCheckpointAsync_WithNullRunId_ThrowsArgumentExceptionAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() =>
+ store.CreateCheckpointAsync(null!, checkpointValue).AsTask());
+ }
+
+ [SkippableFact]
+ public async Task CreateCheckpointAsync_WithEmptyRunId_ThrowsArgumentExceptionAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() =>
+ store.CreateCheckpointAsync("", checkpointValue).AsTask());
+ }
+
+ [SkippableFact]
+ public async Task RetrieveCheckpointAsync_WithNullCheckpointInfo_ThrowsArgumentNullExceptionAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ using var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var runId = Guid.NewGuid().ToString();
+
+ // Act & Assert
+ await Assert.ThrowsAsync(() =>
+ store.RetrieveCheckpointAsync(runId, null!).AsTask());
+ }
+
+ #endregion
+
+ #region Disposal Tests
+
+ [SkippableFact]
+ public async Task Dispose_AfterDisposal_ThrowsObjectDisposedExceptionAsync()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+ var checkpointValue = JsonSerializer.SerializeToElement(new { data = "test" }, s_jsonOptions);
+
+ // Act
+ store.Dispose();
+
+ // Assert
+ await Assert.ThrowsAsync(() =>
+ store.CreateCheckpointAsync("test-run", checkpointValue).AsTask());
+ }
+
+ [SkippableFact]
+ public void Dispose_MultipleCalls_DoesNotThrow()
+ {
+ this.SkipIfEmulatorNotAvailable();
+
+ // Arrange
+ var store = new CosmosCheckpointStore(this._cosmosClient!, s_testDatabaseId, TestContainerId);
+
+ // Act & Assert (should not throw)
+ store.Dispose();
+ store.Dispose();
+ store.Dispose();
+ }
+
+ #endregion
+
+ public void Dispose()
+ {
+ this.Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (disposing)
+ {
+ this._cosmosClient?.Dispose();
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosDBCollectionFixture.cs b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosDBCollectionFixture.cs
new file mode 100644
index 00000000000..195c433de59
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/CosmosDBCollectionFixture.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Xunit;
+
+namespace Microsoft.Agents.AI.CosmosNoSql.UnitTests;
+
+///
+/// Defines a collection fixture for Cosmos DB tests to ensure they run sequentially.
+/// This prevents race conditions and resource conflicts when tests create and delete
+/// databases in the Cosmos DB Emulator.
+///
+[CollectionDefinition("CosmosDB", DisableParallelization = true)]
+public sealed class CosmosDBCollectionFixture
+{
+ // This class has no code, and is never created. Its purpose is simply
+ // to be the place to apply [CollectionDefinition] and all the
+ // ICollectionFixture<> interfaces.
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj
new file mode 100644
index 00000000000..d60418ee2c4
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ net10.0;net9.0
+ $(NoWarn);MEAI001
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AgentBotElementYamlTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AgentBotElementYamlTests.cs
new file mode 100644
index 00000000000..31cadfb0cea
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AgentBotElementYamlTests.cs
@@ -0,0 +1,310 @@
+// Copyright (c) Microsoft. All rights reserved.
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.IO;
+using System.Linq;
+using System.Text.Json.Serialization;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.Configuration;
+using Microsoft.PowerFx;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests;
+
+///
+/// Unit tests for
+///
+public sealed class AgentBotElementYamlTests
+{
+ [Theory]
+ [InlineData(PromptAgents.AgentWithEverything)]
+ [InlineData(PromptAgents.AgentWithApiKeyConnection)]
+ [InlineData(PromptAgents.AgentWithVariableReferences)]
+ [InlineData(PromptAgents.AgentWithOutputSchema)]
+ [InlineData(PromptAgents.OpenAIChatAgent)]
+ [InlineData(PromptAgents.AgentWithCurrentModels)]
+ [InlineData(PromptAgents.AgentWithRemoteConnection)]
+ public void FromYaml_DoesNotThrow(string text)
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(text);
+
+ // Assert
+ Assert.NotNull(agent);
+ }
+
+ [Fact]
+ public void FromYaml_NotPromptAgent_Throws()
+ {
+ // Arrange & Act & Assert
+ Assert.Throws(() => AgentBotElementYaml.FromYaml(PromptAgents.Workflow));
+ }
+
+ [Fact]
+ public void FromYaml_Properties()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("AgentName", agent.Name);
+ Assert.Equal("Agent description", agent.Description);
+ Assert.Equal("You are a helpful assistant.", agent.Instructions?.ToTemplateString());
+ Assert.NotNull(agent.Model);
+ Assert.True(agent.Tools.Length > 0);
+ }
+
+ [Fact]
+ public void FromYaml_CurrentModels()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithCurrentModels);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ Assert.Equal("gpt-4o", agent.Model.ModelNameHint);
+ Assert.NotNull(agent.Model.Options);
+ Assert.Equal(0.7f, (float?)agent.Model.Options?.Temperature?.LiteralValue);
+ Assert.Equal(0.9f, (float?)agent.Model.Options?.TopP?.LiteralValue);
+
+ // Assert contents using extension methods
+ Assert.Equal(1024, agent.Model.Options?.MaxOutputTokens?.LiteralValue);
+ Assert.Equal(50, agent.Model.Options?.TopK?.LiteralValue);
+ Assert.Equal(0.7f, (float?)agent.Model.Options?.FrequencyPenalty?.LiteralValue);
+ Assert.Equal(0.7f, (float?)agent.Model.Options?.PresencePenalty?.LiteralValue);
+ Assert.Equal(42, agent.Model.Options?.Seed?.LiteralValue);
+ Assert.Equal(PromptAgents.s_stopSequences, agent.Model.Options?.StopSequences);
+ Assert.True(agent.Model.Options?.AllowMultipleToolCalls?.LiteralValue);
+ Assert.Equal(ChatToolMode.Auto, agent.Model.Options?.AsChatToolMode());
+ }
+
+ [Fact]
+ public void FromYaml_OutputSchema()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithOutputSchema);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.OutputType);
+ ChatResponseFormatJson responseFormat = (agent.OutputType.AsChatResponseFormat() as ChatResponseFormatJson)!;
+ Assert.NotNull(responseFormat);
+ Assert.NotNull(responseFormat.Schema);
+ }
+
+ [Fact]
+ public void FromYaml_CodeInterpreter()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var codeInterpreterTools = tools.Where(t => t is CodeInterpreterTool).ToArray();
+ Assert.Single(codeInterpreterTools);
+ CodeInterpreterTool codeInterpreterTool = (codeInterpreterTools[0] as CodeInterpreterTool)!;
+ Assert.NotNull(codeInterpreterTool);
+ }
+
+ [Fact]
+ public void FromYaml_FunctionTool()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var functionTools = tools.Where(t => t is InvokeClientTaskAction).ToArray();
+ Assert.Single(functionTools);
+ InvokeClientTaskAction functionTool = (functionTools[0] as InvokeClientTaskAction)!;
+ Assert.NotNull(functionTool);
+ Assert.Equal("GetWeather", functionTool.Name);
+ Assert.Equal("Get the weather for a given location.", functionTool.Description);
+ // TODO check schema
+ }
+
+ [Fact]
+ public void FromYaml_MCP()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var mcpTools = tools.Where(t => t is McpServerTool).ToArray();
+ Assert.Single(mcpTools);
+ McpServerTool mcpTool = (mcpTools[0] as McpServerTool)!;
+ Assert.NotNull(mcpTool);
+ Assert.Equal("PersonInfoTool", mcpTool.ServerName?.LiteralValue);
+ AnonymousConnection connection = (mcpTool.Connection as AnonymousConnection)!;
+ Assert.NotNull(connection);
+ Assert.Equal("https://my-mcp-endpoint.com/api", connection.Endpoint?.LiteralValue);
+ }
+
+ [Fact]
+ public void FromYaml_WebSearchTool()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var webSearchTools = tools.Where(t => t is WebSearchTool).ToArray();
+ Assert.Single(webSearchTools);
+ Assert.NotNull(webSearchTools[0] as WebSearchTool);
+ }
+
+ [Fact]
+ public void FromYaml_FileSearchTool()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithEverything);
+
+ // Assert
+ Assert.NotNull(agent);
+ var tools = agent.Tools;
+ var fileSearchTools = tools.Where(t => t is FileSearchTool).ToArray();
+ Assert.Single(fileSearchTools);
+ FileSearchTool fileSearchTool = (fileSearchTools[0] as FileSearchTool)!;
+ Assert.NotNull(fileSearchTool);
+
+ // Verify vector store content property exists and has correct values
+ Assert.NotNull(fileSearchTool.VectorStoreIds);
+ Assert.Equal(3, fileSearchTool.VectorStoreIds.LiteralValue.Length);
+ Assert.Equal("1", fileSearchTool.VectorStoreIds.LiteralValue[0]);
+ Assert.Equal("2", fileSearchTool.VectorStoreIds.LiteralValue[1]);
+ Assert.Equal("3", fileSearchTool.VectorStoreIds.LiteralValue[2]);
+ }
+
+ [Fact]
+ public void FromYaml_ApiKeyConnection()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithApiKeyConnection);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ CurrentModels model = (agent.Model as CurrentModels)!;
+ Assert.NotNull(model);
+ Assert.NotNull(model.Connection);
+ Assert.IsType(model.Connection);
+ ApiKeyConnection connection = (model.Connection as ApiKeyConnection)!;
+ Assert.NotNull(connection);
+ Assert.Equal("https://my-azure-openai-endpoint.openai.azure.com/", connection.Endpoint?.LiteralValue);
+ Assert.Equal("my-api-key", connection.Key?.LiteralValue);
+ }
+
+ [Fact]
+ public void FromYaml_RemoteConnection()
+ {
+ // Arrange & Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithRemoteConnection);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ CurrentModels model = (agent.Model as CurrentModels)!;
+ Assert.NotNull(model);
+ Assert.NotNull(model.Connection);
+ Assert.IsType(model.Connection);
+ RemoteConnection connection = (model.Connection as RemoteConnection)!;
+ Assert.NotNull(connection);
+ Assert.Equal("https://my-azure-openai-endpoint.openai.azure.com/", connection.Endpoint?.LiteralValue);
+ }
+
+ [Fact]
+ public void FromYaml_WithVariableReferences()
+ {
+ // Arrange
+ IConfiguration configuration = new ConfigurationBuilder()
+ .AddInMemoryCollection(new Dictionary
+ {
+ ["OpenAIEndpoint"] = "endpoint",
+ ["OpenAIApiKey"] = "apiKey",
+ ["Temperature"] = "0.9",
+ ["TopP"] = "0.8"
+ })
+ .Build();
+
+ // Act
+ var agent = AgentBotElementYaml.FromYaml(PromptAgents.AgentWithVariableReferences, configuration);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.NotNull(agent.Model);
+ CurrentModels model = (agent.Model as CurrentModels)!;
+ Assert.NotNull(model);
+ Assert.NotNull(model.Options);
+ Assert.Equal(0.9, Eval(model.Options?.Temperature, configuration));
+ Assert.Equal(0.8, Eval(model.Options?.TopP, configuration));
+ Assert.NotNull(model.Connection);
+ Assert.IsType(model.Connection);
+ ApiKeyConnection connection = (model.Connection as ApiKeyConnection)!;
+ Assert.NotNull(connection);
+ Assert.NotNull(connection.Endpoint);
+ Assert.NotNull(connection.Key);
+ Assert.Equal("endpoint", Eval(connection.Endpoint, configuration));
+ Assert.Equal("apiKey", Eval(connection.Key, configuration));
+ }
+
+ ///
+ /// Represents information about a person, including their name, age, and occupation, matched to the JSON schema used in the agent.
+ ///
+ [Description("Information about a person including their name, age, and occupation")]
+ public sealed class PersonInfo
+ {
+ [JsonPropertyName("name")]
+ public string? Name { get; set; }
+
+ [JsonPropertyName("age")]
+ public int? Age { get; set; }
+
+ [JsonPropertyName("occupation")]
+ public string? Occupation { get; set; }
+ }
+
+ private static string? Eval(StringExpression? expression, IConfiguration? configuration = null)
+ {
+ if (expression is null)
+ {
+ return null;
+ }
+
+ RecalcEngine engine = new();
+ if (configuration is not null)
+ {
+ foreach (var kvp in configuration.AsEnumerable())
+ {
+ engine.UpdateVariable(kvp.Key, kvp.Value ?? string.Empty);
+ }
+ }
+
+ return expression.Eval(engine);
+ }
+
+ private static double? Eval(NumberExpression? expression, IConfiguration? configuration = null)
+ {
+ if (expression is null)
+ {
+ return null;
+ }
+
+ RecalcEngine engine = new();
+ if (configuration != null)
+ {
+ foreach (var kvp in configuration.AsEnumerable())
+ {
+ engine.UpdateVariable(kvp.Key, kvp.Value ?? string.Empty);
+ }
+ }
+
+ return expression.Eval(engine);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs
new file mode 100644
index 00000000000..d20bd9be00c
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/AggregatorPromptAgentFactoryTests.cs
@@ -0,0 +1,89 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Bot.ObjectModel;
+using Microsoft.Extensions.AI;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests;
+
+///
+/// Unit tests for
+///
+public sealed class AggregatorPromptAgentFactoryTests
+{
+ [Fact]
+ public void AggregatorAgentFactory_ThrowsForEmptyArray()
+ {
+ // Arrange & Act & Assert
+ Assert.Throws(() => new AggregatorPromptAgentFactory([]));
+ }
+
+ [Fact]
+ public async Task AggregatorAgentFactory_ReturnsNull()
+ {
+ // Arrange
+ var factory = new AggregatorPromptAgentFactory([new TestAgentFactory(null)]);
+
+ // Act
+ var agent = await factory.TryCreateAsync(new GptComponentMetadata("test"));
+
+ // Assert
+ Assert.Null(agent);
+ }
+
+ [Fact]
+ public async Task AggregatorAgentFactory_ReturnsAgent()
+ {
+ // Arrange
+ var agentToReturn = new TestAgent();
+ var factory = new AggregatorPromptAgentFactory([new TestAgentFactory(null), new TestAgentFactory(agentToReturn)]);
+
+ // Act
+ var agent = await factory.TryCreateAsync(new GptComponentMetadata("test"));
+
+ // Assert
+ Assert.Equal(agentToReturn, agent);
+ }
+
+ private sealed class TestAgentFactory : PromptAgentFactory
+ {
+ private readonly AIAgent? _agentToReturn;
+
+ public TestAgentFactory(AIAgent? agentToReturn = null)
+ {
+ this._agentToReturn = agentToReturn;
+ }
+
+ public override Task TryCreateAsync(GptComponentMetadata promptAgent, CancellationToken cancellationToken = default)
+ {
+ return Task.FromResult(this._agentToReturn);
+ }
+ }
+
+ private sealed class TestAgent : AIAgent
+ {
+ public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override AgentThread GetNewThread()
+ {
+ throw new NotImplementedException();
+ }
+
+ public override Task RunAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+
+ public override IAsyncEnumerable RunStreamingAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/ChatClient/ChatClientAgentFactoryTests.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/ChatClient/ChatClientAgentFactoryTests.cs
new file mode 100644
index 00000000000..85906620005
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/ChatClient/ChatClientAgentFactoryTests.cs
@@ -0,0 +1,107 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Threading.Tasks;
+using Microsoft.Extensions.AI;
+using Moq;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests.ChatClient;
+
+///
+/// Unit tests for .
+///
+public sealed class ChatClientAgentFactoryTests
+{
+ private readonly Mock _mockChatClient;
+
+ public ChatClientAgentFactoryTests()
+ {
+ this._mockChatClient = new();
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_WithChatClientInConstructor_CreatesAgentAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientPromptAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ Assert.Equal("Test Agent", agent.Name);
+ Assert.Equal("Test Description", agent.Description);
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_Creates_ChatClientAgentAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientPromptAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ var chatClientAgent = agent as ChatClientAgent;
+ Assert.NotNull(chatClientAgent);
+ Assert.Equal("You are a helpful assistant.", chatClientAgent.Instructions);
+ Assert.NotNull(chatClientAgent.ChatClient);
+ Assert.NotNull(chatClientAgent.ChatOptions);
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_Creates_ChatOptionsAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientPromptAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ var chatClientAgent = agent as ChatClientAgent;
+ Assert.NotNull(chatClientAgent?.ChatOptions);
+ Assert.Equal("You are a helpful assistant.", chatClientAgent?.ChatOptions?.Instructions);
+ Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.Temperature);
+ Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.FrequencyPenalty);
+ Assert.Equal(1024, chatClientAgent?.ChatOptions?.MaxOutputTokens);
+ Assert.Equal(0.9F, chatClientAgent?.ChatOptions?.TopP);
+ Assert.Equal(50, chatClientAgent?.ChatOptions?.TopK);
+ Assert.Equal(0.7F, chatClientAgent?.ChatOptions?.PresencePenalty);
+ Assert.Equal(42L, chatClientAgent?.ChatOptions?.Seed);
+ Assert.NotNull(chatClientAgent?.ChatOptions?.ResponseFormat);
+ Assert.Equal("gpt-4o", chatClientAgent?.ChatOptions?.ModelId);
+ Assert.Equal(["###", "END", "STOP"], chatClientAgent?.ChatOptions?.StopSequences);
+ Assert.True(chatClientAgent?.ChatOptions?.AllowMultipleToolCalls);
+ Assert.Equal(ChatToolMode.Auto, chatClientAgent?.ChatOptions?.ToolMode);
+ Assert.Equal("customValue", chatClientAgent?.ChatOptions?.AdditionalProperties?["customProperty"]);
+ }
+
+ [Fact]
+ public async Task TryCreateAsync_Creates_ToolsAsync()
+ {
+ // Arrange
+ var promptAgent = PromptAgents.CreateTestPromptAgent();
+ ChatClientPromptAgentFactory factory = new(this._mockChatClient.Object);
+
+ // Act
+ AIAgent? agent = await factory.TryCreateAsync(promptAgent);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.IsType(agent);
+ var chatClientAgent = agent as ChatClientAgent;
+ Assert.NotNull(chatClientAgent?.ChatOptions?.Tools);
+ var tools = chatClientAgent?.ChatOptions?.Tools;
+ Assert.Equal(5, tools?.Count);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj
new file mode 100644
index 00000000000..d348a0b433b
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj
@@ -0,0 +1,17 @@
+
+
+
+ $(NoWarn);IDE1006;VSTHRD200
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/PromptAgents.cs b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/PromptAgents.cs
new file mode 100644
index 00000000000..163e4ded18a
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Declarative.UnitTests/PromptAgents.cs
@@ -0,0 +1,386 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using Microsoft.Bot.ObjectModel;
+
+namespace Microsoft.Agents.AI.Declarative.UnitTests;
+
+internal static class PromptAgents
+{
+ internal const string AgentWithEverything =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.7
+ maxOutputTokens: 1024
+ topP: 0.9
+ topK: 50
+ frequencyPenalty: 0.0
+ presencePenalty: 0.0
+ seed: 42
+ responseFormat: text
+ stopSequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allowMultipleToolCalls: true
+ tools:
+ - kind: codeInterpreter
+ inputs:
+ - kind: HostedFileContent
+ FileId: fileId123
+ - kind: function
+ name: GetWeather
+ description: Get the weather for a given location.
+ parameters:
+ - name: location
+ type: string
+ description: The city and state, e.g. San Francisco, CA
+ required: true
+ - name: unit
+ type: string
+ description: The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.
+ required: false
+ enum:
+ - celsius
+ - fahrenheit
+ - kind: mcp
+ serverName: PersonInfoTool
+ serverDescription: Get information about a person.
+ connection:
+ kind: AnonymousConnection
+ endpoint: https://my-mcp-endpoint.com/api
+ allowedTools:
+ - "GetPersonInfo"
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ approvalMode:
+ kind: HostedMcpServerToolRequireSpecificApprovalMode
+ AlwaysRequireApprovalToolNames:
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ NeverRequireApprovalToolNames:
+ - "GetPersonInfo"
+ - kind: webSearch
+ name: WebSearchTool
+ description: Search the web for information.
+ - kind: fileSearch
+ name: FileSearchTool
+ description: Search files for information.
+ ranker: default
+ scoreThreshold: 0.5
+ maxResults: 5
+ maxContentLength: 2000
+ vectorStoreIds:
+ - 1
+ - 2
+ - 3
+ """;
+
+ internal const string AgentWithOutputSchema =
+ """
+ kind: Prompt
+ name: Translation Assistant
+ description: A helpful assistant that translates text to a specified language.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.9
+ topP: 0.95
+ instructions: You are a helpful assistant. You answer questions in {language}. You return your answers in a JSON format.
+ additionalInstructions: You must always respond in the specified language.
+ tools:
+ - kind: codeInterpreter
+ template:
+ format: PowerFx # Mustache is the other option
+ parser: None # Prompty and XML are the other options
+ inputSchema:
+ properties:
+ language: string
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+ internal const string AgentWithApiKeyConnection =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ connection:
+ kind: ApiKey
+ endpoint: https://my-azure-openai-endpoint.openai.azure.com/
+ key: my-api-key
+ """;
+
+ internal const string AgentWithRemoteConnection =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ connection:
+ kind: Remote
+ endpoint: https://my-azure-openai-endpoint.openai.azure.com/
+ """;
+
+ internal const string AgentWithVariableReferences =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: =Env.Temperature
+ topP: =Env.TopP
+ connection:
+ kind: apiKey
+ endpoint: =Env.OpenAIEndpoint
+ key: =Env.OpenAIApiKey
+ """;
+
+ internal const string OpenAIChatAgent =
+ """
+ kind: Prompt
+ name: Assistant
+ description: Helpful assistant
+ instructions: You are a helpful assistant. You answer questions in the language specified by the user. You return your answers in a JSON format.
+ model:
+ id: =Env.OPENAI_MODEL
+ options:
+ temperature: 0.9
+ topP: 0.95
+ connection:
+ kind: apiKey
+ key: =Env.OPENAI_APIKEY
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+ internal const string AgentWithCurrentModels =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.7
+ maxOutputTokens: 1024
+ topP: 0.9
+ topK: 50
+ frequencyPenalty: 0.7
+ presencePenalty: 0.7
+ seed: 42
+ responseFormat: text
+ stopSequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allowMultipleToolCalls: true
+ chatToolMode: auto
+ """;
+
+ internal const string AgentWithCurrentModelsSnakeCase =
+ """
+ kind: Prompt
+ name: AgentName
+ description: Agent description
+ instructions: You are a helpful assistant.
+ model:
+ id: gpt-4o
+ options:
+ temperature: 0.7
+ max_output_tokens: 1024
+ top_p: 0.9
+ top_k: 50
+ frequency_penalty: 0.7
+ presence_penalty: 0.7
+ seed: 42
+ response_format: text
+ stop_sequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allow_multiple_tool_calls: true
+ chat_tool_mode: auto
+ """;
+
+ internal const string Workflow =
+ """
+ kind: Workflow
+ trigger:
+
+ kind: OnConversationStart
+ id: workflow_demo
+ actions:
+
+ - kind: InvokeAzureAgent
+ id: question_student
+ conversationId: =System.ConversationId
+ agent:
+ name: StudentAgent
+
+ - kind: InvokeAzureAgent
+ id: question_teacher
+ conversationId: =System.ConversationId
+ agent:
+ name: TeacherAgent
+ output:
+ messages: Local.TeacherResponse
+
+ - kind: SetVariable
+ id: set_count_increment
+ variable: Local.TurnCount
+ value: =Local.TurnCount + 1
+
+ - kind: ConditionGroup
+ id: check_completion
+ conditions:
+
+ - condition: =!IsBlank(Find("CONGRATULATIONS", Upper(MessageText(Local.TeacherResponse))))
+ id: check_turn_done
+ actions:
+
+ - kind: SendActivity
+ id: sendActivity_done
+ activity: GOLD STAR!
+
+ - condition: =Local.TurnCount < 4
+ id: check_turn_count
+ actions:
+
+ - kind: GotoAction
+ id: goto_student_agent
+ actionId: question_student
+
+ elseActions:
+
+ - kind: SendActivity
+ id: sendActivity_tired
+ activity: Let's try again later...
+
+ """;
+
+ internal static readonly string[] s_stopSequences = ["###", "END", "STOP"];
+
+ internal static GptComponentMetadata CreateTestPromptAgent(string? publisher = "OpenAI", string? apiType = "Chat")
+ {
+ string agentYaml =
+ $"""
+ kind: Prompt
+ name: Test Agent
+ description: Test Description
+ instructions: You are a helpful assistant.
+ additionalInstructions: Provide detailed and accurate responses.
+ model:
+ id: gpt-4o
+ publisher: {publisher}
+ apiType: {apiType}
+ options:
+ modelId: gpt-4o
+ temperature: 0.7
+ maxOutputTokens: 1024
+ topP: 0.9
+ topK: 50
+ frequencyPenalty: 0.7
+ presencePenalty: 0.7
+ seed: 42
+ responseFormat: text
+ stopSequences:
+ - "###"
+ - "END"
+ - "STOP"
+ allowMultipleToolCalls: true
+ chatToolMode: auto
+ customProperty: customValue
+ connection:
+ kind: apiKey
+ endpoint: https://my-azure-openai-endpoint.openai.azure.com/
+ key: my-api-key
+ tools:
+ - kind: codeInterpreter
+ - kind: function
+ name: GetWeather
+ description: Get the weather for a given location.
+ parameters:
+ - name: location
+ type: string
+ description: The city and state, e.g. San Francisco, CA
+ required: true
+ - name: unit
+ type: string
+ description: The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.
+ required: false
+ enum:
+ - celsius
+ - fahrenheit
+ - kind: mcp
+ serverName: PersonInfoTool
+ serverDescription: Get information about a person.
+ allowedTools:
+ - "GetPersonInfo"
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ approvalMode:
+ kind: HostedMcpServerToolRequireSpecificApprovalMode
+ AlwaysRequireApprovalToolNames:
+ - "UpdatePersonInfo"
+ - "DeletePersonInfo"
+ NeverRequireApprovalToolNames:
+ - "GetPersonInfo"
+ connection:
+ kind: AnonymousConnection
+ endpoint: https://my-mcp-endpoint.com/api
+ - kind: webSearch
+ name: WebSearchTool
+ description: Search the web for information.
+ - kind: fileSearch
+ name: FileSearchTool
+ description: Search files for information.
+ vectorStoreIds:
+ - 1
+ - 2
+ - 3
+ outputSchema:
+ properties:
+ language:
+ type: string
+ required: true
+ description: The language of the answer.
+ answer:
+ type: string
+ required: true
+ description: The answer text.
+ """;
+
+ return AgentBotElementYaml.FromYaml(agentYaml);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj
index 9135a90e2ed..1fc964e702d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj
@@ -1,15 +1,13 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
false
$(NoWarn);CA1812
-
-
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs
index 73c230410cd..1ae3c668348 100644
--- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/AgentEntityTests.cs
@@ -2,6 +2,8 @@
using System.Diagnostics;
using System.Reflection;
+using Microsoft.Agents.AI.DurableTask.State;
+using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Client.Entities;
using Microsoft.DurableTask.Entities;
@@ -67,8 +69,72 @@ await simpleAgentProxy.RunAsync(
cancellationToken: this.TestTimeoutToken);
// Assert: verify the agent state was stored with the correct entity name prefix
- entity = await client.Entities.GetEntityAsync(expectedEntityId, false, this.TestTimeoutToken);
+ entity = await client.Entities.GetEntityAsync(expectedEntityId, true, this.TestTimeoutToken);
Assert.NotNull(entity);
+ Assert.True(entity.IncludesState);
+
+ DurableAgentState state = entity.State.ReadAs();
+
+ DurableAgentStateRequest request = Assert.Single(state.Data.ConversationHistory.OfType());
+
+ Assert.Null(request.OrchestrationId);
+ }
+
+ [Fact]
+ public async Task OrchestrationIdSetDuringOrchestrationAsync()
+ {
+ // Arrange
+ AIAgent simpleAgent = TestHelper.GetAzureOpenAIChatClient(s_configuration).CreateAIAgent(
+ name: "TestAgent",
+ instructions: "You are a helpful assistant that always responds with a friendly greeting."
+ );
+
+ using TestHelper testHelper = TestHelper.Start(
+ [simpleAgent],
+ this._outputHelper,
+ registry => registry.AddOrchestrator());
+
+ DurableTaskClient client = testHelper.GetClient();
+
+ // Act
+ string orchestrationId = await client.ScheduleNewOrchestrationInstanceAsync(nameof(TestOrchestrator), "What is the capital of Maine?");
+
+ OrchestrationMetadata? status = await client.WaitForInstanceCompletionAsync(
+ orchestrationId,
+ true,
+ this.TestTimeoutToken);
+
+ // Assert
+ EntityInstanceId expectedEntityId = AgentSessionId.Parse(status.ReadOutputAs()!);
+
+ EntityMetadata? entity = await client.Entities.GetEntityAsync(expectedEntityId, true, this.TestTimeoutToken);
+
+ Assert.NotNull(entity);
+ Assert.True(entity.IncludesState);
+
+ DurableAgentState state = entity.State.ReadAs();
+
+ DurableAgentStateRequest request = Assert.Single(state.Data.ConversationHistory.OfType());
+
+ Assert.Equal(orchestrationId, request.OrchestrationId);
+ }
+
+ [System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Constructed via reflection.")]
+ private sealed class TestOrchestrator : TaskOrchestrator
+ {
+ public override async Task RunAsync(TaskOrchestrationContext context, string input)
+ {
+ DurableAIAgent writer = context.GetAgent("TestAgent");
+ AgentThread writerThread = writer.GetNewThread();
+
+ await writer.RunAsync(
+ message: context.GetInput()!,
+ thread: writerThread);
+
+ AgentSessionId sessionId = writerThread.GetService();
+
+ return sessionId.ToString();
+ }
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj
index 7150e74bd85..db6aa6d62be 100644
--- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
b7762d10-e29b-4bb1-8b74-b6d69a667dd4
diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj
index b413733f2b8..b0cf00cae18 100644
--- a/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
b7762d10-e29b-4bb1-8b74-b6d69a667dd4
diff --git a/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/State/DurableAgentStateRequestTests.cs b/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/State/DurableAgentStateRequestTests.cs
new file mode 100644
index 00000000000..acdc6021652
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.DurableTask.UnitTests/State/DurableAgentStateRequestTests.cs
@@ -0,0 +1,34 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System.Text.Json;
+using Microsoft.Agents.AI.DurableTask.State;
+
+namespace Microsoft.Agents.AI.DurableTask.Tests.Unit.State;
+
+public sealed class DurableAgentStateRequestTests
+{
+ [Fact]
+ public void RequestSerializationDeserialization()
+ {
+ // Arrange
+ RunRequest originalRequest = new("Hello, world!")
+ {
+ OrchestrationId = "orch-456"
+ };
+ DurableAgentStateRequest originalDurableRequest = DurableAgentStateRequest.FromRunRequest(originalRequest);
+
+ // Act
+ string jsonContent = JsonSerializer.Serialize(
+ originalDurableRequest,
+ DurableAgentStateJsonContext.Default.GetTypeInfo(typeof(DurableAgentStateRequest))!);
+
+ DurableAgentStateRequest? convertedJsonContent = (DurableAgentStateRequest?)JsonSerializer.Deserialize(
+ jsonContent,
+ DurableAgentStateJsonContext.Default.GetTypeInfo(typeof(DurableAgentStateRequest))!);
+
+ // Assert
+ Assert.NotNull(convertedJsonContent);
+ Assert.Equal(originalRequest.CorrelationId, convertedJsonContent.CorrelationId);
+ Assert.Equal(originalRequest.OrchestrationId, convertedJsonContent.OrchestrationId);
+ }
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
index 07dde4f8027..42d86828700 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj
@@ -1,16 +1,16 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
-
-
+
+
-
-
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs
index 923eaa77521..5bc4e8afad2 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/BasicStreamingTests.cs
@@ -276,18 +276,15 @@ public async ValueTask DisposeAsync()
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated via dependency injection")]
internal sealed class FakeChatClientAgent : AIAgent
{
- private readonly string _agentId;
- private readonly string _description;
-
public FakeChatClientAgent()
{
- this._agentId = "fake-agent";
- this._description = "A fake agent for testing";
+ this.Id = "fake-agent";
+ this.Description = "A fake agent for testing";
}
- public override string Id => this._agentId;
+ public override string Id { get; }
- public override string? Description => this._description;
+ public override string? Description { get; }
public override AgentThread GetNewThread()
{
@@ -353,18 +350,15 @@ public FakeInMemoryAgentThread(JsonElement serializedThread, JsonSerializerOptio
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated via dependency injection")]
internal sealed class FakeMultiMessageAgent : AIAgent
{
- private readonly string _agentId;
- private readonly string _description;
-
public FakeMultiMessageAgent()
{
- this._agentId = "fake-multi-message-agent";
- this._description = "A fake agent that sends multiple messages for testing";
+ this.Id = "fake-multi-message-agent";
+ this.Description = "A fake agent that sends multiple messages for testing";
}
- public override string Id => this._agentId;
+ public override string Id { get; }
- public override string? Description => this._description;
+ public override string? Description { get; }
public override AgentThread GetNewThread()
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs
new file mode 100644
index 00000000000..df8caea2149
--- /dev/null
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ForwardedPropertiesTests.cs
@@ -0,0 +1,358 @@
+// Copyright (c) Microsoft. All rights reserved.
+
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.IO;
+using System.Net.Http;
+using System.Net.ServerSentEvents;
+using System.Runtime.CompilerServices;
+using System.Text;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting.Server;
+using Microsoft.AspNetCore.TestHost;
+using Microsoft.Extensions.AI;
+using Microsoft.Extensions.DependencyInjection;
+
+namespace Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests;
+
+public sealed class ForwardedPropertiesTests : IAsyncDisposable
+{
+ private WebApplication? _app;
+ private HttpClient? _client;
+
+ [Fact]
+ public async Task ForwardedProps_AreParsedAndPassedToAgent_WhenProvidedInRequestAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ // Create request JSON with forwardedProps (per AG-UI protocol spec)
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test forwarded props" }],
+ "forwardedProps": { "customProp": "customValue", "sessionId": "test-session-123" }
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+
+ // Assert
+ response.IsSuccessStatusCode.Should().BeTrue();
+ fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
+ fakeAgent.ReceivedForwardedProperties.GetProperty("customProp").GetString().Should().Be("customValue");
+ fakeAgent.ReceivedForwardedProperties.GetProperty("sessionId").GetString().Should().Be("test-session-123");
+ }
+
+ [Fact]
+ public async Task ForwardedProps_WithNestedObjects_AreCorrectlyParsedAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test nested props" }],
+ "forwardedProps": {
+ "user": { "id": "user-1", "name": "Test User" },
+ "metadata": { "version": "1.0", "feature": "test" }
+ }
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+
+ // Assert
+ response.IsSuccessStatusCode.Should().BeTrue();
+ fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
+
+ JsonElement user = fakeAgent.ReceivedForwardedProperties.GetProperty("user");
+ user.GetProperty("id").GetString().Should().Be("user-1");
+ user.GetProperty("name").GetString().Should().Be("Test User");
+
+ JsonElement metadata = fakeAgent.ReceivedForwardedProperties.GetProperty("metadata");
+ metadata.GetProperty("version").GetString().Should().Be("1.0");
+ metadata.GetProperty("feature").GetString().Should().Be("test");
+ }
+
+ [Fact]
+ public async Task ForwardedProps_WithArrays_AreCorrectlyParsedAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test array props" }],
+ "forwardedProps": {
+ "tags": ["tag1", "tag2", "tag3"],
+ "scores": [1, 2, 3, 4, 5]
+ }
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+
+ // Assert
+ response.IsSuccessStatusCode.Should().BeTrue();
+ fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
+
+ JsonElement tags = fakeAgent.ReceivedForwardedProperties.GetProperty("tags");
+ tags.GetArrayLength().Should().Be(3);
+ tags[0].GetString().Should().Be("tag1");
+
+ JsonElement scores = fakeAgent.ReceivedForwardedProperties.GetProperty("scores");
+ scores.GetArrayLength().Should().Be(5);
+ scores[2].GetInt32().Should().Be(3);
+ }
+
+ [Fact]
+ public async Task ForwardedProps_WhenEmpty_DoesNotCauseErrorsAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test empty props" }],
+ "forwardedProps": {}
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+
+ // Assert
+ response.IsSuccessStatusCode.Should().BeTrue();
+ }
+
+ [Fact]
+ public async Task ForwardedProps_WhenNotProvided_AgentStillWorksAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test no props" }]
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+
+ // Assert
+ response.IsSuccessStatusCode.Should().BeTrue();
+ fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Undefined);
+ }
+
+ [Fact]
+ public async Task ForwardedProps_ReturnsValidSSEResponse_WithTextDeltaEventsAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test response" }],
+ "forwardedProps": { "customProp": "value" }
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+ response.EnsureSuccessStatusCode();
+
+ Stream stream = await response.Content.ReadAsStreamAsync();
+ List> events = [];
+ await foreach (SseItem item in SseParser.Create(stream).EnumerateAsync())
+ {
+ events.Add(item);
+ }
+
+ // Assert
+ events.Should().NotBeEmpty();
+
+ // SSE events have EventType = "message" and the actual type is in the JSON data
+ // Should have run_started event
+ events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_STARTED\""));
+
+ // Should have text_message_start event
+ events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_START\""));
+
+ // Should have text_message_content event with the response text
+ events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"TEXT_MESSAGE_CONTENT\""));
+
+ // Should have run_finished event
+ events.Should().Contain(e => e.Data != null && e.Data.Contains("\"type\":\"RUN_FINISHED\""));
+ }
+
+ [Fact]
+ public async Task ForwardedProps_WithMixedTypes_AreCorrectlyParsedAsync()
+ {
+ // Arrange
+ FakeForwardedPropsAgent fakeAgent = new();
+ await this.SetupTestServerAsync(fakeAgent);
+
+ const string RequestJson = """
+ {
+ "threadId": "thread-123",
+ "runId": "run-456",
+ "messages": [{ "id": "msg-1", "role": "user", "content": "test mixed types" }],
+ "forwardedProps": {
+ "stringProp": "text",
+ "numberProp": 42,
+ "boolProp": true,
+ "nullProp": null,
+ "arrayProp": [1, "two", false],
+ "objectProp": { "nested": "value" }
+ }
+ }
+ """;
+
+ using StringContent content = new(RequestJson, Encoding.UTF8, "application/json");
+
+ // Act
+ HttpResponseMessage response = await this._client!.PostAsync(new Uri("/agent", UriKind.Relative), content);
+
+ // Assert
+ response.IsSuccessStatusCode.Should().BeTrue();
+ fakeAgent.ReceivedForwardedProperties.ValueKind.Should().Be(JsonValueKind.Object);
+
+ fakeAgent.ReceivedForwardedProperties.GetProperty("stringProp").GetString().Should().Be("text");
+ fakeAgent.ReceivedForwardedProperties.GetProperty("numberProp").GetInt32().Should().Be(42);
+ fakeAgent.ReceivedForwardedProperties.GetProperty("boolProp").GetBoolean().Should().BeTrue();
+ fakeAgent.ReceivedForwardedProperties.GetProperty("nullProp").ValueKind.Should().Be(JsonValueKind.Null);
+ fakeAgent.ReceivedForwardedProperties.GetProperty("arrayProp").GetArrayLength().Should().Be(3);
+ fakeAgent.ReceivedForwardedProperties.GetProperty("objectProp").GetProperty("nested").GetString().Should().Be("value");
+ }
+
+ private async Task SetupTestServerAsync(FakeForwardedPropsAgent fakeAgent)
+ {
+ WebApplicationBuilder builder = WebApplication.CreateBuilder();
+ builder.Services.AddAGUI();
+ builder.WebHost.UseTestServer();
+
+ this._app = builder.Build();
+
+ this._app.MapAGUI("/agent", fakeAgent);
+
+ await this._app.StartAsync();
+
+ TestServer testServer = this._app.Services.GetRequiredService() as TestServer
+ ?? throw new InvalidOperationException("TestServer not found");
+
+ this._client = testServer.CreateClient();
+ }
+
+ public async ValueTask DisposeAsync()
+ {
+ this._client?.Dispose();
+ if (this._app != null)
+ {
+ await this._app.DisposeAsync();
+ }
+ }
+}
+
+[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated in tests")]
+internal sealed class FakeForwardedPropsAgent : AIAgent
+{
+ public FakeForwardedPropsAgent()
+ {
+ }
+
+ public override string? Description => "Agent for forwarded properties testing";
+
+ public JsonElement ReceivedForwardedProperties { get; private set; }
+
+ public override Task RunAsync(IEnumerable messages, AgentThread? thread = null, AgentRunOptions? options = null, CancellationToken cancellationToken = default)
+ {
+ return this.RunStreamingAsync(messages, thread, options, cancellationToken).ToAgentRunResponseAsync(cancellationToken);
+ }
+
+ public override async IAsyncEnumerable RunStreamingAsync(
+ IEnumerable messages,
+ AgentThread? thread = null,
+ AgentRunOptions? options = null,
+ [EnumeratorCancellation] CancellationToken cancellationToken = default)
+ {
+ // Extract forwarded properties from ChatOptions.AdditionalProperties (set by AG-UI hosting layer)
+ if (options is ChatClientAgentRunOptions { ChatOptions.AdditionalProperties: { } properties } &&
+ properties.TryGetValue("ag_ui_forwarded_properties", out object? propsObj) &&
+ propsObj is JsonElement forwardedProps)
+ {
+ this.ReceivedForwardedProperties = forwardedProps;
+ }
+
+ // Always return a text response
+ string messageId = Guid.NewGuid().ToString("N");
+ yield return new AgentRunResponseUpdate
+ {
+ MessageId = messageId,
+ Role = ChatRole.Assistant,
+ Contents = [new TextContent("Forwarded props processed")]
+ };
+
+ await Task.CompletedTask;
+ }
+
+ public override AgentThread GetNewThread() => new FakeInMemoryAgentThread();
+
+ public override AgentThread DeserializeThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
+ {
+ return new FakeInMemoryAgentThread(serializedThread, jsonSerializerOptions);
+ }
+
+ private sealed class FakeInMemoryAgentThread : InMemoryAgentThread
+ {
+ public FakeInMemoryAgentThread()
+ : base()
+ {
+ }
+
+ public FakeInMemoryAgentThread(JsonElement serializedThread, JsonSerializerOptions? jsonSerializerOptions = null)
+ : base(serializedThread, jsonSerializerOptions)
+ {
+ }
+ }
+
+ public override object? GetService(Type serviceType, object? serviceKey = null) => null;
+}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj
index f87cd59c27b..6b909fd4f2b 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
@@ -11,18 +10,14 @@
-
-
-
-
-
-
-
-
-
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs
index 47d9e63520c..c96f2d92d00 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/SharedStateTests.cs
@@ -62,7 +62,7 @@ public async Task StateSnapshot_IsReturnedAsDataContent_WithCorrectMediaTypeAsyn
// Verify the state content
string receivedJson = System.Text.Encoding.UTF8.GetString(dataContent!.Data.ToArray());
- JsonElement receivedState = JsonSerializer.Deserialize(receivedJson);
+ JsonElement receivedState = JsonElement.Parse(receivedJson);
receivedState.GetProperty("counter").GetInt32().Should().Be(43, "state should be incremented");
receivedState.GetProperty("status").GetString().Should().Be("active");
}
@@ -141,7 +141,7 @@ public async Task ComplexState_WithNestedObjectsAndArrays_RoundTripsCorrectlyAsy
DataContent? dataContent = stateUpdate!.Contents.OfType().FirstOrDefault(dc => dc.MediaType == "application/json");
string receivedJson = System.Text.Encoding.UTF8.GetString(dataContent!.Data.ToArray());
- JsonElement receivedState = JsonSerializer.Deserialize(receivedJson);
+ JsonElement receivedState = JsonElement.Parse(receivedJson);
receivedState.GetProperty("sessionId").GetString().Should().Be("test-123");
receivedState.GetProperty("nested").GetProperty("count").GetInt32().Should().Be(10);
@@ -196,7 +196,7 @@ public async Task StateSnapshot_CanBeUsedInSubsequentRequest_ForStateRoundTripAs
DataContent? secondStateContent = secondStateUpdate!.Contents.OfType().FirstOrDefault(dc => dc.MediaType == "application/json");
string secondStateJson = System.Text.Encoding.UTF8.GetString(secondStateContent!.Data.ToArray());
- JsonElement secondState = JsonSerializer.Deserialize(secondStateJson);
+ JsonElement secondState = JsonElement.Parse(secondStateJson);
secondState.GetProperty("counter").GetInt32().Should().Be(3, "counter should be incremented twice: 1 -> 2 -> 3");
}
@@ -304,7 +304,7 @@ public async Task NonStreamingRunAsync_WithState_ReturnsStateInResponseAsync()
DataContent? dataContent = stateResponseMessage!.Contents.OfType().FirstOrDefault(dc => dc.MediaType == "application/json");
string receivedJson = System.Text.Encoding.UTF8.GetString(dataContent!.Data.ToArray());
- JsonElement receivedState = JsonSerializer.Deserialize(receivedJson);
+ JsonElement receivedState = JsonElement.Parse(receivedJson);
receivedState.GetProperty("counter").GetInt32().Should().Be(6);
}
@@ -385,7 +385,7 @@ stateObj is JsonElement state &&
{
modifiedState[prop.Name] = prop.Value.GetString();
}
- else if (prop.Value.ValueKind == JsonValueKind.Object || prop.Value.ValueKind == JsonValueKind.Array)
+ else if (prop.Value.ValueKind is JsonValueKind.Object or JsonValueKind.Array)
{
modifiedState[prop.Name] = prop.Value;
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs
index c5ee3d711ba..178ed20d730 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/ToolCallingTests.cs
@@ -396,7 +396,7 @@ public async Task AGUIChatClientCombinesCustomJsonSerializerOptionsAsync()
var json = JsonSerializer.Serialize(testResponse, ClientJsonContext.Default.ClientForecastResponse);
// Assert
- var jsonElement = JsonDocument.Parse(json).RootElement;
+ var jsonElement = JsonElement.Parse(json);
jsonElement.GetProperty("MaxTemp").GetInt32().Should().Be(75);
jsonElement.GetProperty("MinTemp").GetInt32().Should().Be(60);
jsonElement.GetProperty("Outlook").GetString().Should().Be("Rainy");
@@ -652,15 +652,15 @@ public async IAsyncEnumerable GetStreamingResponseAsync(
return functionName switch
{
"GetWeather" => new Dictionary { ["location"] = "Seattle" },
- "GetTime" => new Dictionary(), // No parameters
+ "GetTime" => [], // No parameters
"Calculate" => new Dictionary { ["a"] = 5, ["b"] = 3 },
"FormatText" => new Dictionary { ["text"] = "hello" },
- "GetServerData" => new Dictionary(), // No parameters
- "GetClientData" => new Dictionary(), // No parameters
+ "GetServerData" => [], // No parameters
+ "GetClientData" => [], // No parameters
// For custom types, the parameter name is "request" and the value is an instance of the request type
"GetServerForecast" => new Dictionary { ["request"] = new ServerForecastRequest("Seattle", 5) },
"GetClientForecast" => new Dictionary { ["request"] = new ClientForecastRequest("Portland", true) },
- _ => new Dictionary() // Default: no parameters
+ _ => [] // Default: no parameters
};
}
@@ -689,9 +689,9 @@ public record ClientForecastResponse(int MaxTemp, int MinTemp, string Outlook);
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(ServerForecastRequest))]
[JsonSerializable(typeof(ServerForecastResponse))]
-internal sealed partial class ServerJsonContext : JsonSerializerContext { }
+internal sealed partial class ServerJsonContext : JsonSerializerContext;
[JsonSourceGenerationOptions(WriteIndented = false)]
[JsonSerializable(typeof(ClientForecastRequest))]
[JsonSerializable(typeof(ClientForecastResponse))]
-internal sealed partial class ClientJsonContext : JsonSerializerContext { }
+internal sealed partial class ClientJsonContext : JsonSerializerContext;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs
index e5fb2061479..78a30487473 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/AGUIEndpointRouteBuilderExtensionsTests.cs
@@ -38,7 +38,7 @@ public void MapAGUIAgent_MapsEndpoint_AtSpecifiedPattern()
AIAgent agent = new TestAgent();
// Act
- IEndpointConventionBuilder? result = AGUIEndpointRouteBuilderExtensions.MapAGUI(endpointsMock.Object, Pattern, agent);
+ IEndpointConventionBuilder? result = endpointsMock.Object.MapAGUI(Pattern, agent);
// Assert
Assert.NotNull(result);
@@ -305,7 +305,7 @@ public async Task MapAGUIAgent_EmitsTextMessageContent_WithCorrectDeltaAsync()
public async Task MapAGUIAgent_WithCustomAgent_ProducesExpectedStreamStructureAsync()
{
// Arrange
- AIAgent customAgentFactory(IEnumerable messages, IEnumerable tools, IEnumerable> context, JsonElement props)
+ static AIAgent CustomAgentFactory(IEnumerable messages, IEnumerable tools, IEnumerable> context, JsonElement props)
{
return new MultiResponseAgent();
}
@@ -322,7 +322,7 @@ AIAgent customAgentFactory(IEnumerable messages, IEnumerable messages, IEnumerable events = ParseSseEvents(responseContent);
- List contentEvents = new();
+ List contentEvents = [];
foreach (JsonElement evt in events)
{
if (evt.GetProperty("type").GetString() == AGUIEventTypes.TextMessageContent)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj
index e6d4459c6eb..57a653d9f06 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj
@@ -1,17 +1,17 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
-
-
-
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj
index ae816efb7fb..fb955c3162f 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj
@@ -1,15 +1,13 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
b7762d10-e29b-4bb1-8b74-b6d69a667dd4
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs
index ef9807fddf6..0ba879f0244 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/SamplesValidation.cs
@@ -111,7 +111,7 @@ await this.RunSampleTestAsync(samplePath, async (logs) =>
startResponse.IsSuccessStatusCode,
$"Start orchestration failed with status: {startResponse.StatusCode}");
string startResponseText = await startResponse.Content.ReadAsStringAsync();
- JsonElement startResult = JsonSerializer.Deserialize(startResponseText);
+ JsonElement startResult = JsonElement.Parse(startResponseText);
Assert.True(startResult.TryGetProperty("statusQueryGetUri", out JsonElement statusUriElement));
Uri statusUri = new(statusUriElement.GetString()!);
@@ -126,7 +126,7 @@ await this.RunSampleTestAsync(samplePath, async (logs) =>
$"Status check failed with status: {statusResponse.StatusCode}");
string statusText = await statusResponse.Content.ReadAsStringAsync();
- JsonElement statusResult = JsonSerializer.Deserialize(statusText);
+ JsonElement statusResult = JsonElement.Parse(statusText);
Assert.Equal("Completed", statusResult.GetProperty("runtimeStatus").GetString());
Assert.True(statusResult.TryGetProperty("output", out JsonElement outputElement));
@@ -154,7 +154,7 @@ await this.RunSampleTestAsync(samplePath, async (logs) =>
Assert.True(startResponse.IsSuccessStatusCode, $"Start orchestration failed with status: {startResponse.StatusCode}");
string startResponseText = await startResponse.Content.ReadAsStringAsync();
- JsonElement startResult = JsonSerializer.Deserialize(startResponseText);
+ JsonElement startResult = JsonElement.Parse(startResponseText);
Assert.True(startResult.TryGetProperty("instanceId", out JsonElement instanceIdElement));
Assert.True(startResult.TryGetProperty("statusQueryGetUri", out JsonElement statusUriElement));
@@ -169,7 +169,7 @@ await this.RunSampleTestAsync(samplePath, async (logs) =>
Assert.True(statusResponse.IsSuccessStatusCode, $"Status check failed with status: {statusResponse.StatusCode}");
string statusText = await statusResponse.Content.ReadAsStringAsync();
- JsonElement statusResult = JsonSerializer.Deserialize(statusText);
+ JsonElement statusResult = JsonElement.Parse(statusText);
Assert.Equal("Completed", statusResult.GetProperty("runtimeStatus").GetString());
Assert.True(statusResult.TryGetProperty("output", out JsonElement outputElement));
@@ -233,7 +233,7 @@ await this.RunSampleTestAsync(samplePath, async (logs) =>
startResponse.IsSuccessStatusCode,
$"Start HITL orchestration failed with status: {startResponse.StatusCode}");
string startResponseText = await startResponse.Content.ReadAsStringAsync();
- JsonElement startResult = JsonSerializer.Deserialize(startResponseText);
+ JsonElement startResult = JsonElement.Parse(startResponseText);
Assert.True(startResult.TryGetProperty("statusQueryGetUri", out JsonElement statusUriElement));
Uri statusUri = new(statusUriElement.GetString()!);
@@ -250,7 +250,7 @@ await this.RunSampleTestAsync(samplePath, async (logs) =>
string statusText = await statusResponse.Content.ReadAsStringAsync();
this._outputHelper.WriteLine($"HITL orchestration status text: {statusText}");
- JsonElement statusResult = JsonSerializer.Deserialize(statusText);
+ JsonElement statusResult = JsonElement.Parse(statusText);
// The orchestration should complete with a failed status due to timeout
Assert.Equal("Failed", statusResult.GetProperty("runtimeStatus").GetString());
@@ -423,7 +423,7 @@ private async Task TestSpamDetectionAsync(string emailId, string emailContent, b
Assert.True(startResponse.IsSuccessStatusCode, $"Start orchestration failed with status: {startResponse.StatusCode}");
string startResponseText = await startResponse.Content.ReadAsStringAsync();
- JsonElement startResult = JsonSerializer.Deserialize(startResponseText);
+ JsonElement startResult = JsonElement.Parse(startResponseText);
Assert.True(startResult.TryGetProperty("statusQueryGetUri", out JsonElement statusUriElement));
Uri statusUri = new(statusUriElement.GetString()!);
@@ -436,7 +436,7 @@ private async Task TestSpamDetectionAsync(string emailId, string emailContent, b
Assert.True(statusResponse.IsSuccessStatusCode, $"Status check failed with status: {statusResponse.StatusCode}");
string statusText = await statusResponse.Content.ReadAsStringAsync();
- JsonElement statusResult = JsonSerializer.Deserialize(statusText);
+ JsonElement statusResult = JsonElement.Parse(statusText);
Assert.Equal("Completed", statusResult.GetProperty("runtimeStatus").GetString());
Assert.True(statusResult.TryGetProperty("output", out JsonElement outputElement));
@@ -722,15 +722,12 @@ private async Task WaitForOrchestrationCompletionAsync(Uri statusUri)
if (response.IsSuccessStatusCode)
{
string responseText = await response.Content.ReadAsStringAsync(timeoutCts.Token);
- JsonElement result = JsonSerializer.Deserialize(responseText);
+ JsonElement result = JsonElement.Parse(responseText);
- if (result.TryGetProperty("runtimeStatus", out JsonElement statusElement))
+ if (result.TryGetProperty("runtimeStatus", out JsonElement statusElement) &&
+ statusElement.GetString() is "Completed" or "Failed" or "Terminated")
{
- string status = statusElement.GetString()!;
- if (status == "Completed" || status == "Failed" || status == "Terminated")
- {
- return;
- }
+ return;
}
}
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs
index c9a13d7298c..7d3a2ec13e8 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/DurableAgentFunctionMetadataTransformerTests.cs
@@ -134,7 +134,7 @@ static FunctionsAgentOptions CreateFunctionsAgentOptions(bool httpEnabled, bool
Assert.Contains($"agents/{agentName}/run", httpMeta.RawBindings[0]);
// We expect 2 mcp tool triggers only for agentB and agentC
- if (agentName == "agentB" || agentName == "agentC")
+ if (agentName is "agentB" or "agentC")
{
DefaultFunctionMetadata? mcpToolMeta =
Assert.Single(metadataList, m => m.Name == $"mcptool-{agentName}") as DefaultFunctionMetadata;
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj
index d3842800b95..7b053abe832 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
enable
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/ContentTypeEventGeneratorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/ContentTypeEventGeneratorTests.cs
index 5a8f4ea442e..1be9d06ca7e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/ContentTypeEventGeneratorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/ContentTypeEventGeneratorTests.cs
@@ -47,7 +47,7 @@ public async Task TextReasoningContent_GeneratesReasoningItem_SuccessAsync()
var firstItemAddedEvent = events.First(e => e.GetProperty("type").GetString() == "response.output_item.added");
var firstItem = firstItemAddedEvent.GetProperty("item");
Assert.Equal("reasoning", firstItem.GetProperty("type").GetString());
- Assert.True(firstItemAddedEvent.GetProperty("output_index").GetInt32() == 0);
+ Assert.Equal(0, firstItemAddedEvent.GetProperty("output_index").GetInt32());
// Verify reasoning item done
var firstItemDoneEvent = events.First(e =>
@@ -153,7 +153,7 @@ public async Task ErrorContent_GeneratesRefusalItem_SuccessAsync()
// Verify item added event
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var item = itemAddedEvent.GetProperty("item");
Assert.Equal("message", item.GetProperty("type").GetString());
@@ -166,7 +166,7 @@ public async Task ErrorContent_GeneratesRefusalItem_SuccessAsync()
Assert.NotEmpty(contentArray);
var refusalContent = contentArray.First(c => c.GetProperty("type").GetString() == "refusal");
- Assert.True(refusalContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, refusalContent.ValueKind);
Assert.Equal(ErrorMessage, refusalContent.GetProperty("refusal").GetString());
}
@@ -246,12 +246,12 @@ public async Task ImageContent_UriContent_GeneratesImageItem_SuccessAsync()
// Assert
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var imageContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_image");
- Assert.True(imageContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, imageContent.ValueKind);
Assert.Equal(ImageUrl, imageContent.GetProperty("image_url").GetString());
}
@@ -270,12 +270,12 @@ public async Task ImageContent_DataContent_GeneratesImageItem_SuccessAsync()
// Assert
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var imageContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_image");
- Assert.True(imageContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, imageContent.ValueKind);
Assert.Equal(DataUri, imageContent.GetProperty("image_url").GetString());
}
@@ -295,12 +295,12 @@ public async Task ImageContent_WithDetailProperty_IncludesDetail_SuccessAsync()
// Assert
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var imageContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_image");
- Assert.True(imageContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, imageContent.ValueKind);
Assert.True(imageContent.TryGetProperty("detail", out var detailProp));
Assert.Equal(Detail, detailProp.GetString());
}
@@ -345,12 +345,12 @@ public async Task AudioContent_Mp3Format_GeneratesAudioItem_SuccessAsync()
// Assert
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var audioContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_audio");
- Assert.True(audioContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, audioContent.ValueKind);
Assert.Equal(AudioDataUri, audioContent.GetProperty("data").GetString());
Assert.Equal("mp3", audioContent.GetProperty("format").GetString());
}
@@ -421,12 +421,12 @@ public async Task HostedFileContent_GeneratesFileItem_SuccessAsync()
// Assert
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var fileContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_file");
- Assert.True(fileContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, fileContent.ValueKind);
Assert.Equal(FileId, fileContent.GetProperty("file_id").GetString());
}
@@ -471,12 +471,12 @@ public async Task FileContent_WithDataUri_GeneratesFileItem_SuccessAsync()
// Assert
var itemAddedEvent = events.FirstOrDefault(e => e.GetProperty("type").GetString() == "response.output_item.added");
- Assert.True(itemAddedEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, itemAddedEvent.ValueKind);
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var fileContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_file");
- Assert.True(fileContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, fileContent.ValueKind);
Assert.Equal(FileDataUri, fileContent.GetProperty("file_data").GetString());
Assert.Equal(Filename, fileContent.GetProperty("filename").GetString());
}
@@ -499,7 +499,7 @@ public async Task FileContent_WithoutFilename_GeneratesFileItemWithoutFilename_S
var content = itemAddedEvent.GetProperty("item").GetProperty("content");
var fileContent = content.EnumerateArray().First(c => c.GetProperty("type").GetString() == "input_file");
- Assert.True(fileContent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, fileContent.ValueKind);
Assert.Equal(FileDataUri, fileContent.GetProperty("file_data").GetString());
// filename property might be null or absent
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/FunctionApprovalTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/FunctionApprovalTests.cs
index c9a76e49904..296217f9313 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/FunctionApprovalTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/FunctionApprovalTests.cs
@@ -95,7 +95,7 @@ public async Task FunctionApprovalRequest_WithComplexArguments_GeneratesCorrectE
// Assert
JsonElement approvalEvent = events.FirstOrDefault(e =>
e.GetProperty("type").GetString() == "response.function_approval.requested");
- Assert.True(approvalEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, approvalEvent.ValueKind);
JsonElement functionCallElement = approvalEvent.GetProperty("function_call");
JsonElement argumentsElement = functionCallElement.GetProperty("arguments");
@@ -235,7 +235,7 @@ public async Task FunctionApprovalResponse_Rejected_GeneratesCorrectEvent_Succes
// Assert
JsonElement approvalEvent = events.FirstOrDefault(e =>
e.GetProperty("type").GetString() == "response.function_approval.responded");
- Assert.True(approvalEvent.ValueKind != JsonValueKind.Undefined);
+ Assert.NotEqual(JsonValueKind.Undefined, approvalEvent.ValueKind);
Assert.Equal(RequestId, approvalEvent.GetProperty("request_id").GetString());
Assert.False(approvalEvent.GetProperty("approved").GetBoolean());
@@ -340,7 +340,7 @@ public async Task MixedContent_MultipleApprovalRequests_GeneratesMultipleEvents_
private static List ParseSseEvents(string sseContent)
{
- List events = new();
+ List events = [];
string[] lines = sseContent.Split('\n');
for (int i = 0; i < lines.Length; i++)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj
index 7d64f7ae2b0..17d9742436c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj
@@ -1,18 +1,19 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
false
$(NoWarn);OPENAI001;CA1812
-
-
+
-
-
+
+
+
+
+
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIChatCompletionsConformanceTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIChatCompletionsConformanceTests.cs
index 8a383890355..ad7e6410f83 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIChatCompletionsConformanceTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIChatCompletionsConformanceTests.cs
@@ -138,7 +138,7 @@ public async Task BasicRequestResponseAsync()
AssertJsonPropertyExists(response, "service_tier");
var serviceTier = response.GetProperty("service_tier").GetString();
Assert.NotNull(serviceTier);
- Assert.True(serviceTier == "default" || serviceTier == "auto", $"service_tier should be 'default' or 'auto', got '{serviceTier}'");
+ Assert.True(serviceTier is "default" or "auto", $"service_tier should be 'default' or 'auto', got '{serviceTier}'");
}
[Fact]
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIConversationsSerializationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIConversationsSerializationTests.cs
index ecbdba4a532..7dc700abe68 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIConversationsSerializationTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIConversationsSerializationTests.cs
@@ -329,7 +329,7 @@ public void Deserialize_AllItemResponses_HaveRequiredFields()
Assert.NotNull(item);
Assert.NotNull(item.Id);
Assert.Equal("message", item.Type);
- var messageItem = Assert.IsAssignableFrom(item);
+ var messageItem = Assert.IsType(item, exactMatch: false);
// Content is on concrete message types (ResponsesAssistantMessageItemResource, etc.)
// For this test, we just verify the type is correct
Assert.NotNull(messageItem);
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIHttpApiIntegrationTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIHttpApiIntegrationTests.cs
index 1a72b252b56..a76820fff1e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIHttpApiIntegrationTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/OpenAIHttpApiIntegrationTests.cs
@@ -209,7 +209,7 @@ public async Task CreateConversationAndResponse_NonStreaming_Background_UpdatesC
// Assert - Response is in progress or queued
string status = response.GetProperty("status").GetString()!;
- Assert.True(status == "in_progress" || status == "queued" || status == "completed", $"Expected 'in_progress', 'queued', or 'completed', got '{status}'");
+ Assert.True(status is "in_progress" or "queued" or "completed", $"Expected 'in_progress', 'queued', or 'completed', got '{status}'");
string responseId = response.GetProperty("id").GetString()!;
// Wait for completion by polling
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/TestHelpers.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/TestHelpers.cs
index 11a0c1940dc..c3054e0296d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/TestHelpers.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/TestHelpers.cs
@@ -516,7 +516,7 @@ public ToolCallMockChatClient(string functionName, string argumentsJson)
this._functionName = functionName;
// Parse JSON arguments into dictionary
using var doc = System.Text.Json.JsonDocument.Parse(argumentsJson);
- this._arguments = new Dictionary();
+ this._arguments = [];
foreach (var prop in doc.RootElement.EnumerateObject())
{
this._arguments[prop.Name] = prop.Value.ValueKind switch
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs
index 3d96567e850..03ab65c9f2e 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/AgentHostingServiceCollectionExtensionsTests.cs
@@ -58,7 +58,7 @@ public void AddAIAgentWithKey_NullName_ThrowsArgumentNullException()
public void AddAIAgentWithKey_NullInstructions_AllowsNull()
{
var services = new ServiceCollection();
- var result = services.AddAIAgent("agentName", null!, "key");
+ var result = services.AddAIAgent("agentName", null, "key");
Assert.NotNull(result);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderAgentExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderAgentExtensionsTests.cs
index a29a6208f98..0036a60cc73 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderAgentExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderAgentExtensionsTests.cs
@@ -67,7 +67,7 @@ public void AddAIAgentWithKey_NullName_ThrowsArgumentNullException()
public void AddAIAgentWithKey_NullInstructions_AllowsNull()
{
var builder = new HostApplicationBuilder();
- var result = builder.AddAIAgent("agentName", null!, "key");
+ var result = builder.AddAIAgent("agentName", null, "key");
Assert.NotNull(result);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderWorkflowExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderWorkflowExtensionsTests.cs
index a1b7d29f55f..d27b9a17e33 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderWorkflowExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostApplicationBuilderWorkflowExtensionsTests.cs
@@ -59,7 +59,7 @@ public void AddWorkflow_ValidParameters_ReturnsBuilder()
var result = builder.AddWorkflow("workflowName", (sp, key) => CreateTestWorkflow(key));
Assert.NotNull(result);
- Assert.IsAssignableFrom(result);
+ Assert.IsType(result, exactMatch: false);
}
///
@@ -234,7 +234,7 @@ public void AddAsAIAgent_ReturnsHostedAgentBuilder()
var agentBuilder = workflowBuilder.AddAsAIAgent(AgentName);
Assert.NotNull(agentBuilder);
- Assert.IsAssignableFrom(agentBuilder);
+ Assert.IsType(agentBuilder, exactMatch: false);
Assert.Equal(AgentName, agentBuilder.Name);
}
@@ -251,7 +251,7 @@ public void AddAsAIAgent_WithoutName_ReturnsHostedAgentBuilderWithWorkflowName()
var agentBuilder = workflowBuilder.AddAsAIAgent();
Assert.NotNull(agentBuilder);
- Assert.IsAssignableFrom(agentBuilder);
+ Assert.IsType(agentBuilder, exactMatch: false);
Assert.Equal(WorkflowName, agentBuilder.Name);
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostedAgentBuilderToolsExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostedAgentBuilderToolsExtensionsTests.cs
index 9993007de1b..a229c7e1f83 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostedAgentBuilderToolsExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/HostedAgentBuilderToolsExtensionsTests.cs
@@ -118,9 +118,7 @@ private static IList ResolveAgentTools(IServiceProvider serviceProvider,
///
/// Dummy AITool implementation for testing.
///
- private sealed class DummyAITool : AITool
- {
- }
+ private sealed class DummyAITool : AITool;
///
/// Mock chat client for testing.
diff --git a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj
index 087c58ca92b..1279b20397d 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj
@@ -1,8 +1,7 @@
- $(ProjectsCoreTargetFrameworks)
- $(ProjectsDebugCoreTargetFrameworks)
+ $(TargetFrameworksCore)
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj
index 190d38e1dd2..99b028963aa 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj
@@ -1,8 +1,6 @@
- $(ProjectsTargetFrameworks)
- $(ProjectsDebugTargetFrameworks)
True
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs b/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs
index 46a5482f155..0515c8e7aca 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Mem0ProviderTests.cs
@@ -91,8 +91,8 @@ public async Task InvokingAsync_PerformsSearch_AndReturnsContextMessageAsync()
ThreadId = "thread",
UserId = "user"
};
- var sut = new Mem0Provider(this._httpClient, storageScope, loggerFactory: this._loggerFactoryMock.Object);
- var invokingContext = new AIContextProvider.InvokingContext(new[] { new ChatMessage(ChatRole.User, "What is my name?") });
+ var sut = new Mem0Provider(this._httpClient, storageScope, options: new() { EnableSensitiveTelemetryData = true }, loggerFactory: this._loggerFactoryMock.Object);
+ var invokingContext = new AIContextProvider.InvokingContext([new ChatMessage(ChatRole.User, "What is my name?")]);
// Act
var aiContext = await sut.InvokingAsync(invokingContext);
@@ -130,6 +130,60 @@ public async Task InvokingAsync_PerformsSearch_AndReturnsContextMessageAsync()
Times.Once);
}
+ [Theory]
+ [InlineData(false, false, 2)]
+ [InlineData(true, false, 2)]
+ [InlineData(false, true, 1)]
+ [InlineData(true, true, 1)]
+ public async Task InvokingAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogInvocations)
+ {
+ // Arrange
+ if (requestThrows)
+ {
+ this._handler.EnqueueEmptyInternalServerError();
+ }
+ else
+ {
+ this._handler.EnqueueJsonResponse("[ { \"id\": \"1\", \"memory\": \"Name is Caoimhe\", \"hash\": \"h\", \"metadata\": null, \"score\": 0.9, \"created_at\": \"2023-01-01T00:00:00Z\", \"updated_at\": null, \"user_id\": \"u\", \"app_id\": null, \"agent_id\": \"agent\", \"session_id\": \"thread\" } ]");
+ }
+
+ var storageScope = new Mem0ProviderScope
+ {
+ ApplicationId = "app",
+ AgentId = "agent",
+ ThreadId = "thread",
+ UserId = "user"
+ };
+ var options = new Mem0ProviderOptions { EnableSensitiveTelemetryData = enableSensitiveTelemetryData };
+
+ var sut = new Mem0Provider(this._httpClient, storageScope, options: options, loggerFactory: this._loggerFactoryMock.Object);
+ var invokingContext = new AIContextProvider.InvokingContext(new[] { new ChatMessage(ChatRole.User, "Who am I?") });
+
+ // Act
+ await sut.InvokingAsync(invokingContext, CancellationToken.None);
+
+ // Assert
+ Assert.Equal(expectedLogInvocations, this._loggerMock.Invocations.Count);
+ foreach (var logInvocation in this._loggerMock.Invocations)
+ {
+ var state = Assert.IsAssignableFrom>>(logInvocation.Arguments[2]);
+ var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
+ Assert.Equal(enableSensitiveTelemetryData ? "user" : "", userIdValue);
+
+ var inputValue = state.FirstOrDefault(kvp => kvp.Key == "Input").Value;
+ if (inputValue != null)
+ {
+ Assert.Equal(enableSensitiveTelemetryData ? "Who am I?" : "", inputValue);
+ }
+
+ var messageTextValue = state.FirstOrDefault(kvp => kvp.Key == "MessageText").Value;
+ if (messageTextValue != null)
+ {
+ Assert.Equal(enableSensitiveTelemetryData ? "## Memories\nConsider the following memories when answering user questions:\nName is Caoimhe" : "", messageTextValue);
+ }
+ }
+ }
+
[Fact]
public async Task InvokedAsync_PersistsAllowedMessagesAsync()
{
@@ -218,6 +272,55 @@ public async Task InvokedAsync_ShouldNotThrow_WhenStorageFailsAsync()
Times.Once);
}
+ [Theory]
+ [InlineData(false, false, 0)]
+ [InlineData(true, false, 0)]
+ [InlineData(false, true, 1)]
+ [InlineData(true, true, 1)]
+ public async Task InvokedAsync_LogsUserIdBasedOnEnableSensitiveTelemetryDataAsync(bool enableSensitiveTelemetryData, bool requestThrows, int expectedLogCount)
+ {
+ // Arrange
+ if (requestThrows)
+ {
+ this._handler.EnqueueEmptyInternalServerError();
+ }
+ else
+ {
+ this._handler.EnqueueJsonResponse("[ { \"id\": \"1\", \"memory\": \"Name is Caoimhe\", \"hash\": \"h\", \"metadata\": null, \"score\": 0.9, \"created_at\": \"2023-01-01T00:00:00Z\", \"updated_at\": null, \"user_id\": \"u\", \"app_id\": null, \"agent_id\": \"agent\", \"session_id\": \"thread\" } ]");
+ }
+
+ var storageScope = new Mem0ProviderScope
+ {
+ ApplicationId = "app",
+ AgentId = "agent",
+ ThreadId = "thread",
+ UserId = "user"
+ };
+
+ var options = new Mem0ProviderOptions { EnableSensitiveTelemetryData = enableSensitiveTelemetryData };
+ var sut = new Mem0Provider(this._httpClient, storageScope, options: options, loggerFactory: this._loggerFactoryMock.Object);
+ var requestMessages = new List
+ {
+ new(ChatRole.User, "User text")
+ };
+ var responseMessages = new List
+ {
+ new(ChatRole.Assistant, "Assistant text")
+ };
+
+ // Act
+ await sut.InvokedAsync(new AIContextProvider.InvokedContext(requestMessages, aiContextProviderMessages: null) { ResponseMessages = responseMessages });
+
+ // Assert
+ Assert.Equal(expectedLogCount, this._loggerMock.Invocations.Count);
+ foreach (var logInvocation in this._loggerMock.Invocations)
+ {
+ var state = Assert.IsAssignableFrom>>(logInvocation.Arguments[2]);
+ var userIdValue = state.First(kvp => kvp.Key == "UserId").Value;
+ Assert.Equal(enableSensitiveTelemetryData ? "user" : "", userIdValue);
+ }
+ }
+
[Fact]
public async Task ClearStoredMemoriesAsync_SendsDeleteWithQueryAsync()
{
@@ -316,7 +419,7 @@ public void Dispose()
private sealed class RecordingHandler : HttpMessageHandler
{
private readonly Queue _responses = new();
- public List<(HttpRequestMessage RequestMessage, string RequestBody)> Requests { get; } = new();
+ public List<(HttpRequestMessage RequestMessage, string RequestBody)> Requests { get; } = [];
protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj
index 1836f437d51..5abb64ca222 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj
@@ -1,9 +1,5 @@
-
- $(ProjectsTargetFrameworks)
-
-
false
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs
index 61e3f5ef57d..26f855d59e1 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIAssistantClientExtensionsTests.cs
@@ -4,6 +4,7 @@
using System.ClientModel;
using System.ClientModel.Primitives;
using System.IO;
+using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.AI;
@@ -91,7 +92,7 @@ public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly()
{
Name = "Test Agent",
Description = "Test description",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
@@ -222,7 +223,7 @@ public void GetAIAgent_WithClientResultAndOptions_WorksCorrectly()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -249,7 +250,7 @@ public void GetAIAgent_WithAssistantAndOptions_WorksCorrectly()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -298,7 +299,7 @@ public void GetAIAgent_WithAgentIdAndOptions_WorksCorrectly()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -325,7 +326,7 @@ public async Task GetAIAgentAsync_WithAgentIdAndOptions_WorksCorrectlyAsync()
{
Name = "Override Name",
Description = "Override Description",
- Instructions = "Override Instructions"
+ ChatOptions = new() { Instructions = "Override Instructions" }
};
// Act
@@ -455,6 +456,162 @@ public async Task GetAIAgentAsync_WithEmptyAgentId_ThrowsArgumentExceptionAsync(
Assert.Equal("agentId", exception.ParamName);
}
+ ///
+ /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var serviceProvider = new TestServiceProvider();
+ const string ModelId = "test-model";
+
+ // Act
+ var agent = assistantClient.CreateAIAgent(
+ ModelId,
+ instructions: "Test instructions",
+ name: "Test Agent",
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options and services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithOptionsAndServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var serviceProvider = new TestServiceProvider();
+ const string ModelId = "test-model";
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new() { Instructions = "Test instructions" }
+ };
+
+ // Act
+ var agent = assistantClient.CreateAIAgent(ModelId, options, services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that GetAIAgent with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void GetAIAgent_WithServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var serviceProvider = new TestServiceProvider();
+ var assistant = ModelReaderWriter.Read(BinaryData.FromString("""{"id": "asst_abc123", "name": "Test Agent"}"""))!;
+
+ // Act
+ var agent = assistantClient.GetAIAgent(assistant, services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that GetAIAgentAsync with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public async Task GetAIAgentAsync_WithServices_PassesServicesToAgentAsync()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var serviceProvider = new TestServiceProvider();
+
+ // Act
+ var agent = await assistantClient.GetAIAgentAsync("asst_abc123", services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that CreateAIAgent with both clientFactory and services works correctly.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactoryAndServices_AppliesBothCorrectly()
+ {
+ // Arrange
+ var assistantClient = new TestAssistantClient();
+ var serviceProvider = new TestServiceProvider();
+ var testChatClient = new TestChatClient(assistantClient.AsIChatClient("test-model"));
+ const string ModelId = "test-model";
+
+ // Act
+ var agent = assistantClient.CreateAIAgent(
+ ModelId,
+ instructions: "Test instructions",
+ name: "Test Agent",
+ clientFactory: (innerClient) => testChatClient,
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the custom chat client was applied
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+
+ // Verify the IServiceProvider was passed through
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Uses reflection to access the FunctionInvocationServices property which is not public.
+ ///
+ private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client)
+ {
+ var property = typeof(FunctionInvokingChatClient).GetProperty(
+ "FunctionInvocationServices",
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ return property?.GetValue(client) as IServiceProvider;
+ }
+
///
/// Creates a test AssistantClient implementation for testing.
///
@@ -488,6 +645,11 @@ public TestChatClient(IChatClient innerClient) : base(innerClient)
}
}
+ private sealed class TestServiceProvider : IServiceProvider
+ {
+ public object? GetService(Type serviceType) => null;
+ }
+
private sealed class FakePipelineResponse : PipelineResponse
{
public override int Status => throw new NotImplementedException();
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs
index 72ea0395e9b..ef9f27b01a6 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIChatClientExtensionsTests.cs
@@ -130,7 +130,7 @@ public void CreateAIAgent_WithOptionsAndClientFactory_AppliesFactoryCorrectly()
{
Name = "Test Agent",
Description = "Test description",
- Instructions = "Test instructions"
+ ChatOptions = new() { Instructions = "Test instructions" }
};
// Act
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs
index 2612f4bfa95..f31d343157a 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Extensions/OpenAIResponseClientExtensionsTests.cs
@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
+using System.Reflection;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
@@ -167,4 +168,114 @@ public void CreateAIAgent_WithNullOptions_ThrowsArgumentNullException()
Assert.Equal("options", exception.ParamName);
}
+
+ ///
+ /// Verify that CreateAIAgent with services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var responseClient = new TestOpenAIResponseClient();
+ var serviceProvider = new TestServiceProvider();
+
+ // Act
+ var agent = responseClient.CreateAIAgent(
+ instructions: "Test instructions",
+ name: "Test Agent",
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that CreateAIAgent with options and services parameter correctly passes it through to the ChatClientAgent.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithOptionsAndServices_PassesServicesToAgent()
+ {
+ // Arrange
+ var responseClient = new TestOpenAIResponseClient();
+ var serviceProvider = new TestServiceProvider();
+ var options = new ChatClientAgentOptions
+ {
+ Name = "Test Agent",
+ ChatOptions = new() { Instructions = "Test instructions" }
+ };
+
+ // Act
+ var agent = responseClient.CreateAIAgent(options, services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+ Assert.Equal("Test Agent", agent.Name);
+
+ // Verify the IServiceProvider was passed through to the FunctionInvokingChatClient
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// Verify that CreateAIAgent with both clientFactory and services works correctly.
+ ///
+ [Fact]
+ public void CreateAIAgent_WithClientFactoryAndServices_AppliesBothCorrectly()
+ {
+ // Arrange
+ var responseClient = new TestOpenAIResponseClient();
+ var serviceProvider = new TestServiceProvider();
+ var testChatClient = new TestChatClient(responseClient.AsIChatClient());
+
+ // Act
+ var agent = responseClient.CreateAIAgent(
+ instructions: "Test instructions",
+ name: "Test Agent",
+ clientFactory: (innerClient) => testChatClient,
+ services: serviceProvider);
+
+ // Assert
+ Assert.NotNull(agent);
+
+ // Verify the custom chat client was applied
+ var retrievedTestClient = agent.GetService();
+ Assert.NotNull(retrievedTestClient);
+ Assert.Same(testChatClient, retrievedTestClient);
+
+ // Verify the IServiceProvider was passed through
+ var chatClient = agent.GetService();
+ Assert.NotNull(chatClient);
+ var functionInvokingClient = chatClient.GetService();
+ Assert.NotNull(functionInvokingClient);
+ Assert.Same(serviceProvider, GetFunctionInvocationServices(functionInvokingClient));
+ }
+
+ ///
+ /// A simple test IServiceProvider implementation for testing.
+ ///
+ private sealed class TestServiceProvider : IServiceProvider
+ {
+ public object? GetService(Type serviceType) => null;
+ }
+
+ ///
+ /// Uses reflection to access the FunctionInvocationServices property which is not public.
+ ///
+ private static IServiceProvider? GetFunctionInvocationServices(FunctionInvokingChatClient client)
+ {
+ var property = typeof(FunctionInvokingChatClient).GetProperty(
+ "FunctionInvocationServices",
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ return property?.GetValue(client) as IServiceProvider;
+ }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj
index 7f26fdc1326..515ca2fb8d7 100644
--- a/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj
@@ -1,9 +1,5 @@
-
- $(ProjectsTargetFrameworks)
-
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj b/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj
index bd07eca8ab6..0129bba5d14 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj
+++ b/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj
@@ -1,9 +1,5 @@
-
- $(ProjectsTargetFrameworks)
-
-
diff --git a/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/PurviewClientTests.cs b/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/PurviewClientTests.cs
index b2b0ac45e62..3e45d8d4bd2 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/PurviewClientTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/PurviewClientTests.cs
@@ -220,10 +220,10 @@ public async Task GetProtectionScopesAsync_WithValidRequest_ReturnsSuccessRespon
var request = new ProtectionScopesRequest("test-user-id", "test-tenant-id")
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new("microsoft.graph.policyLocationApplication", "app-123")
- }
+ ]
};
var expectedResponse = new ProtectionScopesResponse
@@ -233,10 +233,10 @@ public async Task GetProtectionScopesAsync_WithValidRequest_ReturnsSuccessRespon
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new ("microsoft.graph.policyLocationApplication", "app-123")
- }
+ ]
}
}
};
@@ -502,7 +502,7 @@ private static ContentToProcess CreateValidContentToProcess()
};
return new ContentToProcess(
- new List { metadata },
+ [metadata],
activityMetadata,
deviceMetadata,
integratedAppMetadata,
@@ -554,9 +554,10 @@ protected override async Task SendAsync(HttpRequestMessage
throw new HttpRequestException("Simulated network error");
}
- var response = new HttpResponseMessage(this.StatusCodeToReturn);
-
- response.Content = new StringContent(this.ResponseToReturn ?? string.Empty, Encoding.UTF8, "application/json");
+ var response = new HttpResponseMessage(this.StatusCodeToReturn)
+ {
+ Content = new StringContent(this.ResponseToReturn ?? string.Empty, Encoding.UTF8, "application/json")
+ };
if (!string.IsNullOrEmpty(this.ETagToReturn))
{
diff --git a/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/ScopedContentProcessorTests.cs b/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/ScopedContentProcessorTests.cs
index f43f086de7a..9d56e0bc507 100644
--- a/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/ScopedContentProcessorTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.Purview.UnitTests/ScopedContentProcessorTests.cs
@@ -61,10 +61,10 @@ public async Task ProcessMessagesAsync_WithBlockAccessAction_ReturnsShouldBlockT
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new ("microsoft.graph.policyLocationApplication", "app-123")
- },
+ ],
ExecutionMode = ExecutionMode.EvaluateInline
}
}
@@ -120,10 +120,10 @@ public async Task ProcessMessagesAsync_WithRestrictionActionBlock_ReturnsShouldB
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new ("microsoft.graph.policyLocationApplication", "app-123")
- },
+ ],
ExecutionMode = ExecutionMode.EvaluateInline
}
}
@@ -179,10 +179,10 @@ public async Task ProcessMessagesAsync_WithNoBlockingActions_ReturnsShouldBlockF
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new("microsoft.graph.policyLocationApplication", "app-123")
- },
+ ],
ExecutionMode = ExecutionMode.EvaluateInline
}
}
@@ -234,10 +234,10 @@ public async Task ProcessMessagesAsync_UsesCachedProtectionScopes_WhenAvailableA
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new ("microsoft.graph.policyLocationApplication", "app-123")
- },
+ ],
ExecutionMode = ExecutionMode.EvaluateInline
}
}
@@ -290,10 +290,10 @@ public async Task ProcessMessagesAsync_InvalidatesCache_WhenProtectionScopeModif
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new ("microsoft.graph.policyLocationApplication", "app-123")
- },
+ ],
ExecutionMode = ExecutionMode.EvaluateInline
}
}
@@ -347,10 +347,10 @@ public async Task ProcessMessagesAsync_SendsContentActivities_WhenNoApplicableSc
new()
{
Activities = ProtectionScopeActivities.UploadText,
- Locations = new List
- {
+ Locations =
+ [
new ("microsoft.graph.policyLocationApplication", "app-456")
- }
+ ]
}
}
};
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AIAgentBuilderTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AIAgentBuilderTests.cs
index ca5803bba46..7f455327dcb 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/AIAgentBuilderTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/AIAgentBuilderTests.cs
@@ -257,11 +257,10 @@ public void BuildsPipelineInOrderAdded()
{
// Arrange
var mockInnerAgent = new Mock();
- var builder = new AIAgentBuilder(mockInnerAgent.Object);
-
- builder.Use(next => new InnerAgentCapturingAgent("First", next));
- builder.Use(next => new InnerAgentCapturingAgent("Second", next));
- builder.Use(next => new InnerAgentCapturingAgent("Third", next));
+ var builder = new AIAgentBuilder(mockInnerAgent.Object)
+ .Use(next => new InnerAgentCapturingAgent("First", next))
+ .Use(next => new InnerAgentCapturingAgent("Second", next))
+ .Use(next => new InnerAgentCapturingAgent("Third", next));
// Act
var first = (InnerAgentCapturingAgent)builder.Build();
@@ -306,7 +305,7 @@ public void UsesEmptyServiceProviderWhenNoServicesProvided()
{
Assert.Null(serviceProvider.GetService(typeof(object)));
- var keyedServiceProvider = Assert.IsAssignableFrom(serviceProvider);
+ var keyedServiceProvider = Assert.IsType(serviceProvider, exactMatch: false);
Assert.Null(keyedServiceProvider.GetKeyedService(typeof(object), "key"));
Assert.Throws(() => keyedServiceProvider.GetRequiredKeyedService(typeof(object), "key"));
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs
index dc983ef2022..58cf5f718fb 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentOptionsTests.cs
@@ -19,7 +19,6 @@ public void DefaultConstructor_InitializesWithNullValues()
// Assert
Assert.Null(options.Name);
- Assert.Null(options.Instructions);
Assert.Null(options.Description);
Assert.Null(options.ChatOptions);
Assert.Null(options.ChatMessageStoreFactory);
@@ -27,90 +26,44 @@ public void DefaultConstructor_InitializesWithNullValues()
}
[Fact]
- public void ParameterizedConstructor_WithNullValues_SetsPropertiesCorrectly()
+ public void Constructor_WithNullValues_SetsPropertiesCorrectly()
{
// Act
- var options = new ChatClientAgentOptions(
- instructions: null,
- name: null,
- description: null,
- tools: null);
+ var options = new ChatClientAgentOptions() { Name = null, Description = null, ChatOptions = new() { Tools = null, Instructions = null } };
// Assert
Assert.Null(options.Name);
- Assert.Null(options.Instructions);
Assert.Null(options.Description);
- Assert.Null(options.ChatOptions);
Assert.Null(options.AIContextProviderFactory);
- }
-
- [Fact]
- public void ParameterizedConstructor_WithInstructionsOnly_SetsChatOptionsWithInstructions()
- {
- // Arrange
- const string Instructions = "Test instructions";
-
- // Act
- var options = new ChatClientAgentOptions(
- instructions: Instructions,
- name: null,
- description: null,
- tools: null);
-
- // Assert
- Assert.Null(options.Name);
- Assert.Equal(Instructions, options.Instructions);
- Assert.Null(options.Description);
- Assert.Null(options.ChatOptions);
- }
-
- [Fact]
- public void ParameterizedConstructor_WithToolsOnly_SetsChatOptionsWithTools()
- {
- // Arrange
- var tools = new List { AIFunctionFactory.Create(() => "test") };
-
- // Act
- var options = new ChatClientAgentOptions(
- instructions: null,
- name: null,
- description: null,
- tools: tools);
-
- // Assert
- Assert.Null(options.Name);
- Assert.Null(options.Instructions);
- Assert.Null(options.Description);
+ Assert.Null(options.ChatMessageStoreFactory);
Assert.NotNull(options.ChatOptions);
Assert.Null(options.ChatOptions.Instructions);
- Assert.Same(tools, options.ChatOptions.Tools);
+ Assert.Null(options.ChatOptions.Tools);
}
[Fact]
- public void ParameterizedConstructor_WithInstructionsAndTools_SetsChatOptionsWithBoth()
+ public void Constructor_WithToolsOnly_SetsChatOptionsWithTools()
{
// Arrange
- const string Instructions = "Test instructions";
var tools = new List { AIFunctionFactory.Create(() => "test") };
// Act
- var options = new ChatClientAgentOptions(
- instructions: Instructions,
- name: null,
- description: null,
- tools: tools);
+ var options = new ChatClientAgentOptions()
+ {
+ Name = null,
+ Description = null,
+ ChatOptions = new() { Tools = tools }
+ };
// Assert
Assert.Null(options.Name);
- Assert.Equal(Instructions, options.Instructions);
Assert.Null(options.Description);
Assert.NotNull(options.ChatOptions);
- Assert.Null(options.ChatOptions.Instructions);
- Assert.Same(tools, options.ChatOptions.Tools);
+ AssertSameTools(tools, options.ChatOptions.Tools);
}
[Fact]
- public void ParameterizedConstructor_WithAllParameters_SetsAllPropertiesCorrectly()
+ public void Constructor_WithAllParameters_SetsAllPropertiesCorrectly()
{
// Arrange
const string Instructions = "Test instructions";
@@ -119,38 +72,37 @@ public void ParameterizedConstructor_WithAllParameters_SetsAllPropertiesCorrectl
var tools = new List { AIFunctionFactory.Create(() => "test") };
// Act
- var options = new ChatClientAgentOptions(
- instructions: Instructions,
- name: Name,
- description: Description,
- tools: tools);
+ var options = new ChatClientAgentOptions()
+ {
+ Name = Name,
+ Description = Description,
+ ChatOptions = new() { Tools = tools, Instructions = Instructions }
+ };
// Assert
Assert.Equal(Name, options.Name);
- Assert.Equal(Instructions, options.Instructions);
+ Assert.Equal(Instructions, options.ChatOptions.Instructions);
Assert.Equal(Description, options.Description);
Assert.NotNull(options.ChatOptions);
- Assert.Null(options.ChatOptions.Instructions);
- Assert.Same(tools, options.ChatOptions.Tools);
+ AssertSameTools(tools, options.ChatOptions.Tools);
}
[Fact]
- public void ParameterizedConstructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions()
+ public void Constructor_WithNameAndDescriptionOnly_DoesNotCreateChatOptions()
{
// Arrange
const string Name = "Test name";
const string Description = "Test description";
// Act
- var options = new ChatClientAgentOptions(
- instructions: null,
- name: Name,
- description: Description,
- tools: null);
+ var options = new ChatClientAgentOptions()
+ {
+ Name = Name,
+ Description = Description,
+ };
// Assert
Assert.Equal(Name, options.Name);
- Assert.Null(options.Instructions);
Assert.Equal(Description, options.Description);
Assert.Null(options.ChatOptions);
}
@@ -159,7 +111,6 @@ public void ParameterizedConstructor_WithNameAndDescriptionOnly_DoesNotCreateCha
public void Clone_CreatesDeepCopyWithSameValues()
{
// Arrange
- const string Instructions = "Test instructions";
const string Name = "Test name";
const string Description = "Test description";
var tools = new List { AIFunctionFactory.Create(() => "test") };
@@ -171,8 +122,11 @@ static AIContextProvider AIContextProviderFactory(
ChatClientAgentOptions.AIContextProviderFactoryContext ctx) =>
new Mock().Object;
- var original = new ChatClientAgentOptions(Instructions, Name, Description, tools)
+ var original = new ChatClientAgentOptions()
{
+ Name = Name,
+ Description = Description,
+ ChatOptions = new() { Tools = tools },
Id = "test-id",
ChatMessageStoreFactory = ChatMessageStoreFactory,
AIContextProviderFactory = AIContextProviderFactory
@@ -185,7 +139,6 @@ static AIContextProvider AIContextProviderFactory(
Assert.NotSame(original, clone);
Assert.Equal(original.Id, clone.Id);
Assert.Equal(original.Name, clone.Name);
- Assert.Equal(original.Instructions, clone.Instructions);
Assert.Equal(original.Description, clone.Description);
Assert.Same(original.ChatMessageStoreFactory, clone.ChatMessageStoreFactory);
Assert.Same(original.AIContextProviderFactory, clone.AIContextProviderFactory);
@@ -197,14 +150,13 @@ static AIContextProvider AIContextProviderFactory(
}
[Fact]
- public void Clone_WithNullChatOptions_ClonesCorrectly()
+ public void Clone_WithoutProvidingChatOptions_ClonesCorrectly()
{
// Arrange
var original = new ChatClientAgentOptions
{
Id = "test-id",
Name = "Test name",
- Instructions = "Test instructions",
Description = "Test description"
};
@@ -215,10 +167,19 @@ public void Clone_WithNullChatOptions_ClonesCorrectly()
Assert.NotSame(original, clone);
Assert.Equal(original.Id, clone.Id);
Assert.Equal(original.Name, clone.Name);
- Assert.Equal(original.Instructions, clone.Instructions);
Assert.Equal(original.Description, clone.Description);
- Assert.Null(clone.ChatOptions);
+ Assert.Null(original.ChatOptions);
Assert.Null(clone.ChatMessageStoreFactory);
Assert.Null(clone.AIContextProviderFactory);
}
+
+ private static void AssertSameTools(IList? expected, IList? actual)
+ {
+ var index = 0;
+ foreach (var tool in expected ?? [])
+ {
+ Assert.Same(tool, actual?[index]);
+ index++;
+ }
+ }
}
diff --git a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
index 862b9ef3b40..6e9d952b57c 100644
--- a/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
+++ b/dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgentTests.cs
@@ -31,7 +31,7 @@ public void VerifyChatClientAgentDefinition()
Id = "test-agent-id",
Name = "test name",
Description = "test description",
- Instructions = "test instructions",
+ ChatOptions = new() { Instructions = "test instructions" },
});
// Assert
@@ -65,7 +65,7 @@ public async Task VerifyChatClientAgentInvocationAsync()
ChatClientAgent agent =
new(mockService.Object, options: new()
{
- Instructions = "test instructions"
+ ChatOptions = new() { Instructions = "base instructions" },
});
// Act
@@ -99,7 +99,7 @@ public async Task RunAsyncThrowsArgumentNullExceptionWhenMessagesIsNullAsync()
{
// Arrange
var chatClient = new Mock().Object;
- ChatClientAgent agent = new(chatClient, options: new() { Instructions = "test instructions" });
+ ChatClientAgent agent = new(chatClient, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
// Act & Assert
await Assert.ThrowsAsync(() => agent.RunAsync((IReadOnlyCollection)null!));
@@ -120,7 +120,7 @@ public async Task RunAsyncPassesChatOptionsWhenUsingChatClientAgentRunOptionsAsy
It.Is(opts => opts.MaxOutputTokens == 100),
It.IsAny())).ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
- ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "test instructions" });
+ ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "test instructions" } });
// Act
await agent.RunAsync([new(ChatRole.User, "test")], options: new ChatClientAgentRunOptions(chatOptions));
@@ -181,7 +181,7 @@ public async Task RunAsyncIncludesBaseInstructionsInOptionsAsync()
capturedMessages.AddRange(msgs))
.ReturnsAsync(new ChatResponse([new(ChatRole.Assistant, "response")]));
- ChatClientAgent agent = new(mockService.Object, options: new() { Instructions = "base instructions" });
+ ChatClientAgent agent = new(mockService.Object, options: new() { ChatOptions = new() { Instructions = "base instructions" } });
var runOptions = new AgentRunOptions();
// Act
@@ -212,7 +212,7 @@ public async Task RunAsyncSetsAuthorNameOnAllResponseMessagesAsync(string? autho
It.IsAny(),
It.IsAny