Compare commits

..
Author SHA1 Message Date
Dmytro Struk 1a6ff77fcc Added AI Search example 2025-11-12 13:59:31 -08:00
Dmytro StrukandGitHub 562064cbde Merge branch 'main' into feature-python-foundry-agents 2025-11-12 12:59:17 -08:00
Dmytro Struk 5c74c3fd9c Addressed PR feedback 2025-11-12 12:48:23 -08:00
Dmytro StrukandGitHub 71358853fb Python: [Feature Branch] Resolve CI issues (#2143)
* Small documentation and code fixes

* Small fix in documentation
2025-11-12 11:54:40 -08:00
Dmytro StrukandGitHub 693d5c941d Updated azure-ai-projects package version and small fixes (#2139) 2025-11-12 10:40:35 -08:00
Dmytro Struk f309a2818b Merge branch 'main' into feature-python-foundry-agents 2025-11-12 07:53:38 -08:00
Dmytro Struk f55ac70178 Merge branch 'main' into feature-python-foundry-agents 2025-11-11 23:17:45 -08:00
+19
Dmytro StrukGitHubdependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>ChrisCopilotkzucopilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>Reuben BondPeter IbekweJeff HandleyDaniel RothVictor DibiaMark WallaceShawn HenryJavier Calvarro NelsonEvan MattsonEduard van ValkenburgKorolev Dmitrywesteydependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>Reuben BondTao ChenwuwengRoger BarretoSergeyMenshykhCopilotJacob AlberGiles OdigweDaniel Cazzulino
361c47f30f Python: [Feature Branch] Merge from main to Azure AI branch (#2111)
* Do not build DevUI assets during .NET project build (#2010)

* .NET: Add unit tests for declarative executor SetMultipleVariables (#2016)

* Add unit tests for create conversation executor

* Update indentation and comment typo.

* Added unit tests for declarative executor SetMultipleVariablesExecutor

* Updated comments and syntactic sugar

* Python: DevUI: Use metadata.entity_id instead of model field (#1984)

* DevUI: Use metadata.entity_id for agent/workflow name instead of model field

* OpenAI Responses: add explicit request validation

* Review feedback

* .NET: DevUI - Do not automatically add/map OpenAI services/endpoints (#2014)

* Don't add OpenAIResponses as part of Dev UI

You should be able to add and remove Dev UI without impacting your other production endpoints.

* Remove `AddDevUI()` and do not map OpenAI endpoints from `MapDevUI()`

* Fix comment wording

* Revise documentation

---------

Co-authored-by: Daniel Roth <daroth@microsoft.com>

* Python: DevUI: Add OpenAI Responses API proxy support  + HIL for Workflows (#1737)

* DevUI: Add OpenAI Responses API proxy support with enhanced UI features

This commit adds support for proxying requests to OpenAI's Responses API,
allowing DevUI to route conversations to OpenAI models when configured to enable testing.

Backend changes:
- Add OpenAI proxy executor with conversation routing logic
- Enhance event mapper to support OpenAI Responses API format
- Extend server endpoints to handle OpenAI proxy mode
- Update models with OpenAI-specific response types
- Remove emojis from logging and CLI output for cleaner text

Frontend changes:
- Add settings modal with OpenAI proxy configuration UI
- Enhance agent and workflow views with improved state management
- Add new UI components (separator, switch) for settings
- Update debug panel with better event filtering
- Improve message renderers for OpenAI content types
- Update types and API client for OpenAI integration

* update ui, settings modal and workflow input form, add register cleanup hooks.

* add workflow HIL support, user mode, other fixes

* feat(devui): add human-in-the-loop (HIL) support with dynamic response schemas

Implement  HIL workflow support allowing workflows to pause for user input
with dynamically generated JSON schemas based on response handler type hints.

Key Features:
- Automatic response schema extraction from @response_handler decorators
- Dynamic form generation in UI based on Pydantic/dataclass response types
- Checkpoint-based conversation storage for HIL requests/responses
- Resume workflow execution after user provides HIL response

Backend Changes:
- Add extract_response_type_from_executor() to introspect response handlers
- Enrich RequestInfoEvent with response_schema via _enrich_request_info_event_with_response_schema()
- Map RequestInfoEvent to response.input.requested OpenAI event format
- Store HIL responses in conversation history and restore checkpoints

Frontend Changes:
- Add HILInputModal component with SchemaFormRenderer for dynamic forms
- Support Pydantic BaseModel and dataclass response types
- Render enum fields as dropdowns, strings as text/textarea, numbers, booleans, arrays, objects
- Display original request context alongside response form

Testing:
- Add  tests for checkpoint storage (test_checkpoints.py)
- Add schema generation tests for all input types (test_schema_generation.py)
- Validate end-to-end HIL flow with spam workflow sample

This enables workflows to seamlessly pause execution and request structured user input
with type-safe, validated forms generated automatically from response type annotations.

* improve HIL support, improve workflow execution view

* ui updates

* ui updates

* improve HIL for workflows, add auth and view modes

* update workflow

* security improvements , ui fixes

* fix mypy error

* update loading spinner in ui

---------

Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>

* .NET: Remove launchSettings.json from .gitignore in dotnet/samples (#2006)

* Remove launchSettings.json from .gitignore in dotnet/samples

* Update dotnet/samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/Properties/launchSettings.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update dotnet/samples/AGUIClientServer/AGUIServer/Properties/launchSettings.json

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* DevUI: Serialize workflow input as string to maintain conformance with OpenAI Responses format (#2021)

Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>

* Add Microsoft Agent Framework logo to assets (#2007)

* Updated package versions (#2027)

* DevUI: Prevent line breaks within words in the agent view (#2024)

Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>

* .NET [AG-UI]: Adds support for shared state. (#1996)

* Product changes

* Tests

* Dojo project

* Cleanups

* Python: Fix underlying tool choice bug and all for return to previous Handoff subagent (#2037)

* Fix tool_choice override bug and add enable_return_to_previous support

* Add unit test for handoff checkpointing

* Handle tools when we have them

* added missing chatAgent params (#2044)

* .NET: fix ChatCompletions Tools serialization (#2043)

* fix serialization in chat completions on tools

* nit

* .NET: assign AgentCard's URL to mapped-endpoint if not defined explicitly (#2047)

* fix serialization in chat completions on tools

* nit

* write e2e test for agent card resolve + adjust behavior

* nit

* Version 1.0.0-preview.251110.1 (#2048)

* .NET: Remove moved OpenAPI sample and point to SK one. (#1997)

* Remove moved OpenAPI sample and point to SK one.

* Update dotnet/samples/GettingStarted/Agents/README.md

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.2 to 4.0.4.6 (#2031)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.4.6
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* .NET: Separate all memory and rag samples into their own folders (#2000)

* Separate all memory and rag samples into their own folders

* Fix broken link.

* Python: .Net: Dotnet devui compatibility fixes (#2026)

* DevUI: Add OpenAI Responses API proxy support with enhanced UI features

This commit adds support for proxying requests to OpenAI's Responses API,
allowing DevUI to route conversations to OpenAI models when configured to enable testing.

Backend changes:
- Add OpenAI proxy executor with conversation routing logic
- Enhance event mapper to support OpenAI Responses API format
- Extend server endpoints to handle OpenAI proxy mode
- Update models with OpenAI-specific response types
- Remove emojis from logging and CLI output for cleaner text

Frontend changes:
- Add settings modal with OpenAI proxy configuration UI
- Enhance agent and workflow views with improved state management
- Add new UI components (separator, switch) for settings
- Update debug panel with better event filtering
- Improve message renderers for OpenAI content types
- Update types and API client for OpenAI integration

* update ui, settings modal and workflow input form, add register cleanup hooks.

* add workflow HIL support, user mode, other fixes

* feat(devui): add human-in-the-loop (HIL) support with dynamic response schemas

Implement  HIL workflow support allowing workflows to pause for user input
with dynamically generated JSON schemas based on response handler type hints.

Key Features:
- Automatic response schema extraction from @response_handler decorators
- Dynamic form generation in UI based on Pydantic/dataclass response types
- Checkpoint-based conversation storage for HIL requests/responses
- Resume workflow execution after user provides HIL response

Backend Changes:
- Add extract_response_type_from_executor() to introspect response handlers
- Enrich RequestInfoEvent with response_schema via _enrich_request_info_event_with_response_schema()
- Map RequestInfoEvent to response.input.requested OpenAI event format
- Store HIL responses in conversation history and restore checkpoints

Frontend Changes:
- Add HILInputModal component with SchemaFormRenderer for dynamic forms
- Support Pydantic BaseModel and dataclass response types
- Render enum fields as dropdowns, strings as text/textarea, numbers, booleans, arrays, objects
- Display original request context alongside response form

Testing:
- Add  tests for checkpoint storage (test_checkpoints.py)
- Add schema generation tests for all input types (test_schema_generation.py)
- Validate end-to-end HIL flow with spam workflow sample

This enables workflows to seamlessly pause execution and request structured user input
with type-safe, validated forms generated automatically from response type annotations.

* improve HIL support, improve workflow execution view

* ui updates

* ui updates

* improve HIL for workflows, add auth and view modes

* update workflow

* security improvements , ui fixes

* fix mypy error

* update loading spinner in ui

* DevUI: Serialize workflow input as string to maintain conformance with OpenAI Responses format

* Phase 1: Add /meta endpoint and fix workflow event naming for .NET DevUI compatibility

* additional fixes for .NET DevUI workflow visualization item ID tracking

**Problem:**
.NET DevUI was generating different item IDs for ExecutorInvokedEvent and
ExecutorCompletedEvent, causing only the first executor to highlight in the
workflow graph. Long executor names and error messages also broke UI layout.

**Changes:**
- Add ExecutorActionItemResource to match Python DevUI implementation
- Track item IDs per executor using dictionary in AgentRunResponseUpdateExtensions
- Reuse same item ID across invoked/completed/failed events for proper pairing
- Add truncateText() utility to workflow-utils.ts
- Truncate executor names to 35 chars in execution timeline
- Truncate error messages to 150 chars in workflow graph nodes

** Details:**
- ExecutorActionItemResource registered with JSON source generation context
- Dictionary cleaned up after executor completion/failure to prevent memory leaks
- Frontend item tracking by unique item.id supports multiple executor runs
- All changes follow existing codebase patterns and conventions

Tested with review-workflow showing correct executor highlighting and state
transitions for sequential and concurrent executors.

* format fixes, remove cors tests

* remove unecessary attributes

---------

Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Reuben Bond <reuben.bond@gmail.com>

* DevUI: support having both an agent and a workflow with the same id in discovery (#2023)

* Python: Fix Model ID attribute not showing up in `invoke_agent` span (#2061)

* Best effort to surface the model id to invoke agent span

* Fix tests

* Fix tests

* Version 1.0.0-preview.251107.2 (#2065)

* Version 1.0.0-preview.251110.2 (#2067)

* Update README.md to change Grafana links to Azure portal links for dashboard access (#1983)

* .NET - Enable build & test on branch `feature-foundry-agents` (#2068)

* Tests good, mkay

* Update .github/workflows/dotnet-build-and-test.yml

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Enable feature build pipelines

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>

* Python: Add concrete AGUIChatClient (#2072)

* Add concrete AGUIChatClient

* Update logging docstrings and conventions

* PR feedback

* Updates to support client-side tool calls

* .NET: Move catalog samples to the HostedAgents folder (#2090)

* move catalog samples to the HostedAgents folder

* move the catalog samples' projects to the HostedAgents folder

* Bump OpenTelemetry.Instrumentation.Runtime from 1.12.0 to 1.13.0 (#1856)

---
updated-dependencies:
- dependency-name: OpenTelemetry.Instrumentation.Runtime
  dependency-version: 1.13.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* .NET: Bump Microsoft.SemanticKernel.Agents.Abstractions from 1.66.0 to 1.67.0 (#1962)

* Bump Microsoft.SemanticKernel.Agents.Abstractions from 1.66.0 to 1.67.0

---
updated-dependencies:
- dependency-name: Microsoft.SemanticKernel.Agents.Abstractions
  dependency-version: 1.67.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>

* .NET: Bump all Microsoft.SemanticKernel packages from 1.66.* to 1.67.* (#1969)

* Initial plan

* Update all Microsoft.SemanticKernel packages to 1.67.*

Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>

* Remove unrelated changes to package-lock.json and yarn.lock

Co-authored-by: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: rogerbarreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: markwallace-microsoft <127216156+markwallace-microsoft@users.noreply.github.com>

* .NET: fix: WorkflowAsAgent Sample (#1787)

* fix: WorkflowAsAgent Sample

* Also makes ChatForwardingExecutor public

* feat: Expand ChatForwardingExecutor handled types

Make ChatForwardingExecutor match the input types of ChatProtocolExecutor.

* fix: Update for the new AgentRunResponseUpdate merge logic

AIAgent always sends out List<ChatMessage> now.

* Updated (#2076)

* Bump vite in /python/samples/demos/chatkit-integration/frontend (#1918)

Bumps [vite](https://github.com/vitejs/vite/tree/HEAD/packages/vite) from 7.1.9 to 7.1.12.
- [Release notes](https://github.com/vitejs/vite/releases)
- [Changelog](https://github.com/vitejs/vite/blob/v7.1.12/packages/vite/CHANGELOG.md)
- [Commits](https://github.com/vitejs/vite/commits/v7.1.12/packages/vite)

---
updated-dependencies:
- dependency-name: vite
  dependency-version: 7.1.12
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump Roslynator.Analyzers from 4.14.0 to 4.14.1 (#1857)

---
updated-dependencies:
- dependency-name: Roslynator.Analyzers
  dependency-version: 4.14.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump MishaKav/pytest-coverage-comment from 1.1.57 to 1.1.59 (#2034)

Bumps [MishaKav/pytest-coverage-comment](https://github.com/mishakav/pytest-coverage-comment) from 1.1.57 to 1.1.59.
- [Release notes](https://github.com/mishakav/pytest-coverage-comment/releases)
- [Changelog](https://github.com/MishaKav/pytest-coverage-comment/blob/main/CHANGELOG.md)
- [Commits](https://github.com/mishakav/pytest-coverage-comment/compare/v1.1.57...v1.1.59)

---
updated-dependencies:
- dependency-name: MishaKav/pytest-coverage-comment
  dependency-version: 1.1.59
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Python: Handle agent user input request in AgentExecutor (#2022)

* Handle agent user input request in AgentExecutor

* fix test

* Address comments

* Fix tests

* Fix tests

* Address comments

* Address comments

* Python: OpenAI Responses Image Generation Stream Support, Sample and Unit Tests (#1853)

* support for image gen streaming

* small fixes

* fixes

* added comment

* Python: Fix MCP Tool Parameter Descriptions Not Propagated to LLMs (#1978)

* mcp tool description fix

* small fix

* .NET: Allow extending agent run options via additional properties (#1872)

* Allow extending agent run options via additional properties

This mirrors the M.E.AI model in ChatOptions.AdditionalProperties which is very useful when building functionality pipelines.

Fixes https://github.com/microsoft/agent-framework/issues/1815

* Expand XML documentation

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add AdditionalProperties tests to AgentRunOptions

Co-authored-by: kzu <169707+kzu@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: kzu <169707+kzu@users.noreply.github.com>

* Python: Use the last entry in the task history to avoid empty responses (#2101)

* Use the last entry in the task history to avoid empty responses

* History only contains Messages

* Updated package versions (#2104)

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Reuben Bond <203839+ReubenBond@users.noreply.github.com>
Co-authored-by: Peter Ibekwe <109177538+peibekwe@users.noreply.github.com>
Co-authored-by: Jeff Handley <jeffhandley@users.noreply.github.com>
Co-authored-by: Daniel Roth <daroth@microsoft.com>
Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Shawn Henry <sphenry@gmail.com>
Co-authored-by: Javier Calvarro Nelson <jacalvar@microsoft.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Korolev Dmitry <deagle.gross@gmail.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Reuben Bond <reuben.bond@gmail.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
Co-authored-by: wuweng <wuweng@microsoft.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Daniel Cazzulino <daniel@cazzulino.com>
Co-authored-by: kzu <169707+kzu@users.noreply.github.com>
2025-11-11 23:12:09 -08:00
Dmytro Struk 85fcd230bf Revert "Merge from main"
This reverts commit b8206a85d7.
2025-11-11 18:44:25 -08:00
Dmytro Struk b8206a85d7 Merge from main 2025-11-11 18:28:38 -08:00
Dmytro StrukandGitHub 519bc9da0a Added handling for conversation_id (#2098) 2025-11-11 13:56:43 -08:00
26e73756c7 Python: [Feature Branch] Added more examples and fixes for Azure AI agent (#2077)
* Updated azure-ai-projects package version

* Added an example of hosted MCP with approval required

* Updated code interpreter example

* Added file search example

* Update python/samples/getting_started/agents/azure_ai/azure_ai_with_file_search.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update python/samples/getting_started/agents/azure_ai/azure_ai_with_file_search.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Small fix

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-11 07:49:42 -08:00
Dmytro StrukandGitHub c3ef6475a2 Python: [Feature Branch] Fixed "store" parameter handling (#2069)
* Fixed store parameter handling

* Small fix
2025-11-10 18:24:32 -08:00
Dmytro StrukandGitHub 476fbbefc3 Added hosted MCP support (#2018) 2025-11-07 13:55:21 -08:00
ChrisandGitHub cfcfd713d2 Merge branch 'main' into feature-python-foundry-agents 2025-11-07 10:20:41 -08:00
Dmytro StrukandGitHub 50d3e652ec Removed optional ID from FunctionResultContent (#2011) 2025-11-07 10:17:10 -08:00
Dmytro StrukandGitHub 9423c1763c Python: [Feature Branch] Structured Outputs and more examples for AzureAIClient (#1987)
* Small updates

* Added support for structured outputs

* Added code interpreter example

* More examples and fixes

* Added more examples and README

* Small fix

* Addressed PR feedback
2025-11-07 00:17:20 -08:00
915c749e41 Python: [Feature Branch] Added use_latest_version parameter to AzureAIClient (#1959)
* Added use_latest_version parameter to AzureAIClient

* Added unit tests

* Update python/samples/getting_started/agents/azure_ai/azure_ai_use_latest_version.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Update python/packages/azure-ai/agent_framework_azure_ai/_client.py

Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
2025-11-06 07:42:34 -08:00
Dmytro Struk ce4b5fec33 Small fix 2025-11-05 19:37:16 -08:00
Dmytro Struk d8228d3a9d Merge branch 'main' into feature-python-foundry-agents 2025-11-05 19:32:00 -08:00
Dmytro Struk 599c5c2bc6 Merge branch 'main' into feature-python-foundry-agents 2025-11-05 09:21:26 -08:00
Dmytro StrukandGitHub f23070a448 Merge branch 'main' into feature-python-foundry-agents 2025-11-05 08:09:07 -08:00
Dmytro Struk 320a4c7438 Merge branch 'main' into feature-python-foundry-agents 2025-11-05 08:04:26 -08:00
8135a99f9e Python: [Feature Branch] Renamed Azure AI agent and small fixes (#1919)
* Renaming

* Small fixes

* Update python/packages/core/agent_framework/openai/_shared.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-11-05 07:53:55 -08:00
Dmytro StrukandGitHub 39d3111734 Added changes (#1909) 2025-11-04 13:13:21 -08:00
1695 changed files with 14234 additions and 103305 deletions
+2 -3
View File
@@ -1,11 +1,10 @@
{
"name": "C# (.NET)",
"image": "mcr.microsoft.com/devcontainers/dotnet:10.0",
"image": "mcr.microsoft.com/devcontainers/dotnet:9.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/docker-in-docker:2.12.4": {}
"ghcr.io/devcontainers/features/azure-cli:1.2.8": {}
},
"workspaceFolder": "/workspaces/agent-framework/dotnet/",
"customizations": {
-4
View File
@@ -1,4 +0,0 @@
# Code ownership assignments
# https://docs.github.com/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners
python/packages/azurefunctions/ @microsoft/agentframework-durabletask-developers
@@ -1,36 +0,0 @@
name: Azure Functions Integration Test Setup
description: Prepare local emulators and tools for Azure Functions integration tests
runs:
using: "composite"
steps:
- name: Start Durable Task Scheduler Emulator
shell: bash
run: |
if [ "$(docker ps -aq -f name=dts-emulator)" ]; then
echo "Stopping and removing existing Durable Task Scheduler Emulator"
docker rm -f dts-emulator
fi
echo "Starting Durable Task Scheduler Emulator"
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
echo "Waiting for Durable Task Scheduler Emulator to be ready"
timeout 30 bash -c 'until curl --silent http://localhost:8080/healthz; do sleep 1; done'
echo "Durable Task Scheduler Emulator is ready"
- name: Start Azurite (Azure Storage emulator)
shell: bash
run: |
if [ "$(docker ps -aq -f name=azurite)" ]; then
echo "Stopping and removing existing Azurite (Azure Storage emulator)"
docker rm -f azurite
fi
echo "Starting Azurite (Azure Storage emulator)"
docker run -d --name azurite -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite
echo "Waiting for Azurite (Azure Storage emulator) to be ready"
timeout 30 bash -c 'until curl --silent http://localhost:10000/devstoreaccount1; do sleep 1; done'
echo "Azurite (Azure Storage emulator) is ready"
- name: Install Azure Functions Core Tools
shell: bash
run: |
echo "Installing Azure Functions Core Tools"
npm install -g azure-functions-core-tools@4 --unsafe-perm true
func --version
+3 -8
View File
@@ -11,6 +11,9 @@ updates:
schedule:
interval: "cron"
cronjob: "0 8 * * 4,0" # Every Thursday(4) and Sunday(0) at 8:00 UTC
experimental:
nuget-native-updater: false
enable-cooldown-metrics-collection: false
ignore:
# For all System.* and Microsoft.Extensions/Bcl.* packages, ignore all major version updates
- dependency-name: "System.*"
@@ -25,14 +28,6 @@ updates:
- "dependencies"
# Maintain dependencies for python
- package-ecosystem: "pip"
directory: "python/"
schedule:
interval: "weekly"
day: "monday"
labels:
- "python"
- "dependencies"
- package-ecosystem: "uv"
directory: "python/"
schedule:
@@ -1,17 +0,0 @@
---
applyTo: "dotnet/src/Microsoft.Agents.AI.DurableTask/**,dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**"
---
# Durable Task area code instructions
The following guidelines apply to pull requests that modify files under
`dotnet/src/Microsoft.Agents.AI.DurableTask/**` or
`dotnet/src/Microsoft.Agents.AI.Hosting.AzureFunctions/**`:
## CHANGELOG.md
- Each pull request that modifies code should add just one bulleted entry to the `CHANGELOG.md` file containing a change title (usually the PR title) and a link to the PR itself.
- New PRs should be added to the top of the `CHANGELOG.md` file under a "## [Unreleased]" heading.
- If the PR is the first since the last release, the existing "## [Unreleased]" heading should be replaced with a "## v[X.Y.Z]" heading and the PRs since the last release should be added to the new "## [Unreleased]" heading.
- The style of new `CHANGELOG.md` entries should match the style of the other entries in the file.
- If the PR introduces a breaking change, the changelog entry should be prefixed with "[BREAKING]".
@@ -142,9 +142,9 @@ Replace these Semantic Kernel agent classes with their Agent Framework equivalen
|----------------------|----------------------------|-------------------|
| `IChatCompletionService` | `IChatClient` | Convert to `IChatClient` using `chatService.AsChatClient()` extensions |
| `ChatCompletionAgent` | `ChatClientAgent` | Remove `Kernel` parameter, add `IChatClient` parameter |
| `OpenAIAssistantAgent` | `AIAgent` (via extension) | ⚠️ **Deprecated** - Use Responses API instead. <br> **New**: `OpenAIClient.GetAssistantClient().CreateAIAgent()` <br> **Existing**: `OpenAIClient.GetAssistantClient().GetAIAgent(assistantId)` |
| `OpenAIAssistantAgent` | `AIAgent` (via extension) | **New**: `OpenAIClient.GetAssistantClient().CreateAIAgent()` <br> **Existing**: `OpenAIClient.GetAssistantClient().GetAIAgent(assistantId)` |
| `AzureAIAgent` | `AIAgent` (via extension) | **New**: `PersistentAgentsClient.CreateAIAgent()` <br> **Existing**: `PersistentAgentsClient.GetAIAgent(agentId)` |
| `OpenAIResponseAgent` | `AIAgent` (via extension) | Replace with `OpenAIClient.GetOpenAIResponseClient(modelId).CreateAIAgent()` |
| `OpenAIResponseAgent` | `AIAgent` (via extension) | Replace with `OpenAIClient.GetOpenAIResponseClient().CreateAIAgent()` |
| `A2AAgent` | `AIAgent` (via extension) | Replace with `A2ACardResolver.GetAIAgentAsync()` |
| `BedrockAgent` | Not supported | Custom implementation required |
@@ -529,14 +529,14 @@ AIAgent agent = new OpenAIClient(apiKey)
.CreateAIAgent(instructions: instructions);
```
**OpenAI Assistants (New):** ⚠️ *Deprecated - Use Responses API instead*
**OpenAI Assistants (New):**
```csharp
AIAgent agent = new OpenAIClient(apiKey)
.GetAssistantClient()
.CreateAIAgent(modelId, instructions: instructions);
```
**OpenAI Assistants (Existing):** ⚠️ *Deprecated - Use Responses API instead*
**OpenAI Assistants (Existing):**
```csharp
AIAgent agent = new OpenAIClient(apiKey)
.GetAssistantClient()
@@ -562,20 +562,6 @@ AIAgent agent = await new PersistentAgentsClient(endpoint, credential)
.GetAIAgentAsync(agentId);
```
**OpenAI Responses:** *(Recommended for OpenAI)*
```csharp
AIAgent agent = new OpenAIClient(apiKey)
.GetOpenAIResponseClient(modelId)
.CreateAIAgent(instructions: instructions);
```
**Azure OpenAI Responses:** *(Recommended for Azure OpenAI)*
```csharp
AIAgent agent = new AzureOpenAIClient(endpoint, credential)
.GetOpenAIResponseClient(deploymentName)
.CreateAIAgent(instructions: instructions);
```
**A2A:**
```csharp
A2ACardResolver resolver = new(new Uri(agentHost));
@@ -776,57 +762,35 @@ await foreach (var content in agent.InvokeAsync(userInput, thread))
**With this Agent Framework CodeInterpreter pattern:**
```csharp
using System.Text;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
var result = await agent.RunAsync(userInput, thread);
Console.WriteLine(result);
// Get the CodeInterpreterToolCallContent (code input)
CodeInterpreterToolCallContent? toolCallContent = result.Messages
.SelectMany(m => m.Contents)
.OfType<CodeInterpreterToolCallContent>()
.FirstOrDefault();
// Extract chat response MEAI type via first level breaking glass
var chatResponse = result.RawRepresentation as ChatResponse;
if (toolCallContent?.Inputs is not null)
// Extract underlying SDK updates via second level breaking glass
var underlyingStreamingUpdates = chatResponse?.RawRepresentation as IEnumerable<object?> ?? [];
StringBuilder generatedCode = new();
foreach (object? underlyingUpdate in underlyingStreamingUpdates ?? [])
{
DataContent? codeInput = toolCallContent.Inputs.OfType<DataContent>().FirstOrDefault();
if (codeInput?.HasTopLevelMediaType("text") ?? false)
if (underlyingUpdate is RunStepDetailsUpdate stepDetailsUpdate && stepDetailsUpdate.CodeInterpreterInput is not null)
{
Console.WriteLine($"Code Input: {Encoding.UTF8.GetString(codeInput.Data.ToArray())}");
generatedCode.Append(stepDetailsUpdate.CodeInterpreterInput);
}
}
// Get the CodeInterpreterToolResultContent (code output)
CodeInterpreterToolResultContent? toolResultContent = result.Messages
.SelectMany(m => m.Contents)
.OfType<CodeInterpreterToolResultContent>()
.FirstOrDefault();
if (toolResultContent?.Outputs is not null)
if (!string.IsNullOrEmpty(generatedCode.ToString()))
{
TextContent? resultOutput = toolResultContent.Outputs.OfType<TextContent>().FirstOrDefault();
if (resultOutput is not null)
{
Console.WriteLine($"Code Tool Result: {resultOutput.Text}");
}
}
// Getting any annotations generated by the tool
foreach (AIAnnotation annotation in result.Messages
.SelectMany(m => m.Contents)
.SelectMany(c => c.Annotations ?? []))
{
Console.WriteLine($"Annotation: {annotation}");
Console.WriteLine($"\n# {chatResponse?.Messages[0].Role}:Generated Code:\n{generatedCode}");
}
```
**Functional differences:**
1. Code interpreter content is now available via MEAI abstractions - no breaking glass required
2. Use `CodeInterpreterToolCallContent` to access code inputs (the generated code)
3. Use `CodeInterpreterToolResultContent` to access code outputs (execution results)
4. Annotations are accessible via `AIAnnotation` on content items
1. Code interpreter output is separate from text content, not a metadata property
2. Access code via `RunStepDetailsUpdate.CodeInterpreterInput` instead of metadata
3. Use breaking glass pattern to access underlying SDK objects
4. Process text content and code interpreter output independently
</behavioral_changes>
#### Provider-Specific Options Configuration
@@ -1016,8 +980,6 @@ AIAgent agent = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential(
### 3. OpenAI Assistants Migration
> ⚠️ **DEPRECATION WARNING**: The OpenAI Assistants API has been deprecated. The Agent Framework extension methods for Assistants are marked as `[Obsolete]`. **Please use the Responses API instead** (see Section 6: OpenAI Responses Migration).
<configuration_changes>
**Remove Semantic Kernel Packages:**
```xml
@@ -1329,7 +1291,52 @@ var result = await agent.RunAsync(userInput, thread);
```
</api_changes>
### 8. Unsupported Providers (Require Custom Implementation)
### 8. A2A Migration
<configuration_changes>
**Remove Semantic Kernel Packages:**
```xml
<PackageReference Include="Microsoft.SemanticKernel.Agents.A2A" />
```
**Add Agent Framework Packages:**
```xml
<PackageReference Include="Microsoft.Agents.AI.A2A" />
```
</configuration_changes>
<api_changes>
**Replace this Semantic Kernel pattern:**
```csharp
using A2A;
using Microsoft.SemanticKernel;
using Microsoft.SemanticKernel.Agents;
using Microsoft.SemanticKernel.Agents.A2A;
using var httpClient = CreateHttpClient();
var client = new A2AClient(agentUrl, httpClient);
var cardResolver = new A2ACardResolver(url, httpClient);
var agentCard = await cardResolver.GetAgentCardAsync();
Console.WriteLine(JsonSerializer.Serialize(agentCard, s_jsonSerializerOptions));
var agent = new A2AAgent(client, agentCard);
```
**With this Agent Framework pattern:**
```csharp
using System;
using A2A;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.A2A;
// Initialize an A2ACardResolver to get an A2A agent card.
A2ACardResolver agentCardResolver = new(new Uri(a2aAgentHost));
// Create an instance of the AIAgent for an existing A2A agent specified by the agent card.
AIAgent agent = await agentCardResolver.GetAIAgentAsync();
```
</api_changes>
### 9. Unsupported Providers (Require Custom Implementation)
<behavioral_changes>
#### BedrockAgent Migration
@@ -1500,7 +1507,7 @@ Console.WriteLine(result);
```
</behavioral_changes>
### 9. Function Invocation Filtering
### 10. Function Invocation Filtering
**Invocation Context**
@@ -1608,4 +1615,25 @@ var filteredAgent = originalAgent
.Build();
```
### 11. Function Invocation Contexts
**Invocation Context**
Semantic Kernel's `IAutoFunctionInvocationFilter` provides a `AutoFunctionInvocationContext` where Agent Framework provides `FunctionInvocationContext`
The property mapping guide from a `AutoFunctionInvocationContext` to a `FunctionInvocationContext` is as follows:
| Semantic Kernel | Agent Framework |
| --- | --- |
| RequestSequenceIndex | Iteration |
| FunctionSequenceIndex | FunctionCallIndex |
| ToolCallId | CallContent.CallId |
| ChatMessageContent | Messages[0] |
| ExecutionSettings | Options |
| ChatHistory | Messages |
| Function | Function |
| Kernel | N/A |
| Result | Use `return` from the delegate |
| Terminate | Terminate |
| CancellationToken | provided via argument to middleware delegate |
| Arguments | Arguments |
+15 -51
View File
@@ -18,7 +18,6 @@ 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 }}
@@ -60,9 +59,9 @@ jobs:
fail-fast: false
matrix:
include:
- { 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: "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: "net472", os: "windows-latest", configuration: Release, integration-tests: true, environment: "integration" }
runs-on: ${{ matrix.os }}
@@ -70,16 +69,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.1
uses: actions/setup-dotnet@v5.0.0
with:
global-json-file: ${{ github.workspace }}/dotnet/global.json
- name: Build dotnet solutions
@@ -124,17 +123,7 @@ jobs:
popd
rm -rf "$TEMP_DIR"
# 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
- name: Run Unit Tests Windows
shell: bash
run: |
export UT_PROJECTS=$(find ./dotnet -type f -name "*.UnitTests.csproj" | tr '\n' ' ')
@@ -144,20 +133,12 @@ jobs:
# Check if the project supports the target framework
if [[ "$target_frameworks" == *"${{ matrix.targetFramework }}"* ]]; then
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
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
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 }}"
@@ -170,14 +151,6 @@ jobs:
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
# This setup action is required for both Durable Task and Azure Functions integration tests.
# We only run it on Ubuntu since the Durable Task and Azure Functions features are not available
# on .NET Framework (net472) which is what we use the Windows runner for.
- name: Set up Durable Task and Azure Functions Integration Test Emulators
if: github.event_name != 'pull_request' && matrix.integration-tests && matrix.os == 'ubuntu-latest'
uses: ./.github/actions/azure-functions-integration-setup
id: azure-functions-setup
- name: Run Integration Tests
shell: bash
if: github.event_name != 'pull_request' && matrix.integration-tests
@@ -195,16 +168,10 @@ 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 }}
OpenAI__ChatReasoningModelId: ${{ vars.OPENAI__CHATREASONINGMODELID }}
# Azure OpenAI Models
AZURE_OPENAI_CHAT_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__CHATDEPLOYMENTNAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
# Azure AI Foundry
AzureAI__Endpoint: ${{ secrets.AZUREAI__ENDPOINT }}
AzureAI__DeploymentName: ${{ vars.AZUREAI__DEPLOYMENTNAME }}
@@ -216,22 +183,19 @@ jobs:
# Generate test reports and check coverage
- name: Generate test reports
if: matrix.targetFramework == env.COVERAGE_FRAMEWORK
uses: danielpalme/ReportGenerator-GitHub-Action@5.5.1
uses: danielpalme/ReportGenerator-GitHub-Action@5.4.18
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
+1 -1
View File
@@ -22,7 +22,7 @@ jobs:
fail-fast: false
matrix:
include:
- { dotnet: "10.0", configuration: Release, os: ubuntu-latest }
- { dotnet: "9.0", configuration: Release, os: ubuntu-latest }
runs-on: ${{ matrix.os }}
env:
+1 -5
View File
@@ -28,8 +28,6 @@ jobs:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- name: Set up python and install the project
id: python-setup
uses: ./.github/actions/python-setup
@@ -48,6 +46,4 @@ jobs:
with:
extra_args: --config python/.pre-commit-config.yaml --all-files
- name: Run Mypy
env:
GITHUB_BASE_REF: ${{ github.event.pull_request.base.ref || github.base_ref || 'main' }}
run: uv run poe ci-mypy
run: uv run poe mypy
-8
View File
@@ -66,11 +66,6 @@ jobs:
AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME: ${{ vars.AZUREOPENAI__RESPONSESDEPLOYMENTNAME }}
AZURE_OPENAI_ENDPOINT: ${{ vars.AZUREOPENAI__ENDPOINT }}
LOCAL_MCP_URL: ${{ vars.LOCAL_MCP__URL }}
# For Azure Functions integration tests
FUNCTIONS_WORKER_RUNTIME: "python"
DURABLE_TASK_SCHEDULER_CONNECTION_STRING: "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None"
AzureWebJobsStorage: "UseDevelopmentStorage=true"
defaults:
run:
working-directory: python
@@ -92,9 +87,6 @@ jobs:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Set up Azure Functions Integration Test Emulators
uses: ./.github/actions/azure-functions-integration-setup
id: azure-functions-setup
- name: Test with pytest
timeout-minutes: 10
run: uv run poe all-tests -n logical --dist loadfile --dist worksteal --timeout 300 --retries 3 --retry-delay 10
@@ -39,7 +39,7 @@ jobs:
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_ENV
- name: Pytest coverage comment
id: coverageComment
uses: MishaKav/pytest-coverage-comment@v1.2.0
uses: MishaKav/pytest-coverage-comment@v1.1.59
with:
github-token: ${{ secrets.GH_ACTIONS_PR_WRITE }}
issue-number: ${{ env.PR_NUMBER }}
+1 -15
View File
@@ -204,20 +204,6 @@ agents.md
# AI
.claude/
WARP.md
**/memory-bank/
**/projectBrief.md
# Azurite storage emulator files
*/__azurite_db_blob__.json
*/__azurite_db_blob_extent__.json
*/__azurite_db_queue__.json
*/__azurite_db_queue_extent__.json
*/__azurite_db_table__.json
*/__blobstorage__/
*/__queuestorage__/
# Azure Functions local settings
local.settings.json
# Frontend
**/frontend/node_modules/
@@ -225,4 +211,4 @@ local.settings.json
**/frontend/dist/
# Database files
*.db
*.db
-2
View File
@@ -48,8 +48,6 @@ 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
+2 -2
View File
@@ -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, 10.0, netstandard2.0, net472)
- **Platform Requirements**: Python 3.10+ required, specific .NET versions (.NET 8.0, 9.0, netstandard2.0, net472)
- **API Dependencies**: Requires proper configuration of LLM provider keys and endpoints
- **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.
- **Orchestration Features**: Advanced orchestration patterns like GroupChat, Sequential, and Concurrent orchestrations are "coming soon" for Python implementation
- **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.
-3
View File
@@ -1,3 +0,0 @@
# Declarative Agents
This folder contains sample agent definitions than be ran using the declarative agent support, for python see the [declarative agent python sample folder](../python/samples/getting_started/declarative/).
-25
View File
@@ -1,25 +0,0 @@
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 Chat as the type in your response.
model:
id: =Env.AZURE_OPENAI_DEPLOYMENT_NAME
provider: AzureOpenAI
apiType: Chat
options:
temperature: 0.9
topP: 0.95
outputSchema:
properties:
language:
kind: string
required: true
description: The language of the answer.
answer:
kind: string
required: true
description: The answer text.
type:
kind: string
required: true
description: The type of the response.
@@ -1,25 +0,0 @@
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 Assistants as the type in your response.
model:
id: gpt-4o-mini
provider: AzureOpenAI
apiType: Assistants
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.
-25
View File
@@ -1,25 +0,0 @@
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.
@@ -1,25 +0,0 @@
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 Responses as the type in your response.
model:
id: gpt-4o-mini
provider: AzureOpenAI
apiType: Responses
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.
-18
View File
@@ -1,18 +0,0 @@
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.
-29
View File
@@ -1,29 +0,0 @@
kind: Prompt
name: Assistant
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:
- kind: function
name: GetWeather
description: Get the weather for a given location.
bindings:
get_weather: get_weather
parameters:
properties:
location:
kind: string
description: The city and state, e.g. San Francisco, CA
required: true
unit:
kind: string
description: The unit of temperature. Possible values are 'celsius' and 'fahrenheit'.
required: false
enum:
- celsius
- fahrenheit
-22
View File
@@ -1,22 +0,0 @@
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.
@@ -1,21 +0,0 @@
kind: Prompt
name: MicrosoftLearnAgent
description: Microsoft Learn Agent
instructions: You answer questions by searching the Microsoft Learn content only.
model:
id: =Env.AZURE_FOUNDRY_PROJECT_MODEL_ID
options:
temperature: 0.9
topP: 0.95
connection:
kind: remote
endpoint: =Env.AZURE_FOUNDRY_PROJECT_ENDPOINT
tools:
- kind: mcp
name: microsoft_learn
description: Get information from Microsoft Learn.
url: https://learn.microsoft.com/api/mcp
approvalMode:
kind: never
allowedTools:
- microsoft_docs_search
@@ -1,22 +0,0 @@
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.
model:
id: =Env.AZURE_FOUNDRY_PROJECT_MODEL_ID
options:
temperature: 0.9
topP: 0.95
connection:
kind: remote
endpoint: =Env.AZURE_FOUNDRY_PROJECT_ENDPOINT
outputSchema:
properties:
language:
kind: string
required: true
description: The language of the answer.
answer:
kind: string
required: true
description: The answer text.
-28
View File
@@ -1,28 +0,0 @@
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 Chat as the type in your response.
model:
id: =Env.OPENAI_MODEL
provider: OpenAI
apiType: Chat
options:
temperature: 0.9
topP: 0.95
connection:
kind: key
key: =Env.OPENAI_API_KEY
outputSchema:
properties:
language:
kind: string
required: true
description: The language of the answer.
answer:
kind: string
required: true
description: The answer text.
type:
kind: string
required: true
description: The type of the response.
@@ -1,28 +0,0 @@
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 Assistants as the type in your response.
model:
id: gpt-4.1-mini
provider: OpenAI
apiType: Assistants
options:
temperature: 0.9
topP: 0.95
connection:
kind: ApiKey
key: =Env.OPENAI_API_KEY
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.
-28
View File
@@ -1,28 +0,0 @@
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_API_KEY
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.
-28
View File
@@ -1,28 +0,0 @@
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 Responses as the type in your response.
model:
id: gpt-4.1-mini
provider: OpenAI
apiType: Responses
options:
temperature: 0.9
topP: 0.95
connection:
kind: ApiKey
key: =Env.OPENAI_API_KEY
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.
+8 -5
View File
@@ -3,14 +3,17 @@
<!-- Default properties inherited by all projects. Projects can override. -->
<RunAnalyzersDuringBuild>true</RunAnalyzersDuringBuild>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<AnalysisLevel>10.0-all</AnalysisLevel>
<AnalysisMode>AllEnabledByDefault</AnalysisMode>
<AnalysisLevel>latest</AnalysisLevel>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<LangVersion>latest</LangVersion>
<LangVersion>13</LangVersion>
<Nullable>enable</Nullable>
<NoWarn>$(NoWarn);NU5128;CS8002</NoWarn>
<NoWarn>$(NoWarn);NU5128</NoWarn>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<TargetFrameworksCore>net10.0;net9.0;net8.0</TargetFrameworksCore>
<TargetFrameworks>$(TargetFrameworksCore);netstandard2.0;net472</TargetFrameworks>
<ProjectsCoreTargetFrameworks>net9.0;net8.0</ProjectsCoreTargetFrameworks>
<ProjectsDebugCoreTargetFrameworks>net9.0</ProjectsDebugCoreTargetFrameworks>
<ProjectsTargetFrameworks>net9.0;net8.0;netstandard2.0;net472</ProjectsTargetFrameworks>
<ProjectsDebugTargetFrameworks>net9.0;net472</ProjectsDebugTargetFrameworks>
<IsAotCompatible Condition="$([MSBuild]::IsTargetFrameworkCompatible('$(TargetFramework)', 'net7.0'))">true</IsAotCompatible>
<Configurations>Debug;Release;Publish</Configurations>
</PropertyGroup>
+1 -1
View File
@@ -5,7 +5,7 @@
<Sdk Name="Microsoft.Build.CentralPackageVersions" Version="2.1.3" />
<!-- Only run 'dotnet format' on dev machines, Release builds. Skip on GitHub Actions -->
<!-- as this runs in its own Actions job. -->
<Target Name="DotnetFormatOnBuild" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Release' AND '$(GITHUB_ACTIONS)' == '' ">
<Target Name="DotnetFormatOnBuild" BeforeTargets="Build" Condition=" '$(Configuration)' == 'Release' AND '$(GITHUB_ACTIONS)' == '' AND '$(TargetFramework)' == '$(ProjectsDebugTargetFrameworks)'">
<Message Text="Running dotnet format" Importance="high" />
<Exec Command="dotnet format --no-restore -v diag $(ProjectFileName)" />
</Target>
+24 -53
View File
@@ -7,35 +7,24 @@
</PropertyGroup>
<PropertyGroup>
<!-- Aspire -->
<AspireAppHostSdkVersion>13.0.2</AspireAppHostSdkVersion>
<AspireAppHostSdkVersion>13.0.0</AspireAppHostSdkVersion>
</PropertyGroup>
<ItemGroup>
<!-- Aspire.* -->
<PackageVersion Include="Anthropic" Version="11.0.0" />
<PackageVersion Include="Anthropic.Foundry" Version="0.1.0" />
<PackageVersion Include="Aspire.Azure.AI.OpenAI" Version="13.0.0-preview.1.25560.3" />
<PackageVersion Include="Aspire.Hosting.AppHost" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Hosting.Azure.CognitiveServices" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="Aspire.Microsoft.Azure.Cosmos" Version="$(AspireAppHostSdkVersion)" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0-beta.440" />
<PackageVersion Include="CommunityToolkit.Aspire.OllamaSharp" Version="13.0.0-beta.435" />
<!-- Azure.* -->
<PackageVersion Include="Azure.AI.Projects" Version="1.2.0-beta.3" />
<PackageVersion Include="Azure.AI.Projects.OpenAI" Version="1.0.0-beta.4" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.8" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.7.0-beta.2" />
<PackageVersion Include="Azure.AI.Agents.Persistent" Version="1.2.0-beta.7" />
<PackageVersion Include="Azure.AI.OpenAI" Version="2.5.0-beta.1" />
<PackageVersion Include="Azure.Identity" Version="1.17.0" />
<PackageVersion Include="Azure.Monitor.OpenTelemetry.Exporter" Version="1.4.0" />
<!-- Google Gemini -->
<PackageVersion Include="Google.GenAI" Version="0.6.0" />
<PackageVersion Include="Mscc.GenerativeAI.Microsoft" Version="2.9.3" />
<!-- Microsoft.Azure.* -->
<PackageVersion Include="Microsoft.Azure.Cosmos" Version="3.54.0" />
<!-- Newtonsoft.Json -->
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
<!-- System.* -->
<PackageVersion Include="Microsoft.Bcl.AsyncInterfaces" Version="10.0.0" />
<PackageVersion Include="Microsoft.Bcl.HashCode" Version="6.0.0" />
<PackageVersion Include="System.ClientModel" Version="1.8.1" />
<PackageVersion Include="System.ClientModel" Version="1.8.0" />
<PackageVersion Include="System.CodeDom" Version="10.0.0" />
<PackageVersion Include="System.Collections.Immutable" Version="10.0.0" />
<PackageVersion Include="System.CommandLine" Version="2.0.0-rc.2.25502.107" />
@@ -46,7 +35,6 @@
<PackageVersion Include="System.Text.Json" Version="10.0.0" />
<PackageVersion Include="System.Threading.Channels" Version="10.0.0" />
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="System.Net.Security" Version="4.3.2" />
<!-- OpenTelemetry -->
<PackageVersion Include="OpenTelemetry" Version="1.13.1" />
<PackageVersion Include="OpenTelemetry.Api" Version="1.13.1" />
@@ -58,13 +46,13 @@
<PackageVersion Include="OpenTelemetry.Instrumentation.Http" Version="1.13.0" />
<PackageVersion Include="OpenTelemetry.Instrumentation.Runtime" Version="1.13.0" />
<!-- Microsoft.AspNetCore.* -->
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.OpenApi" Version="9.0.11" />
<PackageVersion Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.0.0" />
<!-- Microsoft.Extensions.* -->
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.0.1" />
<PackageVersion Include="Microsoft.Extensions.AI" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.AI.Abstractions" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.AI.AzureAIInference" Version="10.0.0-preview.1.25559.3" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.1-preview.1.25571.5" />
<PackageVersion Include="Microsoft.Extensions.AI.OpenAI" Version="10.0.0-preview.1.25559.3" />
<PackageVersion Include="Microsoft.Extensions.Caching.Memory" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration" Version="10.0.0" />
<PackageVersion Include="Microsoft.Extensions.Configuration.Binder" Version="10.0.0" />
@@ -90,11 +78,7 @@
<PackageVersion Include="Microsoft.SemanticKernel.Agents.AzureAI" Version="1.67.0-preview" />
<PackageVersion Include="Microsoft.SemanticKernel.Plugins.OpenApi" Version="1.67.0" />
<!-- Agent SDKs -->
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.3.171-beta" />
<!-- M365 Agents SDK -->
<PackageVersion Include="AdaptiveCards" Version="3.1.0" />
<PackageVersion Include="Microsoft.Agents.Authentication.Msal" Version="1.3.171-beta" />
<PackageVersion Include="Microsoft.Agents.Hosting.AspNetCore" Version="1.3.171-beta" />
<PackageVersion Include="Microsoft.Agents.CopilotStudio.Client" Version="1.2.41" />
<!-- A2A -->
<PackageVersion Include="A2A" Version="0.3.3-preview" />
<PackageVersion Include="A2A.AspNetCore" Version="0.3.3-preview" />
@@ -102,42 +86,29 @@
<PackageVersion Include="ModelContextProtocol" Version="0.4.0-preview.3" />
<!-- Inference SDKs -->
<PackageVersion Include="Anthropic.SDK" Version="5.8.0" />
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.4.7" />
<PackageVersion Include="AWSSDK.Extensions.Bedrock.MEAI" Version="4.0.4.6" />
<PackageVersion Include="Microsoft.ML.OnnxRuntimeGenAI" Version="0.10.0" />
<PackageVersion Include="OllamaSharp" Version="5.4.8" />
<PackageVersion Include="OpenAI" Version="2.7.0" />
<PackageVersion Include="OpenAI" Version="2.6.0" />
<!-- Identity -->
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.78.0" />
<!-- Workflows -->
<PackageVersion Include="Microsoft.Bot.ObjectModel" Version="1.2025.1106.1" />
<PackageVersion Include="Microsoft.Bot.ObjectModel.Json" Version="1.2025.1106.1" />
<PackageVersion Include="Microsoft.Bot.ObjectModel.PowerFx" Version="1.2025.1106.1" />
<PackageVersion Include="Microsoft.PowerFx.Interpreter" Version="1.5.0-build.20251008-1002" />
<!-- Durable Task -->
<PackageVersion Include="Microsoft.DurableTask.Client" Version="1.16.2" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.16.2-preview.1" />
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.16.2" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.16.2-preview.1" />
<!-- Azure Functions -->
<PackageVersion Include="Microsoft.Azure.Functions.Worker" Version="2.50.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.ApplicationInsights" Version="2.50.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.9.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.3.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" Version="2.1.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.Mcp" Version="1.0.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Sdk" Version="2.0.7" />
<PackageVersion Include="Microsoft.Bot.ObjectModel" Version="1.2025.1003.2" />
<PackageVersion Include="Microsoft.Bot.ObjectModel.Json" Version="1.2025.1003.2" />
<PackageVersion Include="Microsoft.Bot.ObjectModel.PowerFx" Version="1.2025.1003.2" />
<PackageVersion Include="Microsoft.PowerFx.Interpreter" Version="1.4.0" />
<!-- Community -->
<PackageVersion Include="System.Linq.Async" Version="6.0.3" />
<!-- Test -->
<PackageVersion Include="FluentAssertions" Version="8.8.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net8.0'" Version="8.0.22" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net9.0'" Version="9.0.11" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Condition="'$(TargetFramework)' == 'net10.0'" Version="10.0.0" />
<PackageVersion Include="Microsoft.AspNetCore.TestHost" Version="9.0.11" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.0.0" />
<PackageVersion Include="Moq" Version="[4.18.4]" />
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Abstractions" Version="1.67.0" />
<PackageVersion Include="Microsoft.SemanticKernel.Agents.Yaml" Version="1.67.0-beta" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.abstractions" Version="2.0.3" />
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.3" />
<PackageVersion Include="Xunit.SkippableFact" Version="1.5.23" />
<PackageVersion Include="xretry" Version="1.9.0" />
<PackageVersion Include="coverlet.collector" Version="6.0.4" />
<!-- Symbols -->
@@ -164,17 +135,17 @@
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageVersion Include="Roslynator.Analyzers" Version="4.14.1" />
<PackageVersion Include="Roslynator.Analyzers" Version="[4.14.1]" />
<PackageReference Include="Roslynator.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="4.14.1" />
<PackageVersion Include="Roslynator.CodeAnalysis.Analyzers" Version="[4.14.0]" />
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="4.14.1" />
<PackageVersion Include="Roslynator.Formatting.Analyzers" Version="[4.14.0]" />
<PackageReference Include="Roslynator.Formatting.Analyzers">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
+6 -118
View File
@@ -20,44 +20,24 @@
</Folder>
<Folder Name="/Samples/AGUIClientServer/">
<Project Path="samples/AGUIClientServer/AGUIClient/AGUIClient.csproj" />
<Project Path="samples/AGUIClientServer/AGUIDojoServer/AGUIDojoServer.csproj" />
<Project Path="samples/AGUIClientServer/AGUIServer/AGUIServer.csproj" />
</Folder>
<Folder Name="/Samples/AGUIDojo/">
<File Path="samples/AGUIDojo/Design.md" />
<File Path="samples/AGUIDojo/Plan.md" />
<Project Path="samples/AGUIDojo/AGUIDojoClient/AGUIDojoClient.csproj" />
<Project Path="samples/AGUIDojo/AGUIDojoServer/AGUIDojoServer.csproj" />
</Folder>
<Folder Name="/Samples/AzureFunctions/">
<File Path="samples/AzureFunctions/.editorconfig" />
<File Path="samples/AzureFunctions/README.md" />
<Project Path="samples/AzureFunctions/01_SingleAgent/01_SingleAgent.csproj" />
<Project Path="samples/AzureFunctions/02_AgentOrchestration_Chaining/02_AgentOrchestration_Chaining.csproj" />
<Project Path="samples/AzureFunctions/03_AgentOrchestration_Concurrency/03_AgentOrchestration_Concurrency.csproj" />
<Project Path="samples/AzureFunctions/04_AgentOrchestration_Conditionals/04_AgentOrchestration_Conditionals.csproj" />
<Project Path="samples/AzureFunctions/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
<Project Path="samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj" />
<Project Path="samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/">
<File Path="samples/GettingStarted/README.md" />
</Folder>
<Folder Name="/Samples/GettingStarted/A2A/">
<File Path="samples/GettingStarted/A2A/README.md" />
<Project Path="samples/GettingStarted/A2A/A2AAgent_AsFunctionTools/A2AAgent_AsFunctionTools.csproj" />
<Project Path="samples/GettingStarted/A2A/A2AAgent_PollingForTaskCompletion/A2AAgent_PollingForTaskCompletion.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentProviders/">
<File Path="samples/GettingStarted/AgentProviders/README.md" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_A2A/Agent_With_A2A.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_Anthropic/Agent_With_Anthropic.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIAgentsPersistent/Agent_With_AzureAIAgentsPersistent.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureAIProject/Agent_With_AzureAIProject.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryAgent/Agent_With_AzureFoundryAgent.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureFoundryModel/Agent_With_AzureFoundryModel.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIChatCompletion/Agent_With_AzureOpenAIChatCompletion.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_AzureOpenAIResponses/Agent_With_AzureOpenAIResponses.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_CustomImplementation/Agent_With_CustomImplementation.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_GoogleGemini/Agent_With_GoogleGemini.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_Ollama/Agent_With_Ollama.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_ONNX/Agent_With_ONNX.csproj" />
<Project Path="samples/GettingStarted/AgentProviders/Agent_With_OpenAIAssistants/Agent_With_OpenAIAssistants.csproj" />
@@ -84,44 +64,11 @@
<Project Path="samples/GettingStarted/Agents/Agent_Step16_ChatReduction/Agent_Step16_ChatReduction.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step17_BackgroundResponses/Agent_Step17_BackgroundResponses.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step18_DeepResearch/Agent_Step18_DeepResearch.csproj" />
<Project Path="samples/GettingStarted/Agents/Agent_Step19_Declarative/Agent_Step19_Declarative.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/DeclarativeAgents/">
<Project Path="samples/GettingStarted/DeclarativeAgents/ChatClient/DeclarativeChatClientAgents.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/">
<File Path="samples/GettingStarted/AGUI/README.md" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step01_GettingStarted/">
<Project Path="samples/GettingStarted/AGUI/Step01_GettingStarted/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step01_GettingStarted/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step02_BackendTools/">
<Project Path="samples/GettingStarted/AGUI/Step02_BackendTools/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step02_BackendTools/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step03_FrontendTools/">
<Project Path="samples/GettingStarted/AGUI/Step03_FrontendTools/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step03_FrontendTools/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step04_HumanInLoop/">
<Project Path="samples/GettingStarted/AGUI/Step04_HumanInLoop/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step04_HumanInLoop/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AGUI/Step05_StateManagement/">
<Project Path="samples/GettingStarted/AGUI/Step05_StateManagement/Client/Client.csproj" />
<Project Path="samples/GettingStarted/AGUI/Step05_StateManagement/Server/Server.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/DevUI/">
<File Path="samples/GettingStarted/DevUI/README.md" />
<Project Path="samples/GettingStarted/DevUI/DevUI_Step01_BasicUsage/DevUI_Step01_BasicUsage.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithAnthropic/">
<File Path="samples/GettingStarted/AgentWithAnthropic/README.md" />
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step01_Running/Agent_Anthropic_Step01_Running.csproj" />
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step02_Reasoning/Agent_Anthropic_Step02_Reasoning.csproj" />
<Project Path="samples/GettingStarted/AgentWithAnthropic/Agent_Anthropic_Step03_UsingFunctionTools/Agent_Anthropic_Step03_UsingFunctionTools.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithMemory/">
<File Path="samples/GettingStarted/AgentWithMemory/README.md" />
<Project Path="samples/GettingStarted/AgentWithMemory/AgentWithMemory_Step01_ChatHistoryMemory/AgentWithMemory_Step01_ChatHistoryMemory.csproj" />
@@ -132,38 +79,12 @@
<File Path="samples/GettingStarted/AgentWithOpenAI/README.md" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step01_Running/Agent_OpenAI_Step01_Running.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step02_Reasoning/Agent_OpenAI_Step02_Reasoning.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step03_CreateFromChatClient/Agent_OpenAI_Step03_CreateFromChatClient.csproj" />
<Project Path="samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient.csproj" />
</Folder>
<Folder Name="/Samples/Purview/" />
<Folder Name="/Samples/Purview/AgentWithPurview/">
<Project Path="samples/Purview/AgentWithPurview/AgentWithPurview.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/AgentWithRAG/">
<File Path="samples/GettingStarted/AgentWithRAG/README.md" />
<Project Path="samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step01_BasicTextRAG/AgentWithRAG_Step01_BasicTextRAG.csproj" />
<Project Path="samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step02_CustomVectorStoreRAG/AgentWithRAG_Step02_CustomVectorStoreRAG.csproj" />
<Project Path="samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step03_CustomRAGDataSource/AgentWithRAG_Step03_CustomRAGDataSource.csproj" />
<Project Path="samples/GettingStarted/AgentWithRAG/AgentWithRAG_Step04_FoundryServiceRAG/AgentWithRAG_Step04_FoundryServiceRAG.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/FoundryAgents/">
<File Path="samples/GettingStarted/FoundryAgents/README.md" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.1_Basics/FoundryAgents_Step01.1_Basics.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step01.2_Running/FoundryAgents_Step01.2_Running.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step02_MultiturnConversation/FoundryAgents_Step02_MultiturnConversation.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step03_UsingFunctionTools/FoundryAgents_Step03_UsingFunctionTools.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step04_UsingFunctionToolsWithApprovals/FoundryAgents_Step04_UsingFunctionToolsWithApprovals.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step05_StructuredOutput/FoundryAgents_Step05_StructuredOutput.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step06_PersistedConversations/FoundryAgents_Step06_PersistedConversations.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step07_Observability/FoundryAgents_Step07_Observability.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step08_DependencyInjection/FoundryAgents_Step08_DependencyInjection.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step09_UsingMcpClientAsTools/FoundryAgents_Step09_UsingMcpClientAsTools.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step10_UsingImages/FoundryAgents_Step10_UsingImages.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step11_AsFunctionTool/FoundryAgents_Step11_AsFunctionTool.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step12_Middleware/FoundryAgents_Step12_Middleware.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step13_Plugins/FoundryAgents_Step13_Plugins.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step14_CodeInterpreter/FoundryAgents_Step14_CodeInterpreter.csproj" />
<Project Path="samples/GettingStarted/FoundryAgents/FoundryAgents_Step15_ComputerUse/FoundryAgents_Step15_ComputerUse.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/ModelContextProtocol/">
<File Path="samples/GettingStarted/ModelContextProtocol/README.md" />
@@ -189,22 +110,13 @@
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/Declarative/">
<File Path="samples/GettingStarted/Workflows/Declarative/README.md" />
<Project Path="samples/GettingStarted/Workflows/Declarative/ConfirmInput/ConfirmInput.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/CustomerSupport/CustomerSupport.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/DeepResearch/DeepResearch.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/ExecuteCode/ExecuteCode.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/ExecuteWorkflow/ExecuteWorkflow.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/FunctionTools/FunctionTools.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/GenerateCode/GenerateCode.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/HostedWorkflow/HostedWorkflow.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/InputArguments/InputArguments.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/Marketing/Marketing.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/StudentTeacher/StudentTeacher.csproj" />
<Project Path="samples/GettingStarted/Workflows/Declarative/ToolApproval/ToolApproval.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/Declarative/Examples/">
<File Path="../workflow-samples/CustomerSupport.yaml" />
<File Path="../workflow-samples/DeepResearch.yaml" />
<File Path="../workflow-samples/HumanInLoop.yaml" />
<File Path="../workflow-samples/Marketing.yaml" />
<File Path="../workflow-samples/MathChat.yaml" />
<File Path="../workflow-samples/README.md" />
@@ -235,7 +147,7 @@
<Project Path="samples/GettingStarted/Workflows/Observability/WorkflowAsAnAgent/WorkflowAsAnAgentObservability.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/Visualization/">
<Project Path="samples/GettingStarted/Workflows/Visualization/Visualization.csproj" />
<Project Path="samples/GettingStarted/Workflows/Visualization/Visualization.csproj" Id="99bf0bc6-2440-428e-b3e7-d880e4b7a5fd" />
</Folder>
<Folder Name="/Samples/GettingStarted/Workflows/_Foundational/">
<Project Path="samples/GettingStarted/Workflows/_Foundational/01_ExecutorsAndEdges/01_ExecutorsAndEdges.csproj" />
@@ -249,11 +161,8 @@
</Folder>
<Folder Name="/Samples/HostedAgents/">
<Project Path="samples/HostedAgents/AgentsInWorkflows/AgentsInWorkflows.csproj" />
<Project Path="samples/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj" />
<Project Path="samples/HostedAgents/AgentWithTextSearchRag/AgentWithTextSearchRag.csproj" />
</Folder>
<Folder Name="/Samples/M365Agent/">
<Project Path="samples/M365Agent/M365Agent.csproj" />
<Project Path="samples/HostedAgents/AgentWithHostedMCP/AgentWithHostedMCP.csproj" />
</Folder>
<Folder Name="/Solution Items/">
<File Path=".editorconfig" />
@@ -354,7 +263,6 @@
<File Path="src/Shared/Demos/SampleEnvironment.cs" />
</Folder>
<Folder Name="/Solution Items/src/Shared/IntegrationTests/">
<File Path="src/Shared/IntegrationTests/AnthropicConfiguration.cs" />
<File Path="src/Shared/IntegrationTests/AzureAIConfiguration.cs" />
<File Path="src/Shared/IntegrationTests/Mem0Configuration.cs" />
<File Path="src/Shared/IntegrationTests/OpenAIConfiguration.cs" />
@@ -379,24 +287,16 @@
<Project Path="src/Microsoft.Agents.AI.A2A/Microsoft.Agents.AI.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
<Project Path="src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Anthropic/Microsoft.Agents.AI.Anthropic.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI.Persistent/Microsoft.Agents.AI.AzureAI.Persistent.csproj" />
<Project Path="src/Microsoft.Agents.AI.AzureAI/Microsoft.Agents.AI.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.CopilotStudio/Microsoft.Agents.AI.CopilotStudio.csproj" />
<Project Path="src/Microsoft.Agents.AI.CosmosNoSql/Microsoft.Agents.AI.CosmosNoSql.csproj" />
<Project Path="src/Microsoft.Agents.AI.Declarative/Microsoft.Agents.AI.Declarative.csproj" />
<Project Path="src/Microsoft.Agents.AI.DevUI/Microsoft.Agents.AI.DevUI.csproj" />
<Project Path="src/Microsoft.Agents.AI.DurableTask/Microsoft.Agents.AI.DurableTask.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A.AspNetCore/Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.A2A/Microsoft.Agents.AI.Hosting.A2A.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.AzureFunctions/Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting.OpenAI/Microsoft.Agents.AI.Hosting.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
<Project Path="src/Microsoft.Agents.AI.Mem0/Microsoft.Agents.AI.Mem0.csproj" />
<Project Path="src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Purview/Microsoft.Agents.AI.Purview.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative.AzureAI/Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Declarative/Microsoft.Agents.AI.Workflows.Declarative.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
<Project Path="src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
@@ -404,13 +304,9 @@
<Folder Name="/Tests/" />
<Folder Name="/Tests/IntegrationTests/">
<Project Path="tests/AgentConformance.IntegrationTests/AgentConformance.IntegrationTests.csproj" />
<Project Path="tests/AnthropicChatCompletion.IntegrationTests/AnthropicChatCompletion.IntegrationTests.csproj" />
<Project Path="tests/AzureAI.IntegrationTests/AzureAI.IntegrationTests.csproj" />
<Project Path="tests/AzureAIAgentsPersistent.IntegrationTests/AzureAIAgentsPersistent.IntegrationTests.csproj" />
<Project Path="tests/CopilotStudio.IntegrationTests/CopilotStudio.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DurableTask.IntegrationTests/Microsoft.Agents.AI.DurableTask.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests/Microsoft.Agents.AI.Hosting.AzureFunctions.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mem0.IntegrationTests/Microsoft.Agents.AI.Mem0.IntegrationTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests/Microsoft.Agents.AI.Workflows.Declarative.IntegrationTests.csproj" />
<Project Path="tests/OpenAIAssistant.IntegrationTests/OpenAIAssistant.IntegrationTests.csproj" />
@@ -421,23 +317,15 @@
<Project Path="tests/Microsoft.Agents.AI.A2A.UnitTests/Microsoft.Agents.AI.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Abstractions.UnitTests/Microsoft.Agents.AI.Abstractions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AGUI.UnitTests/Microsoft.Agents.AI.AGUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Anthropic.UnitTests/Microsoft.Agents.AI.Anthropic.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests/Microsoft.Agents.AI.AzureAI.Persistent.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.AzureAI.UnitTests/Microsoft.Agents.AI.AzureAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.CosmosNoSql.UnitTests/Microsoft.Agents.AI.CosmosNoSql.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Declarative.UnitTests/Microsoft.Agents.AI.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DevUI.UnitTests/Microsoft.Agents.AI.DevUI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.DurableTask.UnitTests/Microsoft.Agents.AI.DurableTask.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.A2A.UnitTests/Microsoft.Agents.AI.Hosting.A2A.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests/Microsoft.Agents.AI.Hosting.AzureFunctions.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests/Microsoft.Agents.AI.Hosting.OpenAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Hosting.UnitTests/Microsoft.Agents.AI.Hosting.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Mem0.UnitTests/Microsoft.Agents.AI.Mem0.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.OpenAI.UnitTests/Microsoft.Agents.AI.OpenAI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Purview.UnitTests/Microsoft.Agents.AI.Purview.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.UnitTests/Microsoft.Agents.AI.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests/Microsoft.Agents.AI.Workflows.Declarative.UnitTests.csproj" />
<Project Path="tests/Microsoft.Agents.AI.Workflows.UnitTests/Microsoft.Agents.AI.Workflows.UnitTests.csproj" />
</Folder>
</Solution>
</Solution>
-31
View File
@@ -1,31 +0,0 @@
{
"solution": {
"path": "agent-framework-dotnet.slnx",
"projects": [
"src\\Microsoft.Agents.AI.A2A\\Microsoft.Agents.AI.A2A.csproj",
"src\\Microsoft.Agents.AI.Abstractions\\Microsoft.Agents.AI.Abstractions.csproj",
"src\\Microsoft.Agents.AI.AGUI\\Microsoft.Agents.AI.AGUI.csproj",
"src\\Microsoft.Agents.AI.Anthropic\\Microsoft.Agents.AI.Anthropic.csproj",
"src\\Microsoft.Agents.AI.AzureAI.Persistent\\Microsoft.Agents.AI.AzureAI.Persistent.csproj",
"src\\Microsoft.Agents.AI.AzureAI\\Microsoft.Agents.AI.AzureAI.csproj",
"src\\Microsoft.Agents.AI.CopilotStudio\\Microsoft.Agents.AI.CopilotStudio.csproj",
"src\\Microsoft.Agents.AI.CosmosNoSql\\Microsoft.Agents.AI.CosmosNoSql.csproj",
"src\\Microsoft.Agents.AI.Declarative\\Microsoft.Agents.AI.Declarative.csproj",
"src\\Microsoft.Agents.AI.DevUI\\Microsoft.Agents.AI.DevUI.csproj",
"src\\Microsoft.Agents.AI.DurableTask\\Microsoft.Agents.AI.DurableTask.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore\\Microsoft.Agents.AI.Hosting.A2A.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.A2A\\Microsoft.Agents.AI.Hosting.A2A.csproj",
"src\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore\\Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj",
"src\\Microsoft.Agents.AI.Hosting.AzureFunctions\\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj",
"src\\Microsoft.Agents.AI.Hosting.OpenAI\\Microsoft.Agents.AI.Hosting.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Hosting\\Microsoft.Agents.AI.Hosting.csproj",
"src\\Microsoft.Agents.AI.Mem0\\Microsoft.Agents.AI.Mem0.csproj",
"src\\Microsoft.Agents.AI.OpenAI\\Microsoft.Agents.AI.OpenAI.csproj",
"src\\Microsoft.Agents.AI.Purview\\Microsoft.Agents.AI.Purview.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative.AzureAI\\Microsoft.Agents.AI.Workflows.Declarative.AzureAI.csproj",
"src\\Microsoft.Agents.AI.Workflows.Declarative\\Microsoft.Agents.AI.Workflows.Declarative.csproj",
"src\\Microsoft.Agents.AI.Workflows\\Microsoft.Agents.AI.Workflows.csproj",
"src\\Microsoft.Agents.AI\\Microsoft.Agents.AI.csproj"
]
}
}
-9
View File
@@ -11,13 +11,4 @@
<ItemGroup Condition="'$(InjectSharedBuildTestCode)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\CodeTests\*.cs" LinkBase="Shared\CodeTests" />
</ItemGroup>
<ItemGroup Condition="'$(InjectSharedWorkflowsExecution)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Workflows\Execution\*.cs" LinkBase="Shared\Workflows" />
</ItemGroup>
<ItemGroup Condition="'$(InjectSharedWorkflowsSettings)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Workflows\Settings\*.cs" LinkBase="Shared\Workflows" />
</ItemGroup>
<ItemGroup Condition="'$(InjectSharedFoundryAgents)' == 'true'">
<Compile Include="$(MSBuildThisFileDirectory)\..\..\src\Shared\Foundry\Agents\*.cs" LinkBase="Shared\Foundry" />
</ItemGroup>
</Project>
+2 -2
View File
@@ -1,7 +1,7 @@
{
"sdk": {
"version": "10.0.100",
"rollForward": "minor",
"version": "9.0.300",
"rollForward": "latestMajor",
"allowPrerelease": false
}
}
+3 -3
View File
@@ -2,9 +2,9 @@
<PropertyGroup>
<!-- Central version prefix - applies to all nuget packages. -->
<VersionPrefix>1.0.0</VersionPrefix>
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251204.1</PackageVersion>
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251204.1</PackageVersion>
<GitTag>1.0.0-preview.251204.1</GitTag>
<PackageVersion Condition="'$(VersionSuffix)' != ''">$(VersionPrefix)-$(VersionSuffix).251110.2</PackageVersion>
<PackageVersion Condition="'$(VersionSuffix)' == ''">$(VersionPrefix)-preview.251110.2</PackageVersion>
<GitTag>1.0.0-preview.251110.2</GitTag>
<Configurations>Debug;Release;Publish</Configurations>
<IsPackable>true</IsPackable>
-1
View File
@@ -1,7 +1,6 @@
# Suppressing errors for Sample projects under dotnet/samples folder
[*.cs]
dotnet_diagnostic.CA1716.severity = none # Add summary to documentation comment.
dotnet_diagnostic.CA1873.severity = none # Evaluation of logging arguments may be expensive
dotnet_diagnostic.CA2000.severity = none # Call System.IDisposable.Dispose on object before all references to it are out of scope
dotnet_diagnostic.CA2007.severity = none # Do not directly await a Task
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
@@ -12,6 +12,8 @@
<PackageReference Include="A2A" />
<PackageReference Include="System.CommandLine" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
@@ -4,7 +4,6 @@ using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Logging;
using OpenAI;
using OpenAI.Chat;
namespace A2A;
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>5ee045b0-aea3-4f08-8d31-32d1a6f8fed0</UserSecretsId>
@@ -11,11 +11,8 @@
<ItemGroup>
<PackageReference Include="Azure.AI.Agents.Persistent" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<ItemGroup Condition="!$([MSBuild]::IsTargetFrameworkCompatible($(TargetFramework), 'net10.0'))">
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" />
<PackageReference Include="System.Linq.AsyncEnumerable" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
@@ -6,7 +6,6 @@ using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using OpenAI;
using OpenAI.Chat;
namespace A2AServer;
+1 -1
View File
@@ -103,7 +103,7 @@ dotnet run --urls "http://localhost:5002;https://localhost:5012" --agentId "<Log
### Testing the Agents using the Rest Client
This sample contains a [.http file](https://learn.microsoft.com/aspnet/core/test/http-files?view=aspnetcore-10.0) which can be used to test the agent.
This sample contains a [.http file](https://learn.microsoft.com/aspnet/core/test/http-files?view=aspnetcore-9.0) which can be used to test the agent.
1. In Visual Studio open [./A2AServer/A2AServer.http](./A2AServer/A2AServer.http)
1. There are two sent requests for each agent, e.g., for the invoice agent:
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>a8b2e9f0-1ea3-4f18-9d41-42d1a6f8fe10</UserSecretsId>
@@ -11,6 +11,8 @@
<ItemGroup>
<PackageReference Include="System.CommandLine" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
@@ -201,12 +201,12 @@ public static class Program
{
return "";
}
var builder = new StringBuilder().AppendLine();
var builder = new StringBuilder();
builder.AppendLine();
foreach (var kvp in arguments)
{
builder
.AppendLine($" Name: {kvp.Key}")
.AppendLine($" Value: {kvp.Value}");
builder.AppendLine($" Name: {kvp.Key}");
builder.AppendLine($" Value: {kvp.Value}");
}
return builder.ToString();
}
@@ -2,8 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks></TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>b9c3f1e1-2fb4-5g29-0e52-53e2b7g9gf21</UserSecretsId>
@@ -12,6 +11,8 @@
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
@@ -0,0 +1,11 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoServer;
[JsonSerializable(typeof(WeatherInfo))]
[JsonSerializable(typeof(Recipe))]
[JsonSerializable(typeof(Ingredient))]
[JsonSerializable(typeof(RecipeResponse))]
internal sealed partial class AGUIDojoServerSerializerContext : JsonSerializerContext;
@@ -0,0 +1,98 @@
// Copyright (c) Microsoft. All rights reserved.
using System.ComponentModel;
using System.Text.Json;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
using ChatClient = OpenAI.Chat.ChatClient;
namespace AGUIDojoServer;
internal static class ChatClientAgentFactory
{
private static AzureOpenAIClient? s_azureOpenAIClient;
private static string? s_deploymentName;
public static void Initialize(IConfiguration configuration)
{
string endpoint = configuration["AZURE_OPENAI_ENDPOINT"] ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
s_deploymentName = configuration["AZURE_OPENAI_DEPLOYMENT_NAME"] ?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT_NAME is not set.");
s_azureOpenAIClient = new AzureOpenAIClient(
new Uri(endpoint),
new DefaultAzureCredential());
}
public static ChatClientAgent CreateAgenticChat()
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
return chatClient.AsIChatClient().CreateAIAgent(
name: "AgenticChat",
description: "A simple chat agent using Azure OpenAI");
}
public static ChatClientAgent CreateBackendToolRendering()
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
return chatClient.AsIChatClient().CreateAIAgent(
name: "BackendToolRenderer",
description: "An agent that can render backend tools using Azure OpenAI",
tools: [AIFunctionFactory.Create(
GetWeather,
name: "get_weather",
description: "Get the weather for a given location.",
AGUIDojoServerSerializerContext.Default.Options)]);
}
public static ChatClientAgent CreateHumanInTheLoop()
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
return chatClient.AsIChatClient().CreateAIAgent(
name: "HumanInTheLoopAgent",
description: "An agent that involves human feedback in its decision-making process using Azure OpenAI");
}
public static ChatClientAgent CreateToolBasedGenerativeUI()
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
return chatClient.AsIChatClient().CreateAIAgent(
name: "ToolBasedGenerativeUIAgent",
description: "An agent that uses tools to generate user interfaces using Azure OpenAI");
}
public static ChatClientAgent CreateAgenticUI()
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
return chatClient.AsIChatClient().CreateAIAgent(
name: "AgenticUIAgent",
description: "An agent that generates agentic user interfaces using Azure OpenAI");
}
public static AIAgent CreateSharedState(JsonSerializerOptions options)
{
ChatClient chatClient = s_azureOpenAIClient!.GetChatClient(s_deploymentName!);
var baseAgent = chatClient.AsIChatClient().CreateAIAgent(
name: "SharedStateAgent",
description: "An agent that demonstrates shared state patterns using Azure OpenAI");
return new SharedStateAgent(baseAgent, options);
}
[Description("Get the weather for a given location.")]
private static WeatherInfo GetWeather([Description("The location to get the weather for.")] string location) => new()
{
Temperature = 20,
Conditions = "sunny",
Humidity = 50,
WindSpeed = 10,
FeelsLike = 25
};
}
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
internal sealed class Ingredient
{
@@ -35,13 +35,11 @@ app.MapAGUI("/human_in_the_loop", ChatClientAgentFactory.CreateHumanInTheLoop())
app.MapAGUI("/tool_based_generative_ui", ChatClientAgentFactory.CreateToolBasedGenerativeUI());
app.MapAGUI("/agentic_generative_ui", ChatClientAgentFactory.CreateAgenticUI());
var jsonOptions = app.Services.GetRequiredService<IOptions<Microsoft.AspNetCore.Http.Json.JsonOptions>>();
app.MapAGUI("/agentic_generative_ui", ChatClientAgentFactory.CreateAgenticUI(jsonOptions.Value.SerializerOptions));
app.MapAGUI("/shared_state", ChatClientAgentFactory.CreateSharedState(jsonOptions.Value.SerializerOptions));
app.MapAGUI("/predictive_state_updates", ChatClientAgentFactory.CreatePredictiveStateUpdates(jsonOptions.Value.SerializerOptions));
await app.RunAsync();
public partial class Program;
public partial class Program { }
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
internal sealed class Recipe
{
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
#pragma warning disable CA1812 // Used for the JsonSchema response format
internal sealed class RecipeResponse
@@ -6,7 +6,7 @@ using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace AGUIDojoServer.SharedState;
namespace AGUIDojoServer;
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by ChatClientAgentFactory.CreateSharedState")]
internal sealed class SharedStateAgent : DelegatingAIAgent
@@ -2,7 +2,7 @@
using System.Text.Json.Serialization;
namespace AGUIDojoServer.BackendToolRendering;
namespace AGUIDojoServer;
internal sealed class WeatherInfo
{
@@ -2,7 +2,7 @@
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<UserSecretsId>a8b2e9f0-1ea3-4f18-9d41-42d1a6f8fe10</UserSecretsId>
@@ -11,6 +11,8 @@
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.Bcl.AsyncInterfaces" VersionOverride="10.0.0-rc.2.25502.107" />
<PackageReference Include="System.Net.ServerSentEvents" VersionOverride="10.0.0-rc.2.25502.107" />
</ItemGroup>
<ItemGroup>
@@ -6,7 +6,7 @@ using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI.Hosting.AGUI.AspNetCore;
using Microsoft.Extensions.AI;
using OpenAI.Chat;
using OpenAI;
WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient().AddLogging();
-1
View File
@@ -1 +0,0 @@
.playwright-mcp/
-11
View File
@@ -1,11 +0,0 @@
<Solution>
<Project Path="../../src/Microsoft.Agents.AI.Abstractions/Microsoft.Agents.AI.Abstractions.csproj" />
<Project Path="../../src/Microsoft.Agents.AI.AGUI/Microsoft.Agents.AI.AGUI.csproj" />
<Project Path="../../src/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore/Microsoft.Agents.AI.Hosting.AGUI.AspNetCore.csproj" />
<Project Path="../../src/Microsoft.Agents.AI.Hosting/Microsoft.Agents.AI.Hosting.csproj" />
<Project Path="../../src/Microsoft.Agents.AI.OpenAI/Microsoft.Agents.AI.OpenAI.csproj" />
<Project Path="../../src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
<Project Path="../../src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
<Project Path="AGUIDojoClient/AGUIDojoClient.csproj" />
<Project Path="AGUIDojoServer/AGUIDojoServer.csproj" />
</Solution>
@@ -1,16 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFrameworks></TargetFrameworks>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<BlazorDisableThrowNavigationException>true</BlazorDisableThrowNavigationException>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.AGUI\Microsoft.Agents.AI.AGUI.csproj" />
</ItemGroup>
</Project>
@@ -1,141 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.AI;
[CascadingTypeParameter(nameof(TState))]
public partial class AgentStateBoundary<TState> : IComponent, IDisposable
{
private RenderHandle _renderHandle;
private RenderFragment? _renderWithState;
private protected AgentBoundaryContext<TState>? _context;
private AgentThread? _currentThread;
private AIAgent? _currentAgent;
private bool _disposed;
[EditorRequired][Parameter] public AIAgent? Agent { get; set; }
[Parameter] public AgentThread? Thread { get; set; }
[Parameter] public TState? State { get; set; }
[Parameter] public RenderFragment? ChildContent { get; set; }
/// <summary>
/// Callback invoked when the boundary context is created or recreated.
/// Use this to register tools or perform other initialization.
/// </summary>
[Parameter] public EventCallback<IAgentBoundaryContext> OnContextCreated { get; set; }
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
this._renderWithState = this.RenderWithState;
}
// Agent boundary renders once when it receives the initial set of parameters
// and only ever renders again if the Agent, Thread, or State changes.
// Re-rendering the agent boundary will dispose the existing context and create
// a new one.
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (this.Agent == null)
{
throw new InvalidOperationException("AgentBoundary requires an Agent parameter.");
}
// Use Thread parameter if provided, otherwise keep existing thread or create new one
var thread = this.Thread ?? this._currentThread ?? this.Agent.GetNewThread();
TState? currentState = this._context != null ? this._context.CurrentState : default;
bool agentChanged = this.Agent != this._currentAgent;
bool threadChanged = thread != this._currentThread;
bool stateChanged = !EqualityComparer<TState?>.Default.Equals(this.State, currentState);
bool refresh = agentChanged || threadChanged || stateChanged;
if (refresh)
{
this._context?.Dispose();
this._context = null;
this._currentThread = null;
}
this._currentAgent = this.Agent;
this._currentThread = thread;
// Agent is validated non-null above, thread is either from Thread parameter or GetNewThread()
bool isNewContext = this._context == null;
this._context ??= new AgentBoundaryContext<TState>(this.Agent!, thread!);
this._context.CurrentState = this.State;
// Invoke the context created callback if we created a new context
if (isNewContext && this.OnContextCreated.HasDelegate)
{
_ = this.OnContextCreated.InvokeAsync(this._context);
}
if (refresh)
{
this.Render();
}
return Task.CompletedTask;
}
private void Render()
{
this._renderHandle.Render(builder =>
{
builder.OpenComponent<CascadingValue<AgentBoundaryContext<TState>>>(0);
builder.AddComponentParameter(1, "Value", this._context);
builder.AddComponentParameter(2, "IsFixed", true);
builder.AddComponentParameter(3, "ChildContent", this._renderWithState);
builder.CloseComponent();
});
}
protected virtual void RenderWithState(RenderTreeBuilder builder)
{
builder.OpenComponent<CascadingValue<TState?>>(0);
builder.AddComponentParameter(1, "Value", this.State);
builder.AddComponentParameter(2, "IsFixed", false);
builder.AddComponentParameter(3, "ChildContent", this.ChildContent);
builder.CloseComponent();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this._disposed)
{
if (disposing)
{
this._context?.Dispose();
}
this._disposed = true;
}
}
}
public class AgentBoundary : AgentStateBoundary<object?>
{
protected override void RenderWithState(RenderTreeBuilder builder)
{
builder.OpenComponent<CascadingValue<IAgentBoundaryContext>>(0);
builder.AddComponentParameter(1, "Value", this._context);
builder.AddComponentParameter(2, "IsFixed", true);
builder.AddComponentParameter(3, "ChildContent", this.ChildContent);
builder.CloseComponent();
}
}
@@ -1,411 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI;
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public sealed partial class AgentBoundaryContext<TState> : IAgentBoundaryContext, IDisposable
{
private readonly AIAgent _agent;
private readonly AgentThread _thread;
private readonly CancellationTokenSource _cancellationTokenSource;
private readonly List<ChatMessage> _messages = [];
private readonly List<ChatMessage> _pendingMessages = [];
private readonly List<Action> _messageChangeSubscribers = [];
private readonly List<Action> _responseUpdateSubscribers = [];
private readonly List<Action> _runStatusSubscribers = [];
private readonly List<AITool> _tools = [];
private readonly Dictionary<string, TaskCompletionSource<object>> _pendingResponses = [];
public CancellationToken CancellationToken => this._cancellationTokenSource.Token;
public IReadOnlyList<ChatMessage> CompletedMessages => this._messages.AsReadOnly();
public TState? CurrentState { get; set; }
public IReadOnlyList<ChatMessage> PendingMessages => this._pendingMessages.AsReadOnly();
public ChatMessage? CurrentMessage { get; private set; }
public ChatResponseUpdate? CurrentUpdate { get; private set; }
/// <summary>
/// Gets whether the agent is currently processing a turn.
/// </summary>
public bool IsProcessing { get; private set; }
public AgentBoundaryContext(AIAgent agent, AgentThread thread)
{
this._agent = agent;
this._thread = thread;
this._cancellationTokenSource = new CancellationTokenSource();
}
/// <summary>
/// Registers a client-side tool that will be passed to the agent during invocation.
/// These tools are sent to the server via ChatClientAgentRunOptions.ChatOptions.Tools.
/// </summary>
public void RegisterTool(AITool tool)
{
ArgumentNullException.ThrowIfNull(tool);
this._tools.Add(tool);
}
/// <summary>
/// Registers multiple client-side tools that will be passed to the agent during invocation.
/// </summary>
public void RegisterTools(params AITool[] tools)
{
foreach (var tool in tools)
{
this.RegisterTool(tool);
}
}
/// <summary>
/// Creates a pending response for the given key and returns a task that completes when ProvideResponse is called.
/// Used by frontend tools to wait for user input from UI components.
/// </summary>
/// <param name="key">A unique key to identify this pending response (e.g., function call ID).</param>
/// <returns>A task that completes with the response object when ProvideResponse is called.</returns>
public Task<object> WaitForResponse(string key)
{
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
this._pendingResponses[key] = tcs;
return tcs.Task;
}
/// <summary>
/// Provides a response for a pending request, completing the task returned by WaitForResponse.
/// Called by UI components when user interaction is complete.
/// </summary>
/// <param name="key">The key used when calling WaitForResponse.</param>
/// <param name="response">The response object to return.</param>
/// <returns>True if the response was provided successfully, false if no pending request was found.</returns>
public bool ProvideResponse(string key, object response)
{
if (this._pendingResponses.TryGetValue(key, out var tcs))
{
this._pendingResponses.Remove(key);
tcs.TrySetResult(response);
return true;
}
return false;
}
public void Dispose()
{
this._cancellationTokenSource.Cancel();
this._cancellationTokenSource.Dispose();
GC.SuppressFinalize(this);
}
public async Task SendAsync(params ChatMessage[] userMessages)
{
// This starts a new turn. Once completed, we will add the new messages to the conversation.
this._pendingMessages.Clear();
this._pendingMessages.AddRange(userMessages);
// Mark as processing
this.IsProcessing = true;
// Notify subscribers that run status changed (processing started)
this.TriggerRunStatusChanged();
// User messages added, notify subscribers.
this.TriggerMessageChanges();
// Build run options with registered tools if any
AgentRunOptions? options = null;
if (this._tools.Count > 0)
{
options = new ChatClientAgentRunOptions(new ChatOptions
{
Tools = [.. this._tools]
});
}
// Start a turn. Collect all updates as we stream them.
await foreach (var update in this._agent.RunStreamingAsync(
userMessages,
this._thread,
options,
this._cancellationTokenSource.Token))
{
var chatUpdate = update.AsChatResponseUpdate();
this.CurrentUpdate = chatUpdate;
// Notify subscribers of the new update, this always happens before a new message is created/updated.
this.TriggerChatResponseUpdate();
// This creates or adds a new message to the pending messages as needed.
var isNewMessage = MessageHelpers.ProcessUpdate(chatUpdate, this._pendingMessages);
if (isNewMessage)
{
this.CurrentMessage = this._pendingMessages[this._pendingMessages.Count - 1];
// Finalize the previous message's content if we have 2 or more messages now.
if (this._pendingMessages.Count > userMessages.Length + 1)
{
MessageHelpers.CoalesceContent(this._pendingMessages[this._pendingMessages.Count - 2].Contents);
}
// Notify subscribers of new message
this.TriggerMessageChanges();
}
}
// Process any remaining updates to finalize the last message.
if (this._pendingMessages.Count > userMessages.Length)
{
MessageHelpers.CoalesceContent(this._pendingMessages[this._pendingMessages.Count - 1].Contents);
}
// Add the new messages to the conversation
this._messages.AddRange(this._pendingMessages);
// Finish the turn
this._pendingMessages.Clear();
this.CurrentMessage = null;
this.CurrentUpdate = null;
this.IsProcessing = false;
// Notify subscribers that run status changed (processing ended)
this.TriggerRunStatusChanged();
// Notify subscribers
this.TriggerChatResponseUpdate();
this.TriggerMessageChanges();
}
private void TriggerChatResponseUpdate()
{
// Iterate backwards to avoid issues if subscribers are removed during iteration
for (var i = this._responseUpdateSubscribers.Count - 1; i >= 0; i--)
{
this._responseUpdateSubscribers[i]();
}
}
private void TriggerMessageChanges()
{
// Iterate backwards to avoid issues if subscribers are removed during iteration
for (var i = this._messageChangeSubscribers.Count - 1; i >= 0; i--)
{
this._messageChangeSubscribers[i]();
}
}
public MessageSubscription SubscribeToMessageChanges(Action onNewMessage)
{
return new MessageSubscription(this._messageChangeSubscribers, onNewMessage);
}
public ResponseUpdateSubscription SubscribeToResponseUpdates(Action onChatResponse)
{
return new ResponseUpdateSubscription(this._responseUpdateSubscribers, onChatResponse);
}
public RunStatusSubscription SubscribeToRunStatusChanges(Action onRunStatusChanged)
{
return new RunStatusSubscription(this._runStatusSubscribers, onRunStatusChanged);
}
private void TriggerRunStatusChanged()
{
// Iterate backwards to avoid issues if subscribers are removed during iteration
for (var i = this._runStatusSubscribers.Count - 1; i >= 0; i--)
{
this._runStatusSubscribers[i]();
}
}
}
public interface IAgentBoundaryContext
{
// Push new messages to the agent
Task SendAsync(params ChatMessage[] userMessages);
// All message interactions from previous turns
IReadOnlyList<ChatMessage> CompletedMessages { get; }
// All message interactions from the current turn. These represent completed messages only.
IReadOnlyList<ChatMessage> PendingMessages { get; }
// The current message being processed by the agent.
ChatMessage? CurrentMessage { get; }
ChatResponseUpdate? CurrentUpdate { get; }
/// <summary>
/// Gets whether the agent is currently processing a turn.
/// </summary>
bool IsProcessing { get; }
// Triggered any time there is a change on a message.
MessageSubscription SubscribeToMessageChanges(Action onNewMessage);
ResponseUpdateSubscription SubscribeToResponseUpdates(Action onChatResponse);
/// <summary>
/// Subscribes to run status changes (when processing starts or ends).
/// </summary>
/// <param name="onRunStatusChanged">The callback to invoke when run status changes.</param>
/// <returns>A subscription that can be disposed to unsubscribe.</returns>
RunStatusSubscription SubscribeToRunStatusChanges(Action onRunStatusChanged);
CancellationToken CancellationToken { get; }
/// <summary>
/// Registers a client-side tool that will be passed to the agent during invocation.
/// </summary>
void RegisterTool(AITool tool);
/// <summary>
/// Registers multiple client-side tools that will be passed to the agent during invocation.
/// </summary>
void RegisterTools(params AITool[] tools);
/// <summary>
/// Creates a pending response for the given key and returns a task that completes when ProvideResponse is called.
/// Used by frontend tools to wait for user input from UI components.
/// </summary>
/// <param name="key">A unique key to identify this pending response (e.g., function call ID).</param>
/// <returns>A task that completes with the response object when ProvideResponse is called.</returns>
Task<object> WaitForResponse(string key);
/// <summary>
/// Provides a response for a pending request, completing the task returned by WaitForResponse.
/// Called by UI components when user interaction is complete.
/// </summary>
/// <param name="key">The key used when calling WaitForResponse.</param>
/// <param name="response">The response object to return.</param>
/// <returns>True if the response was provided successfully, false if no pending request was found.</returns>
bool ProvideResponse(string key, object response);
}
public readonly struct MessageSubscription : IDisposable, IEquatable<MessageSubscription>
{
internal readonly List<Action> _subscribers;
internal readonly Action _subscription;
internal MessageSubscription(List<Action> subscribers, Action subscription)
{
this._subscribers = subscribers;
this._subscription = subscription;
this._subscribers.Add(this._subscription);
}
public void Dispose() => this._subscribers.Remove(this._subscription);
public override bool Equals(object? obj)
{
return obj is MessageSubscription subscription && this.Equals(subscription);
}
public bool Equals(MessageSubscription other)
{
return EqualityComparer<List<Action>>.Default.Equals(this._subscribers, other._subscribers) &&
EqualityComparer<Action>.Default.Equals(this._subscription, other._subscription);
}
public override int GetHashCode()
{
return HashCode.Combine(this._subscribers, this._subscription);
}
public static bool operator ==(MessageSubscription left, MessageSubscription right)
{
return left.Equals(right);
}
public static bool operator !=(MessageSubscription left, MessageSubscription right)
{
return !(left == right);
}
}
public readonly struct ResponseUpdateSubscription : IDisposable, IEquatable<ResponseUpdateSubscription>
{
private readonly List<Action> _subscribers;
private readonly Action _subscription;
public ResponseUpdateSubscription(List<Action> subscribers, Action subscription)
{
this._subscribers = subscribers;
this._subscription = subscription;
this._subscribers.Add(this._subscription);
}
public void Dispose() => this._subscribers.Remove(this._subscription);
public override bool Equals(object? obj)
{
return obj is ResponseUpdateSubscription subscription && this.Equals(subscription);
}
public bool Equals(ResponseUpdateSubscription other)
{
return EqualityComparer<List<Action>>.Default.Equals(this._subscribers, other._subscribers) &&
EqualityComparer<Action>.Default.Equals(this._subscription, other._subscription);
}
public override int GetHashCode()
{
return HashCode.Combine(this._subscribers, this._subscription);
}
public static bool operator ==(ResponseUpdateSubscription left, ResponseUpdateSubscription right)
{
return left.Equals(right);
}
public static bool operator !=(ResponseUpdateSubscription left, ResponseUpdateSubscription right)
{
return !(left == right);
}
}
public readonly struct RunStatusSubscription : IDisposable, IEquatable<RunStatusSubscription>
{
private readonly List<Action> _subscribers;
private readonly Action _subscription;
public RunStatusSubscription(List<Action> subscribers, Action subscription)
{
this._subscribers = subscribers;
this._subscription = subscription;
this._subscribers.Add(this._subscription);
}
public void Dispose() => this._subscribers.Remove(this._subscription);
public override bool Equals(object? obj)
{
return obj is RunStatusSubscription subscription && this.Equals(subscription);
}
public bool Equals(RunStatusSubscription other)
{
return EqualityComparer<List<Action>>.Default.Equals(this._subscribers, other._subscribers) &&
EqualityComparer<Action>.Default.Equals(this._subscription, other._subscription);
}
public override int GetHashCode()
{
return HashCode.Combine(this._subscribers, this._subscription);
}
public static bool operator ==(RunStatusSubscription left, RunStatusSubscription right)
{
return left.Equals(right);
}
public static bool operator !=(RunStatusSubscription left, RunStatusSubscription right)
{
return !(left == right);
}
}
@@ -1,94 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public sealed partial class AgentInput : IComponent, IDisposable
{
private RenderHandle _renderHandle;
private AgentBoundaryContext<object?>? _context;
private string? _inputText;
private RunStatusSubscription? _subscription;
[CascadingParameter] public AgentBoundaryContext<object?>? AgentContext { get; set; }
[Parameter] public string Placeholder { get; set; } = "Type a message...";
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
// Unsubscribe from previous context if it changed
if (this._context != this.AgentContext)
{
this._subscription?.Dispose();
this._context = this.AgentContext;
// Subscribe to run status changes
if (this._context != null)
{
this._subscription = this._context.SubscribeToRunStatusChanges(this.OnRunStatusChanged);
}
}
this.Render();
return Task.CompletedTask;
}
private void OnRunStatusChanged()
{
this.Render();
}
private void Render()
{
var isProcessing = this._context?.IsProcessing ?? false;
var isDisabled = string.IsNullOrWhiteSpace(this._inputText) || isProcessing;
this._renderHandle.Render(builder =>
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "agent-input");
builder.OpenElement(2, "textarea");
builder.AddAttribute(3, "value", this._inputText);
builder.AddAttribute(4, "oninput", EventCallback.Factory.Create<ChangeEventArgs>(this, e => { this._inputText = e.Value?.ToString(); this.Render(); }));
builder.AddAttribute(5, "placeholder", this.Placeholder);
builder.AddAttribute(6, "rows", "1");
builder.AddAttribute(7, "disabled", isProcessing);
builder.CloseElement();
builder.OpenElement(8, "button");
builder.AddAttribute(9, "class", "send-button");
builder.AddAttribute(10, "onclick", EventCallback.Factory.Create(this, this.SendAsync));
builder.AddAttribute(11, "disabled", isDisabled);
builder.AddMarkupContent(12, """<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M2.01 21L23 12 2.01 3 2 10l15 2-15 2z"/></svg>""");
builder.CloseElement(); // close button
builder.CloseElement(); // close div
});
}
private async Task SendAsync()
{
if (!string.IsNullOrWhiteSpace(this._inputText) && this._context is { IsProcessing: false })
{
var text = this._inputText;
this._inputText = ""; // Clear input immediately
this.Render(); // Re-render to clear input
await this._context.SendAsync(new ChatMessage(ChatRole.User, text));
}
}
public void Dispose()
{
this._subscription?.Dispose();
}
}
@@ -1,72 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// A component that displays a loading indicator (three animated dots) when the agent is processing.
/// </summary>
public sealed partial class AgentLoadingIndicator : IComponent, IDisposable
{
private RenderHandle _renderHandle;
private AgentBoundaryContext<object?>? _context;
private RunStatusSubscription? _subscription;
[CascadingParameter] public AgentBoundaryContext<object?>? AgentContext { get; set; }
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
// Unsubscribe from previous context if it changed
if (this._context != this.AgentContext)
{
this._subscription?.Dispose();
this._context = this.AgentContext;
// Subscribe to run status changes
if (this._context != null)
{
this._subscription = this._context.SubscribeToRunStatusChanges(this.OnRunStatusChanged);
}
}
this.Render();
return Task.CompletedTask;
}
private void OnRunStatusChanged()
{
this.Render();
}
private void Render()
{
var isProcessing = this._context?.IsProcessing ?? false;
this._renderHandle.Render(builder =>
{
if (!isProcessing)
{
return;
}
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "agent-loading-indicator");
builder.OpenElement(2, "div");
builder.AddAttribute(3, "class", "agent-loading-dots");
builder.AddMarkupContent(4, "<span></span><span></span><span></span>");
builder.CloseElement();
builder.CloseElement();
});
}
public void Dispose()
{
this._subscription?.Dispose();
}
}
@@ -1,93 +0,0 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@namespace Microsoft.AspNetCore.Components.AI
@using Microsoft.Extensions.AI
@typeparam TState
@implements IDisposable
<CascadingValue Value="@CurrentState" IsFixed="false">
@ChildContent
</CascadingValue>
@code {
private ResponseUpdateSubscription? _subscription;
/// <summary>
/// The agent boundary context to subscribe to for state updates.
/// </summary>
[CascadingParameter]
public IAgentBoundaryContext? BoundaryContext { get; set; }
/// <summary>
/// The current state value. Can be set initially and will be updated by state events.
/// </summary>
[Parameter]
public TState? CurrentState { get; set; }
/// <summary>
/// Callback to deserialize a STATE_SNAPSHOT (application/json) into TState.
/// </summary>
[Parameter]
public Func<ReadOnlyMemory<byte>, TState?>? OnSnapshot { get; set; }
/// <summary>
/// Callback to apply a STATE_DELTA (application/json-patch+json) to the current state.
/// Returns the updated state.
/// </summary>
[Parameter]
public Func<TState?, ReadOnlyMemory<byte>, TState?>? OnDelta { get; set; }
/// <summary>
/// Optional callback invoked whenever state changes.
/// </summary>
[Parameter]
public EventCallback<TState?> CurrentStateChanged { get; set; }
/// <summary>
/// Child content that will receive the cascaded state.
/// </summary>
[Parameter]
public RenderFragment? ChildContent { get; set; }
protected override void OnInitialized()
{
if (BoundaryContext is not null)
{
_subscription = BoundaryContext.SubscribeToResponseUpdates(OnResponseUpdate);
}
}
private void OnResponseUpdate()
{
var update = BoundaryContext?.CurrentUpdate;
if (update?.Contents is null)
{
return;
}
foreach (var content in update.Contents)
{
if (content is DataContent dataContent)
{
if (string.Equals(dataContent.MediaType, "application/json", StringComparison.OrdinalIgnoreCase) && OnSnapshot is not null)
{
// STATE_SNAPSHOT - let app deserialize
CurrentState = OnSnapshot(dataContent.Data);
_ = CurrentStateChanged.InvokeAsync(CurrentState);
InvokeAsync(StateHasChanged);
}
else if (string.Equals(dataContent.MediaType, "application/json-patch+json", StringComparison.OrdinalIgnoreCase) && OnDelta is not null)
{
// STATE_DELTA - let app apply the patch
CurrentState = OnDelta(CurrentState, dataContent.Data);
_ = CurrentStateChanged.InvokeAsync(CurrentState);
InvokeAsync(StateHasChanged);
}
}
}
}
public void Dispose()
{
_subscription?.Dispose();
}
}
@@ -1,148 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Represents a suggestion that can be displayed and sent to the agent.
/// </summary>
public readonly struct Suggestion : IEquatable<Suggestion>
{
/// <summary>
/// Initializes a new instance of the <see cref="Suggestion"/> struct.
/// </summary>
/// <param name="text">The display text for the suggestion.</param>
/// <param name="message">The message to send when the suggestion is selected.</param>
public Suggestion(string text, ChatMessage message)
{
this.Text = text;
this.Message = message;
}
/// <summary>
/// Initializes a new instance of the <see cref="Suggestion"/> struct with a simple text message.
/// </summary>
/// <param name="text">The display text for the suggestion, also used as the message content.</param>
public Suggestion(string text)
{
this.Text = text;
this.Message = new ChatMessage(ChatRole.User, text);
}
/// <summary>
/// Gets the display text for the suggestion.
/// </summary>
public string Text { get; }
/// <summary>
/// Gets the message to send when the suggestion is selected.
/// </summary>
public ChatMessage Message { get; }
/// <inheritdoc/>
public override bool Equals(object? obj) => obj is Suggestion other && this.Equals(other);
/// <inheritdoc/>
public bool Equals(Suggestion other) => this.Text == other.Text;
/// <inheritdoc/>
public override int GetHashCode() => this.Text?.GetHashCode() ?? 0;
/// <summary>
/// Determines whether two <see cref="Suggestion"/> instances are equal.
/// </summary>
public static bool operator ==(Suggestion left, Suggestion right) => left.Equals(right);
/// <summary>
/// Determines whether two <see cref="Suggestion"/> instances are not equal.
/// </summary>
public static bool operator !=(Suggestion left, Suggestion right) => !left.Equals(right);
}
public sealed partial class AgentSuggestions : IComponent, IDisposable
{
private RenderHandle _renderHandle;
private AgentBoundaryContext<object?>? _context;
private IReadOnlyList<Suggestion>? _suggestions;
private RunStatusSubscription? _subscription;
[CascadingParameter] public AgentBoundaryContext<object?>? AgentContext { get; set; }
[Parameter] public IReadOnlyList<Suggestion>? Suggestions { get; set; }
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
// Unsubscribe from previous context if it changed
if (this._context != this.AgentContext)
{
this._subscription?.Dispose();
this._context = this.AgentContext;
// Subscribe to run status changes
if (this._context != null)
{
this._subscription = this._context.SubscribeToRunStatusChanges(this.OnRunStatusChanged);
}
}
this._suggestions = this.Suggestions;
this.Render();
return Task.CompletedTask;
}
private void OnRunStatusChanged()
{
this.Render();
}
private void Render()
{
var isProcessing = this._context?.IsProcessing ?? false;
this._renderHandle.Render(builder =>
{
if (this._suggestions is null || this._suggestions.Count == 0)
{
return;
}
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "agent-suggestions");
for (var i = 0; i < this._suggestions.Count; i++)
{
var suggestion = this._suggestions[i];
builder.OpenElement(2, "button");
builder.SetKey(suggestion.Text);
builder.AddAttribute(3, "class", "suggestion-button");
builder.AddAttribute(4, "onclick", EventCallback.Factory.Create(this, () => this.SelectSuggestionAsync(suggestion)));
builder.AddAttribute(5, "disabled", isProcessing);
builder.AddContent(6, suggestion.Text);
builder.CloseElement();
}
builder.CloseElement();
});
}
private async Task SelectSuggestionAsync(Suggestion suggestion)
{
if (this._context is { IsProcessing: false })
{
await this._context.SendAsync(suggestion.Message);
}
}
public void Dispose()
{
this._subscription?.Dispose();
}
}
@@ -1,33 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.AI;
#pragma warning disable CA1812 // Internal class is apparently never instantiated
internal sealed class ContentBlock : IComponent
#pragma warning restore CA1812 // Internal class is apparently never instantiated
{
private RenderHandle _renderHandle;
[Parameter] public RenderFragment ChildContent { get; set; } = default!;
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
// Always render when parameters are set - the parent component (MessageList)
// only triggers renders when there are actual updates to show.
this._renderHandle.Render(this.Render);
return Task.CompletedTask;
}
private void Render(RenderTreeBuilder builder)
{
builder.AddContent(0, this.ChildContent);
}
}
@@ -1,10 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public class ContentContext(AIContent content)
{
public AIContent Content { get; init; } = content;
}
@@ -1,14 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.AspNetCore.Components.AI;
public abstract class ContentTemplateBase : IComponent
{
public abstract void Attach(RenderHandle renderHandle);
public abstract Task SetParametersAsync(ParameterView parameters);
public virtual bool When(ContentContext context) => true;
[Parameter] public RenderFragment<ContentContext> ChildContent { get; set; } = (content) => builder => { };
}
@@ -1,36 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Default template for rendering DataContent. Renders nothing by default
/// as DataContent typically contains binary/JSON data not meant for display.
/// </summary>
public class DataContentTemplate : ContentTemplateBase
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
this.ChildContent = this.RenderData;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
public override bool When(ContentContext context)
{
return context.Content is DataContent;
}
private RenderFragment RenderData(ContentContext content) => builder =>
{
// By default, render nothing.
};
}
@@ -1,43 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.AspNetCore.Components;
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Default template for rendering ErrorContent. Renders nothing by default
/// to prevent the error from crashing the UI. More specific templates can
/// override this to display error messages.
/// </summary>
public class ErrorTemplate : ContentTemplateBase
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
this.ChildContent = this.RenderError;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
/// <summary>
/// Determines if this template should handle the given content.
/// Matches ErrorContent.
/// </summary>
public override bool When(ContentContext context)
{
return context.Content is ErrorContent;
}
private RenderFragment RenderError(ContentContext content) => builder =>
{
// By default, render nothing.
// Specific templates can override to display error messages.
};
}
@@ -1,60 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Template for rendering function/tool call content in messages.
/// Provides access to both the call and its result (when available) via InvocationContext.
/// </summary>
public class FunctionCallTemplate : ContentTemplateBase
{
/// <summary>
/// Gets or sets the tool name to filter on. If null, matches all function calls.
/// </summary>
[Parameter] public string? ToolName { get; set; }
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
// This component never renders anything by itself.
this.ChildContent = this.RenderFunctionCall;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
/// <summary>
/// Determines if this template should handle the given content.
/// </summary>
/// <param name="context">The content context.</param>
/// <returns>True if this template should render the content.</returns>
public override bool When(ContentContext context)
{
if (context.Content is not FunctionCallContent call)
{
return false;
}
// Filter by tool name if specified
if (this.ToolName != null && !string.Equals(call.Name, this.ToolName, StringComparison.OrdinalIgnoreCase))
{
return false;
}
return true;
}
private RenderFragment RenderFunctionCall(ContentContext content) => builder =>
{
// By default, function calls are not rendered visually.
// Custom templates (like WeatherCallTemplate) can override this
// behavior for specific functions by registering before this template.
};
}
@@ -1,39 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Default template for FunctionResultContent that renders nothing.
/// Function results are internal tool responses and typically don't need
/// visual representation in the chat UI.
/// </summary>
public class FunctionResultTemplate : ContentTemplateBase
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
// This component never renders anything by itself.
this.ChildContent = this.RenderFunctionResult;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
/// <summary>
/// Only match FunctionResultContent.
/// </summary>
public override bool When(ContentContext context) => context.Content is FunctionResultContent;
private RenderFragment RenderFunctionResult(ContentContext content) => builder =>
{
// By default, function results are not rendered visually.
// The result data is typically processed by the agent to generate text responses.
};
}
@@ -1,135 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Context for a function invocation, tracking the call and its result.
/// </summary>
public class InvocationContext
{
private Action? _resultArrived;
/// <summary>
/// Initializes a new instance of the <see cref="InvocationContext"/> class.
/// </summary>
/// <param name="call">The function call content.</param>
public InvocationContext(FunctionCallContent call)
{
this.Call = call;
}
/// <summary>
/// Gets the function call content.
/// </summary>
public FunctionCallContent Call { get; }
/// <summary>
/// Gets the function result content, if available.
/// </summary>
public FunctionResultContent? ResultContent { get; private set; }
/// <summary>
/// Gets a value indicating whether the result has arrived.
/// </summary>
public bool HasResult => this.ResultContent != null;
/// <summary>
/// Gets the function name from the call.
/// </summary>
public string FunctionName => this.Call.Name;
/// <summary>
/// Gets the call ID.
/// </summary>
public string CallId => this.Call.CallId;
/// <summary>
/// Gets the arguments from the call.
/// </summary>
public IDictionary<string, object?>? Arguments => this.Call.Arguments;
/// <summary>
/// Event raised when the result arrives.
/// </summary>
#pragma warning disable CA1003 // Use generic event handler instances
public event Action? ResultArrived
#pragma warning restore CA1003 // Use generic event handler instances
{
add => this._resultArrived += value;
remove => this._resultArrived -= value;
}
/// <summary>
/// Sets the result and raises the ResultArrived event.
/// </summary>
/// <param name="result">The function result content.</param>
internal void SetResult(FunctionResultContent result)
{
this.ResultContent = result;
this._resultArrived?.Invoke();
this._resultArrived = null; // Clear invocation list after firing
}
/// <summary>
/// Gets an argument value by name, deserializing from JSON if necessary.
/// </summary>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <param name="name">The argument name.</param>
/// <returns>The argument value, or default if not found.</returns>
public T? GetArgument<T>(string name)
{
if (this.Arguments is null || !this.Arguments.TryGetValue(name, out var value))
{
return default;
}
if (value is null)
{
return default;
}
if (value is T typed)
{
return typed;
}
if (value is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>();
}
// Try to convert via JSON serialization
var json = JsonSerializer.Serialize(value);
return JsonSerializer.Deserialize<T>(json);
}
/// <summary>
/// Gets the result as a specific type, deserializing from JSON if necessary.
/// </summary>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The result value, or default if not available.</returns>
public T? GetResult<T>()
{
if (this.ResultContent?.Result is null)
{
return default;
}
if (this.ResultContent.Result is T typed)
{
return typed;
}
if (this.ResultContent.Result is JsonElement jsonElement)
{
return jsonElement.Deserialize<T>();
}
// Try to convert via JSON serialization
var json = JsonSerializer.Serialize(this.ResultContent.Result);
return JsonSerializer.Deserialize<T>(json);
}
}
@@ -1,37 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public class TextTemplate : ContentTemplateBase
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
// This component never renders anything by itself.
this.ChildContent = this.RenderText;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
/// <summary>
/// Only match TextContent, allowing other content templates to handle
/// FunctionCallContent, FunctionResultContent, etc.
/// </summary>
public override bool When(ContentContext context) => context.Content is TextContent;
private RenderFragment RenderText(ContentContext content) => builder =>
{
if (content.Content is TextContent textContent)
{
builder.AddContent(0, textContent.Text);
}
};
}
@@ -1,116 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
#pragma warning disable CA1812 // Internal class is apparently never instantiated
internal sealed class DefaultMessageTemplate : MessageTemplateBase
#pragma warning restore CA1812 // Internal class is apparently never instantiated
{
public override bool When(MessageContext context) => true;
// Buffer to convert response updates to chat messages.
private readonly List<ChatMessage> _buffer = [];
public DefaultMessageTemplate()
{
this.ChildContent = this.SelectTemplate;
}
private RenderFragment SelectTemplate(MessageContext messageContext)
{
if (messageContext.ChatMessage is not null)
{
var getRenderContents = messageContext.RenderContents();
// Return a render fragment that checks visibility at render time
// This is important because message contents may change during streaming
return CreateRenderMessage(
messageContext.ChatMessage,
getRenderContents);
}
else
{
var getRenderContents = messageContext.RenderContents();
var updates = messageContext.ResponseUpdates ?? [];
if (updates.Count == 0)
{
throw new InvalidOperationException("MessageContext must have either a ChatMessage or at least one ResponseUpdate.");
}
this._buffer.Clear();
this._buffer.AddMessages(updates);
if (this._buffer.Count != 1)
{
throw new InvalidOperationException("DefaultMessageTemplate only supports a single ResponseUpdate.");
}
return CreateRenderMessage(
this._buffer[0],
getRenderContents);
}
}
/// <summary>
/// Checks if a message has any visible content that should be displayed.
/// Messages that only contain FunctionResultContent are internal tool messages
/// and should not be rendered as chat bubbles.
/// FunctionCallContent is considered visible because content templates can render them
/// (e.g., WeatherCallTemplate renders weather tool calls as cards).
/// </summary>
private static bool HasVisibleContent(ChatMessage message)
{
if (message.Contents.Count == 0)
{
return false;
}
foreach (var content in message.Contents)
{
// TextContent is visible
if (content is TextContent textContent && !string.IsNullOrWhiteSpace(textContent.Text))
{
return true;
}
// FunctionCallContent is visible - content templates can render them
// (e.g., WeatherCallTemplate renders weather tool calls as cards)
if (content is FunctionCallContent)
{
return true;
}
// Other content types that are not function results are visible
if (content is not FunctionResultContent)
{
return true;
}
}
return false;
}
private static RenderFragment CreateRenderMessage(
ChatMessage message,
RenderFragment getRenderContents)
{
var roleClass = $"{message.Role}-message";
return builder =>
{
// Check visibility at render time, not at template creation time
// This is important because message contents may change during streaming
if (!HasVisibleContent(message))
{
return; // Don't render messages without visible content
}
builder.OpenElement(0, "div");
if (!string.IsNullOrEmpty(message.MessageId))
{
builder.AddAttribute(1, "id", message.MessageId);
}
builder.AddAttribute(2, "class", $"chat-message {roleClass}");
builder.AddContent(3, getRenderContents);
builder.CloseElement();
};
}
}
@@ -1,96 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public class MessageContext
{
private readonly MessageListContext _messageListContext;
private RenderFragment? _contentsRenderer;
private MessageTemplateBase? _template;
private readonly Dictionary<AIContent, RenderFragment> _contentRenderers = [];
internal MessageContext(ChatMessage message, MessageListContext messageListContext)
{
this.ChatMessage = message;
this._messageListContext = messageListContext;
}
public ChatMessage? ChatMessage { get; init; }
public IList<ChatResponseUpdate>? ResponseUpdates { get; init; }
public RenderFragment RenderContents() => this.GetOrCreateContentsRenderer();
internal void SetTemplate(MessageTemplateBase template)
{
this._template = template;
}
private RenderFragment GetOrCreateContentsRenderer()
{
if (this._contentsRenderer != null)
{
return this._contentsRenderer;
}
if (this._template == null)
{
throw new InvalidOperationException("Message template has not been set for this message context.");
}
this._contentsRenderer = builder =>
{
if (this.ChatMessage != null)
{
for (int i = 0; i < this.ChatMessage.Contents.Count; i++)
{
var content = this.ChatMessage.Contents[i];
builder.AddContent(0, this.ResolveContentRenderer(content));
}
}
else if (this.ResponseUpdates != null)
{
for (int i = 0; i < this.ResponseUpdates.Count; i++)
{
var update = this.ResponseUpdates[i];
for (int j = 0; j < update.Contents.Count; j++)
{
var content = update.Contents[j];
if (this._contentRenderers.TryGetValue(content, out var contentRenderer))
{
builder.AddContent(0, contentRenderer);
}
}
}
}
else
{
throw new InvalidOperationException("MessageContext must have either a ChatMessage or ResponseUpdates to render contents.");
}
};
return this._contentsRenderer;
}
private RenderFragment ResolveContentRenderer(AIContent content)
{
if (this._contentRenderers.TryGetValue(content, out var contentRenderer))
{
return contentRenderer;
}
Debug.Assert(this._template != null);
contentRenderer = this._template.GetContentTemplate(content);
if (contentRenderer != null)
{
this._contentRenderers[content] = contentRenderer;
return contentRenderer;
}
contentRenderer = this._messageListContext.GetContentTemplate(content);
this._contentRenderers[content] = contentRenderer;
return contentRenderer;
}
}
@@ -1,322 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text;
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
// Roughly lifted from src/Libraries/Microsoft.Extensions.AI.Abstractions/ChatCompletion/ChatResponseExtensions.cs
internal static class MessageHelpers
{
internal static void CoalesceContent(IList<AIContent> contents)
{
Coalesce<TextContent>(
contents,
mergeSingle: false,
canMerge: null,
static (contents, start, end) => new(MergeText(contents, start, end)) { AdditionalProperties = contents[start].AdditionalProperties?.Clone() });
Coalesce(
contents,
mergeSingle: false,
canMerge: static (r1, r2) => string.IsNullOrEmpty(r1.ProtectedData), // we allow merging if the first item has no ProtectedData, even if the second does
static (contents, start, end) =>
{
TextReasoningContent content = new(MergeText(contents, start, end))
{
AdditionalProperties = contents[start].AdditionalProperties?.Clone()
};
#if DEBUG
for (int i = start; i < end - 1; i++)
{
Debug.Assert(contents[i] is TextReasoningContent { ProtectedData: null }, "Expected all but the last to have a null ProtectedData");
}
#endif
if (((TextReasoningContent)contents[end - 1]).ProtectedData is { } protectedData)
{
content.ProtectedData = protectedData;
}
return content;
});
Coalesce(
contents,
mergeSingle: false,
canMerge: static (r1, r2) => r1.MediaType == r2.MediaType && r1.HasTopLevelMediaType("text") && r1.Name == r2.Name,
static (contents, start, end) =>
{
Debug.Assert(end - start > 1, "Expected multiple contents to merge");
MemoryStream ms = new();
for (int i = start; i < end; i++)
{
var current = (DataContent)contents[i];
#if NET
ms.Write(current.Data.Span);
#else
if (!MemoryMarshal.TryGetArray(current.Data, out var segment))
{
segment = new(current.Data.ToArray());
}
ms.Write(segment.Array!, segment.Offset, segment.Count);
#endif
}
var first = (DataContent)contents[start];
return new DataContent(new ReadOnlyMemory<byte>(ms.GetBuffer(), 0, (int)ms.Length), first.MediaType) { Name = first.Name };
});
Coalesce(
contents,
mergeSingle: true,
canMerge: static (r1, r2) => r1.CallId == r2.CallId,
static (contents, start, end) =>
{
var firstContent = (CodeInterpreterToolCallContent)contents[start];
if (start == end - 1)
{
if (firstContent.Inputs is not null)
{
CoalesceContent(firstContent.Inputs);
}
return firstContent;
}
List<AIContent>? inputs = null;
for (int i = start; i < end; i++)
{
(inputs ??= []).AddRange(((CodeInterpreterToolCallContent)contents[i]).Inputs ?? []);
}
if (inputs is not null)
{
CoalesceContent(inputs);
}
return new()
{
CallId = firstContent.CallId,
Inputs = inputs,
AdditionalProperties = firstContent.AdditionalProperties?.Clone(),
};
});
Coalesce(
contents,
mergeSingle: true,
canMerge: static (r1, r2) => r1.CallId is not null && r2.CallId is not null && r1.CallId == r2.CallId,
static (contents, start, end) =>
{
var firstContent = (CodeInterpreterToolResultContent)contents[start];
if (start == end - 1)
{
if (firstContent.Outputs is not null)
{
CoalesceContent(firstContent.Outputs);
}
return firstContent;
}
List<AIContent>? output = null;
for (int i = start; i < end; i++)
{
(output ??= []).AddRange(((CodeInterpreterToolResultContent)contents[i]).Outputs ?? []);
}
if (output is not null)
{
CoalesceContent(output);
}
return new()
{
CallId = firstContent.CallId,
Outputs = output,
AdditionalProperties = firstContent.AdditionalProperties?.Clone(),
};
});
static string MergeText(IList<AIContent> contents, int start, int end)
{
Debug.Assert(end - start > 1, "Expected multiple contents to merge");
StringBuilder sb = new();
for (int i = start; i < end; i++)
{
_ = sb.Append(contents[i]);
}
return sb.ToString();
}
static void Coalesce<TContent>(
IList<AIContent> contents,
bool mergeSingle,
Func<TContent, TContent, bool>? canMerge,
Func<IList<AIContent>, int, int, TContent> merge)
where TContent : AIContent
{
// Iterate through all of the items in the list looking for contiguous items that can be coalesced.
int start = 0;
while (start < contents.Count)
{
if (!TryAsCoalescable(contents[start], out var firstContent))
{
start++;
continue;
}
// Iterate until we find a non-coalescable item.
int i = start + 1;
TContent prev = firstContent;
while (i < contents.Count && TryAsCoalescable(contents[i], out TContent? next) && (canMerge is null || canMerge(prev, next)))
{
i++;
prev = next;
}
// If there's only one item in the run, and we don't want to merge single items, skip it.
if (start == i - 1 && !mergeSingle)
{
start++;
continue;
}
// Store the replacement node and null out all of the nodes that we coalesced.
// We can then remove all coalesced nodes in one O(N) operation via RemoveAll.
// Leave start positioned at the start of the next run.
contents[start] = merge(contents, start, i);
start++;
while (start < i)
{
contents[start++] = null!;
}
static bool TryAsCoalescable(AIContent content, [NotNullWhen(true)] out TContent? coalescable)
{
if (content is TContent tmp && tmp.Annotations is not { Count: > 0 })
{
coalescable = tmp;
return true;
}
coalescable = null;
return false;
}
}
// Remove all of the null slots left over from the coalescing process.
RemoveNullContents(contents);
}
}
private static void RemoveNullContents<T>(IList<T> contents)
where T : class
{
if (contents is List<AIContent> contentsList)
{
_ = contentsList.RemoveAll(u => u is null);
}
else
{
int nextSlot = 0;
int contentsCount = contents.Count;
for (int i = 0; i < contentsCount; i++)
{
if (contents[i] is { } content)
{
contents[nextSlot++] = content;
}
}
for (int i = contentsCount - 1; i >= nextSlot; i--)
{
contents.RemoveAt(i);
}
Debug.Assert(nextSlot == contents.Count, "Expected final count to equal list length.");
}
}
internal static bool ProcessUpdate(ChatResponseUpdate update, List<ChatMessage> messages)
{
// If there is no message created yet, or if the last update we saw had a different
// identifying parts, create a new message.
bool isNewMessage = true;
if (messages.Count != 0)
{
var lastMessage = messages[messages.Count - 1];
isNewMessage =
NotEmptyOrEqual(update.AuthorName, lastMessage.AuthorName) ||
NotEmptyOrEqual(update.MessageId, lastMessage.MessageId) ||
NotNullOrEqual(update.Role, lastMessage.Role);
}
// Get the message to target, either a new one or the last ones.
ChatMessage message;
if (isNewMessage)
{
message = new(ChatRole.Assistant, []);
messages.Add(message);
}
else
{
message = messages[messages.Count - 1];
}
// Some members on ChatResponseUpdate map to members of ChatMessage.
// Incorporate those into the latest message; in cases where the message
// stores a single value, prefer the latest update's value over anything
// stored in the message.
if (update.AuthorName is not null)
{
message.AuthorName = update.AuthorName;
}
if (message.CreatedAt is null || (update.CreatedAt is not null && update.CreatedAt > message.CreatedAt))
{
message.CreatedAt = update.CreatedAt;
}
if (update.Role is ChatRole role)
{
message.Role = role;
}
if (update.MessageId is { Length: > 0 })
{
// Note that this must come after the message checks earlier, as they depend
// on this value for change detection.
message.MessageId = update.MessageId;
}
foreach (var content in update.Contents)
{
message.Contents.Add(content);
}
return isNewMessage;
}
/// <summary>Gets whether both strings are not null/empty and not the same as each other.</summary>
private static bool NotEmptyOrEqual(string? s1, string? s2) =>
s1 is { Length: > 0 } str1 && s2 is { Length: > 0 } str2 && str1 != str2;
/// <summary>Gets whether two roles are not null and not the same as each other.</summary>
private static bool NotNullOrEqual(ChatRole? r1, ChatRole? r2) =>
r1.HasValue && r2.HasValue && r1.Value != r2.Value;
}
@@ -1,139 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Linq;
using Microsoft.AspNetCore.Components.Rendering;
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
#pragma warning disable CA1812 // Internal class is apparently never instantiated
internal sealed partial class MessageList : IComponent, IDisposable
#pragma warning restore CA1812 // Internal class is apparently never instantiated
{
private RenderHandle _renderHandle;
private MessageSubscription _messageSubscription;
private ResponseUpdateSubscription _responseSubscription;
[CascadingParameter] public MessageListContext MessageListContext { get; set; } = default!;
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
}
public Task SetParametersAsync(ParameterView parameters)
{
var previousContext = this.MessageListContext;
parameters.SetParameterProperties(this);
if (previousContext != null && this.MessageListContext != previousContext)
{
throw new InvalidOperationException(
$"{nameof(MessageList)} does not support changing the {nameof(this.MessageListContext)} once it has been set.");
}
// Subscribe to message updates and response updates for streaming
this._messageSubscription = this.MessageListContext.AgentBoundaryContext.SubscribeToMessageChanges(this.ProcessUpdate);
this._responseSubscription = this.MessageListContext.AgentBoundaryContext.SubscribeToResponseUpdates(this.ProcessUpdate);
// Initial render. This component will only render once since the only parameter is a cascading parameter and it's fixed.
this._renderHandle.Render(this.Render);
return Task.CompletedTask;
}
private void ProcessUpdate()
{
// Scan all messages for function calls and results to track invocations.
// This allows us to associate results with their calls when results arrive.
foreach (var message in this.MessageListContext.AgentBoundaryContext.CompletedMessages)
{
this.ProcessMessageContents(message);
}
foreach (var message in this.MessageListContext.AgentBoundaryContext.PendingMessages)
{
this.ProcessMessageContents(message);
}
this._renderHandle.Render(this.Render);
}
private void ProcessMessageContents(ChatMessage message)
{
foreach (var content in message.Contents)
{
if (content is FunctionCallContent call)
{
this.MessageListContext.GetOrCreateInvocation(call);
}
else if (content is FunctionResultContent result)
{
this.MessageListContext.AssociateResult(result);
}
}
}
public void Render(RenderTreeBuilder builder)
{
// Track all render keys to detect duplicates
var allRenderKeys = new HashSet<string?>();
// Open container div for message list with flex layout
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "messages-container");
foreach (var message in this.MessageListContext.AgentBoundaryContext.CompletedMessages)
{
var renderKey = GetUniqueRenderKey(message);
// Calling GetTemplate will stop template collection on the first message if it
// was still ongoing.
builder.OpenComponent<ContentBlock>(2);
builder.SetKey(renderKey);
builder.AddComponentParameter(3, "ChildContent", this.MessageListContext.GetTemplate(message));
builder.CloseComponent();
}
foreach (var message in this.MessageListContext.AgentBoundaryContext.PendingMessages)
{
var renderKey = GetUniqueRenderKey(message);
builder.OpenComponent<ContentBlock>(4);
builder.SetKey(renderKey);
builder.AddComponentParameter(5, "ChildContent", this.MessageListContext.GetTemplate(message));
builder.CloseComponent();
}
// Close container div
builder.CloseElement();
}
/// <summary>
/// Gets a unique render key for a message.
/// For tool result messages (role=tool), we use MessageId + CallId to ensure uniqueness
/// because the AGUI protocol may reuse MessageId for multiple tool results in the same batch.
/// </summary>
private static string? GetUniqueRenderKey(ChatMessage message)
{
// For tool result messages, combine MessageId with CallId to ensure uniqueness
// This works around a bug in the AGUI protocol where multiple tool results
// can share the same MessageId when processed in the same update batch.
if (message.Role == ChatRole.Tool)
{
var resultContent = message.Contents.OfType<FunctionResultContent>().FirstOrDefault();
if (resultContent != null && !string.IsNullOrEmpty(resultContent.CallId))
{
return $"{message.MessageId}_{resultContent.CallId}";
}
}
return message.MessageId;
}
public void Dispose()
{
((IDisposable)this._messageSubscription).Dispose();
((IDisposable)this._responseSubscription).Dispose();
}
}
@@ -1,137 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public sealed partial class MessageListContext
{
private bool _collectingTemplates;
private readonly List<MessageTemplateBase> _templates = [];
private readonly List<ContentTemplateBase> _contentTemplates = [];
// We compute a render fragment to render each message only once and cache it here.
private readonly Dictionary<ChatMessage, RenderFragment> _templateCache = [];
// Track function invocations by CallId to associate calls with results.
private readonly Dictionary<string, InvocationContext> _invocationMap = [];
public MessageListContext(IAgentBoundaryContext context)
{
this.AgentBoundaryContext = context;
}
public IAgentBoundaryContext AgentBoundaryContext { get; }
public void BeginCollectingTemplates()
{
// This is triggered by the Messages component before rendering its children.
// In this situation we are going to render again the MessageTemplates and
// ContentTemplates and since we can't tell if they have changed we have to
// recompute all the templates again.
this._collectingTemplates = true;
this._templates.Clear();
this._contentTemplates.Clear();
this._templateCache.Clear();
}
public void RegisterTemplate(MessageTemplateBase template)
{
if (this._collectingTemplates)
{
this._templates.Add(template);
}
}
public void RegisterContentTemplate(ContentTemplateBase template)
{
if (this._collectingTemplates)
{
this._contentTemplates.Add(template);
}
}
/// <summary>
/// Gets or creates an invocation context for the given function call.
/// </summary>
/// <param name="call">The function call content.</param>
/// <returns>The invocation context for this call.</returns>
public InvocationContext GetOrCreateInvocation(FunctionCallContent call)
{
if (!this._invocationMap.TryGetValue(call.CallId, out var context))
{
context = new InvocationContext(call);
this._invocationMap[call.CallId] = context;
}
return context;
}
/// <summary>
/// Associates a function result with its corresponding call.
/// </summary>
/// <param name="result">The function result content.</param>
public void AssociateResult(FunctionResultContent result)
{
if (this._invocationMap.TryGetValue(result.CallId, out var context))
{
context.SetResult(result);
}
}
/// <summary>
/// Gets an invocation context by call ID.
/// </summary>
/// <param name="callId">The call ID.</param>
/// <returns>The invocation context, or null if not found.</returns>
public InvocationContext? GetInvocation(string callId)
{
return this._invocationMap.TryGetValue(callId, out var context) ? context : null;
}
internal RenderFragment GetTemplate(ChatMessage message)
{
// We are about to render the first message. If we were collecting templates, stop now.
this._collectingTemplates = false;
if (this._templateCache.TryGetValue(message, out var cachedTemplate))
{
return cachedTemplate;
}
var messageContext = new MessageContext(message, this);
foreach (var template in this._templates)
{
if (template.When(messageContext))
{
var chosen = template;
messageContext.SetTemplate(chosen);
// We ask the template to create a RenderFragment for the message.
// The template will render a wrapper and use the messageContext to
// render the contents.
// The template might call back through the messageContext to get renderers for
// contents if the message template doesn't override the full rendering or
// if it doesn't define the rendering for a content type.
var renderer = chosen.ChildContent(messageContext);
this._templateCache[message] = renderer;
return renderer;
}
}
throw new InvalidOperationException($"No message template found for message of type {message.Role}.");
}
internal RenderFragment GetContentTemplate(AIContent content)
{
foreach (var template in this._contentTemplates)
{
var contentContext = new ContentContext(content);
if (template.When(contentContext))
{
return template.ChildContent(contentContext);
}
}
throw new InvalidOperationException($"No content template found for content of type {content.GetType().Name}.");
}
}
@@ -1,30 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
public abstract class MessageTemplateBase : IComponent
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public abstract bool When(MessageContext context);
[Parameter] public RenderFragment<MessageContext> ChildContent { get; set; } = (message) => builder => { };
public void Attach(RenderHandle renderHandle)
{
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterTemplate(this);
return Task.CompletedTask;
}
internal RenderFragment? GetContentTemplate(AIContent content)
{
return this.Context.GetContentTemplate(content);
}
}
@@ -1,94 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.AspNetCore.Components.Rendering;
namespace Microsoft.AspNetCore.Components.AI;
public partial class Messages : IComponent
{
private RenderHandle _renderHandle;
private RenderFragment? _renderContents;
private IAgentBoundaryContext? _context;
private MessageListContext? _messageListContext;
[CascadingParameter] public IAgentBoundaryContext? AgentContext { get; set; }
[Parameter]
public RenderFragment MessageTemplates { get; set; } = builder =>
{
builder.OpenComponent<DefaultMessageTemplate>(0);
builder.CloseComponent();
};
[Parameter]
public RenderFragment ContentTemplates { get; set; } = builder =>
{
builder.OpenComponent<TextTemplate>(0);
builder.CloseComponent();
builder.OpenComponent<FunctionCallTemplate>(1);
builder.CloseComponent();
builder.OpenComponent<FunctionResultTemplate>(2);
builder.CloseComponent();
builder.OpenComponent<ErrorTemplate>(3);
builder.CloseComponent();
builder.OpenComponent<DataContentTemplate>(4);
builder.CloseComponent();
};
public void Attach(RenderHandle renderHandle)
{
this._renderHandle = renderHandle;
this._renderContents = this.RenderContents;
}
public Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
if (this.AgentContext == null)
{
throw new InvalidOperationException("Messages component must be used within an AgentBoundary.");
}
if (this._context != null && this.AgentContext != this._context)
{
throw new InvalidOperationException("Messages component cannot change AgentBoundaryContext.");
}
this._context = this.AgentContext;
if (this._messageListContext == null)
{
this._messageListContext = new MessageListContext(this.AgentContext);
this.Render();
}
return Task.CompletedTask;
}
private void Render()
{
this._renderHandle.Render(this.RenderCore);
}
private void RenderCore(RenderTreeBuilder builder)
{
builder.OpenComponent<CascadingValue<MessageListContext>>(0);
builder.AddComponentParameter(1, "Value", this._messageListContext);
builder.AddComponentParameter(2, "IsFixed", true);
builder.AddComponentParameter(3, "ChildContent", this._renderContents);
builder.CloseComponent();
}
private void RenderContents(RenderTreeBuilder builder)
{
Debug.Assert(this._messageListContext != null);
this._messageListContext.BeginCollectingTemplates();
builder.AddContent(1, this.MessageTemplates);
builder.AddContent(2, this.ContentTemplates);
builder.OpenComponent<MessageList>(3);
builder.CloseComponent();
}
}
@@ -1,309 +0,0 @@
/* Copyright (c) Microsoft. All rights reserved. */
/* ==========================================================================
AG-UI Components - Shared Styles
These styles provide consistent appearance for AI components across demos.
========================================================================== */
/* ==========================================================================
Agent Input Component
========================================================================== */
.agent-input {
display: flex;
align-items: flex-end;
gap: 12px;
width: 100%;
}
.agent-input textarea {
flex: 1;
padding: 12px 16px;
border: 1px solid #d1d5db;
border-radius: 8px;
font-size: 14px;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;
resize: none;
min-height: 44px;
max-height: 200px;
line-height: 1.5;
outline: none;
transition: border-color 0.2s, box-shadow 0.2s;
}
.agent-input textarea:focus {
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.1);
}
.agent-input textarea::placeholder {
color: #9ca3af;
}
.agent-input textarea:disabled {
background-color: #f9fafb;
color: #9ca3af;
cursor: not-allowed;
}
.agent-input .send-button {
display: flex;
align-items: center;
justify-content: center;
width: 44px;
height: 44px;
border: none;
border-radius: 8px;
background-color: #2563eb;
color: white;
cursor: pointer;
transition: background-color 0.2s, opacity 0.2s;
flex-shrink: 0;
}
.agent-input .send-button:hover:not(:disabled) {
background-color: #1d4ed8;
}
.agent-input .send-button:disabled {
background-color: #d1d5db;
color: #9ca3af;
cursor: not-allowed;
}
.agent-input .send-button svg {
display: block;
}
/* ==========================================================================
Agent Suggestions Component
========================================================================== */
.agent-suggestions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: flex-start;
margin-bottom: 0.75rem;
}
.suggestion-button {
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 1rem;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
.suggestion-button:hover:not(:disabled) {
background: #f0f0f0;
border-color: #0078d4;
color: #0078d4;
}
.suggestion-button:disabled {
opacity: 0.5;
cursor: not-allowed;
}
/* ==========================================================================
Loading Indicator - Triple Dot Animation
========================================================================== */
.agent-loading-indicator {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
color: #6b7280;
font-size: 14px;
}
.agent-loading-dots {
display: flex;
gap: 4px;
align-items: center;
}
.agent-loading-dots span {
width: 6px;
height: 6px;
background-color: #2563eb;
border-radius: 50%;
animation: agent-dot-bounce 1.4s ease-in-out infinite both;
}
.agent-loading-dots span:nth-child(1) {
animation-delay: -0.32s;
}
.agent-loading-dots span:nth-child(2) {
animation-delay: -0.16s;
}
.agent-loading-dots span:nth-child(3) {
animation-delay: 0s;
}
@keyframes agent-dot-bounce {
0%, 80%, 100% {
transform: scale(0.6);
opacity: 0.5;
}
40% {
transform: scale(1);
opacity: 1;
}
}
/* Inline loading indicator for use within messages */
.agent-loading-inline {
display: inline-flex;
align-items: center;
gap: 4px;
padding: 4px 8px;
background-color: #f3f4f6;
border-radius: 12px;
}
.agent-loading-inline span {
width: 5px;
height: 5px;
background-color: #6b7280;
border-radius: 50%;
animation: agent-dot-bounce 1.4s ease-in-out infinite both;
}
.agent-loading-inline span:nth-child(1) {
animation-delay: -0.32s;
}
.agent-loading-inline span:nth-child(2) {
animation-delay: -0.16s;
}
.agent-loading-inline span:nth-child(3) {
animation-delay: 0s;
}
/* ==========================================================================
Processing State Overlay
========================================================================== */
.agent-input-container {
position: relative;
}
.agent-input-container.processing .agent-input textarea {
background-color: #f9fafb;
}
/* ==========================================================================
Message Styles
========================================================================== */
.message-list {
display: flex;
flex-direction: column;
gap: 16px;
}
.message {
display: flex;
flex-direction: column;
gap: 4px;
}
.message-user {
align-items: flex-end;
}
.message-assistant {
align-items: flex-start;
}
.message-content {
max-width: 80%;
padding: 12px 16px;
border-radius: 12px;
font-size: 14px;
line-height: 1.5;
}
.message-user .message-content {
background-color: #2563eb;
color: white;
border-bottom-right-radius: 4px;
}
.message-assistant .message-content {
background-color: #f3f4f6;
color: #1f2937;
border-bottom-left-radius: 4px;
}
/* ==========================================================================
Common Chat Layout Styles
========================================================================== */
.chat-layout {
display: flex;
flex-direction: column;
height: 100%;
max-width: 1200px;
margin: 0 auto;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e5e5;
}
.chat-title {
font-size: 1.5rem;
font-weight: 600;
color: #1a1a1a;
}
.new-chat-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
.new-chat-button:hover {
background: #f5f5f5;
border-color: #b3b3b3;
}
.button-icon {
font-size: 1.2rem;
line-height: 1;
}
.chat-content {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
}
.chat-input-container {
padding: 1.5rem 2rem 2rem;
border-top: 1px solid #e5e5e5;
background: white;
}
@@ -1,23 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<base href="/" />
<link rel="stylesheet" href="app.css" />
<link rel="stylesheet" href="AGUIDojoClient.styles.css" />
<link rel="icon" type="image/png" href="favicon.png" />
<HeadOutlet @rendermode="@renderMode" />
</head>
<body>
<Routes @rendermode="@renderMode" />
<script src="_framework/blazor.web.js"></script>
</body>
</html>
@code {
private readonly IComponentRenderMode renderMode = new InteractiveServerRenderMode(prerender: false);
}
@@ -1,72 +0,0 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@using Microsoft.AspNetCore.Components.AI
@using Microsoft.Agents.AI
@using Microsoft.Extensions.AI
@using Microsoft.Extensions.DependencyInjection
@using AGUIDojoClient.Services
@inject IServiceProvider ServiceProvider
@inject IBackgroundColorService BackgroundColorService
@implements IDisposable
<PageTitle>Agentic Chat</PageTitle>
<div class="chat-layout" style="@GetBackgroundStyle()">
<div class="chat-header">
<div class="chat-title">AGUI WebChat</div>
<button class="new-chat-button" @onclick="ResetConversationAsync">
<span class="button-icon">+</span> New chat
</button>
</div>
<AgentBoundary Agent="@agent">
<div class="chat-content">
<Messages />
<AgentLoadingIndicator />
</div>
<div class="chat-input-container">
<AgentSuggestions Suggestions="@suggestions" />
<AgentInput Placeholder="Type your message..." />
</div>
</AgentBoundary>
</div>
@code {
private AIAgent? agent;
private string? _backgroundColor;
private Suggestion[] suggestions = [
new Suggestion("Change background", new ChatMessage(ChatRole.User, "Change background to light blue")),
new Suggestion("Generate sonnet")
];
[Parameter]
public string ScenarioId { get; set; } = "agentic_chat";
protected override void OnInitialized()
{
agent = ServiceProvider.GetRequiredKeyedService<AIAgent>("agentic-chat");
BackgroundColorService.ColorChanged += OnColorChanged;
}
private string GetBackgroundStyle()
{
return _backgroundColor != null ? $"background-color: {_backgroundColor}" : "";
}
private async void OnColorChanged(object? sender, BackgroundColorChangedEventArgs e)
{
_backgroundColor = e.Color;
await InvokeAsync(StateHasChanged);
}
private void ResetConversationAsync()
{
// Reset would need to be implemented in AgentBoundary
StateHasChanged();
}
public void Dispose()
{
BackgroundColorService.ColorChanged -= OnColorChanged;
}
}
@@ -1,74 +0,0 @@
/* Copyright (c) Microsoft. All rights reserved. */
.chat-layout {
display: flex;
flex-direction: column;
height: 100%;
max-width: 1200px;
margin: 0 auto;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e5e5;
}
.chat-title {
font-size: 1.5rem;
font-weight: 600;
color: #1a1a1a;
}
.new-chat-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
.new-chat-button:hover {
background: #f5f5f5;
border-color: #b3b3b3;
}
.button-icon {
font-size: 1.2rem;
line-height: 1;
}
.chat-content {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
}
.chat-input-container {
padding: 1rem 2rem 1.5rem;
border-top: 1px solid #e5e5e5;
background: white;
}
.agentic-chat-demo {
height: 100%;
display: flex;
flex-direction: column;
}
.chat-container {
padding: 0 2rem 1.5rem;
border-top: 1px solid #e0e0e0;
}
@@ -1,111 +0,0 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@using Microsoft.AspNetCore.Components.AI
@using Microsoft.Agents.AI
@using Microsoft.Extensions.AI
@using Microsoft.Extensions.DependencyInjection
@using AGUIDojoClient.Components.Shared
@using System.Text.Json
@inject IServiceProvider ServiceProvider
<PageTitle>Agentic Generative UI</PageTitle>
<div class="chat-layout">
<div class="chat-header">
<div class="chat-title">Agentic Generative UI</div>
<button class="new-chat-button" @onclick="ResetConversation">
<span class="button-icon">+</span> New chat
</button>
</div>
<AgentBoundary Agent="@agent">
<AgentState TState="Plan"
CurrentState="@currentPlan"
OnSnapshot="@DeserializePlan"
OnDelta="@ApplyPlanDelta"
CurrentStateChanged="@OnPlanChanged">
<div class="chat-content">
<Messages>
<ContentTemplates>
<TaskProgressTemplate />
<TextTemplate />
<FunctionCallTemplate />
<FunctionResultTemplate />
<ErrorTemplate />
<DataContentTemplate />
</ContentTemplates>
</Messages>
<AgentLoadingIndicator />
</div>
<div class="chat-input-container">
<AgentSuggestions Suggestions="@suggestions" />
<AgentInput Placeholder="Ask me to plan something..." />
</div>
</AgentState>
</AgentBoundary>
</div>
@code {
private AIAgent? agent;
private Plan? currentPlan;
private Suggestion[] suggestions = [
new Suggestion("Simple plan", new ChatMessage(ChatRole.User, "Please build a plan to go to mars in 5 steps.")),
new Suggestion("Complex plan", new ChatMessage(ChatRole.User, "Please build a plan to make pizza in 10 steps."))
];
[Parameter]
public string ScenarioId { get; set; } = "agentic_generative_ui";
protected override void OnInitialized()
{
agent = ServiceProvider.GetRequiredKeyedService<AIAgent>("agentic-generative-ui");
}
private Plan? DeserializePlan(ReadOnlyMemory<byte> data)
{
try
{
return JsonSerializer.Deserialize<Plan>(data.Span);
}
catch
{
return null;
}
}
private Plan? ApplyPlanDelta(Plan? current, ReadOnlyMemory<byte> deltaData)
{
if (current is null)
{
return null;
}
try
{
var operations = JsonSerializer.Deserialize<List<JsonPatchOperation>>(deltaData.Span);
if (operations is not null)
{
PlanPatcher.Apply(current, operations);
}
}
catch
{
// Ignore deserialization errors
}
return current;
}
private void OnPlanChanged(Plan? plan)
{
currentPlan = plan;
StateHasChanged();
}
private void ResetConversation()
{
currentPlan = null;
StateHasChanged();
}
}
@@ -1,86 +0,0 @@
/* Copyright (c) Microsoft. All rights reserved. */
.chat-layout {
display: flex;
flex-direction: column;
height: 100%;
max-width: 1200px;
margin: 0 auto;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e5e5;
}
.chat-title {
font-size: 1.5rem;
font-weight: 600;
color: #1a1a1a;
}
.new-chat-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
.new-chat-button:hover {
background: #f5f5f5;
border-color: #b3b3b3;
}
.button-icon {
font-size: 1.2rem;
line-height: 1;
}
.chat-content {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
}
.chat-input-container {
padding: 1.5rem 2rem 2rem;
border-top: 1px solid #e5e5e5;
background: white;
}
::deep .agent-suggestions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: flex-start;
margin-bottom: 0.75rem;
}
::deep .suggestion-button {
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 1rem;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
::deep .suggestion-button:hover {
background: #f0f0f0;
border-color: #0078d4;
color: #0078d4;
}
@@ -1,29 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoClient.Components.Demos.AgenticGenerativeUI;
/// <summary>
/// Represents a JSON Patch operation (RFC 6902).
/// </summary>
public sealed class JsonPatchOperation
{
/// <summary>
/// The operation to perform (e.g., "replace", "add", "remove").
/// </summary>
[JsonPropertyName("op")]
public string Op { get; set; } = string.Empty;
/// <summary>
/// The JSON Pointer path to the target location.
/// </summary>
[JsonPropertyName("path")]
public string Path { get; set; } = string.Empty;
/// <summary>
/// The value for the operation (used with "replace", "add", "test").
/// </summary>
[JsonPropertyName("value")]
public object? Value { get; set; }
}
@@ -1,35 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoClient.Components.Demos.AgenticGenerativeUI;
/// <summary>
/// Represents a plan with multiple steps.
/// </summary>
public sealed class Plan
{
/// <summary>
/// The list of steps in the plan.
/// </summary>
[JsonPropertyName("steps")]
public List<Step> Steps { get; set; } = [];
/// <summary>
/// Gets the count of completed steps.
/// </summary>
[JsonIgnore]
public int CompletedCount => this.Steps.Count(s => s.IsCompleted);
/// <summary>
/// Gets the total number of steps.
/// </summary>
[JsonIgnore]
public int TotalCount => this.Steps.Count;
/// <summary>
/// Gets whether all steps are completed.
/// </summary>
[JsonIgnore]
public bool IsComplete => this.Steps.Count > 0 && this.Steps.All(s => s.IsCompleted);
}
@@ -1,73 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using System.Text.RegularExpressions;
namespace AGUIDojoClient.Components.Demos.AgenticGenerativeUI;
/// <summary>
/// Applies JSON Patch operations to a Plan object.
/// </summary>
public static partial class PlanPatcher
{
/// <summary>
/// Applies a list of JSON Patch operations to the given plan.
/// </summary>
/// <param name="plan">The plan to modify.</param>
/// <param name="operations">The patch operations to apply.</param>
public static void Apply(Plan plan, IEnumerable<JsonPatchOperation> operations)
{
foreach (var operation in operations)
{
ApplyOperation(plan, operation);
}
}
private static void ApplyOperation(Plan plan, JsonPatchOperation operation)
{
// Parse paths like "/steps/0/status" or "/steps/0/description"
var match = StepPathRegex().Match(operation.Path);
if (!match.Success)
{
return;
}
if (!int.TryParse(match.Groups["index"].Value, out var index))
{
return;
}
if (index < 0 || index >= plan.Steps.Count)
{
return;
}
var property = match.Groups["property"].Value;
var step = plan.Steps[index];
if (string.Equals(operation.Op, "replace", StringComparison.OrdinalIgnoreCase))
{
if (string.Equals(property, "status", StringComparison.OrdinalIgnoreCase))
{
step.Status = GetStringValue(operation.Value) ?? step.Status;
}
else if (string.Equals(property, "description", StringComparison.OrdinalIgnoreCase))
{
step.Description = GetStringValue(operation.Value) ?? step.Description;
}
}
}
private static string? GetStringValue(object? value)
{
return value switch
{
string s => s,
JsonElement { ValueKind: JsonValueKind.String } je => je.GetString(),
_ => value?.ToString()
};
}
[GeneratedRegex(@"^/steps/(?<index>\d+)/(?<property>\w+)$")]
private static partial Regex StepPathRegex();
}
@@ -1,29 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace AGUIDojoClient.Components.Demos.AgenticGenerativeUI;
/// <summary>
/// Represents a single step in a plan.
/// </summary>
public sealed class Step
{
/// <summary>
/// The description of the step.
/// </summary>
[JsonPropertyName("description")]
public string Description { get; set; } = string.Empty;
/// <summary>
/// The status of the step (pending or completed).
/// </summary>
[JsonPropertyName("status")]
public string Status { get; set; } = "pending";
/// <summary>
/// Gets whether this step is completed.
/// </summary>
[JsonIgnore]
public bool IsCompleted => string.Equals(this.Status, "completed", StringComparison.OrdinalIgnoreCase);
}
@@ -1,42 +0,0 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@if (Plan is not null && Plan.Steps.Count > 0)
{
<div class="task-progress-card @(Plan.IsComplete ? "completed" : "in-progress")">
<div class="task-header">
<h3>Task Progress</h3>
<span class="task-counter">@Plan.CompletedCount/@Plan.TotalCount Complete</span>
</div>
<div class="task-steps">
@for (int i = 0; i < Plan.Steps.Count; i++)
{
var step = Plan.Steps[i];
var isCurrentStep = !step.IsCompleted &&
(i == 0 || Plan.Steps[i - 1].IsCompleted);
<div class="task-step @(step.IsCompleted ? "step-completed" : isCurrentStep ? "step-current" : "step-pending")">
<span class="step-icon">
@if (step.IsCompleted)
{
<span class="check-icon">âś“</span>
}
else if (isCurrentStep)
{
<span class="current-icon">â—Ź</span>
}
else
{
<span class="pending-icon">â—‹</span>
}
</span>
<span class="step-description">@step.Description</span>
</div>
}
</div>
</div>
}
@code {
[CascadingParameter]
public Plan? Plan { get; set; }
}
@@ -1,139 +0,0 @@
/* Copyright (c) Microsoft. All rights reserved. */
.task-progress-card {
background: #ffffff;
border: 1px solid #e5e5e5;
border-radius: 12px;
padding: 16px;
margin: 8px 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}
.task-progress-card.completed {
background: linear-gradient(135deg, #f0fdf4 0%, #dcfce7 100%);
border-color: #86efac;
}
.task-progress-card.in-progress {
background: #ffffff;
}
.task-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 16px;
padding-bottom: 12px;
border-bottom: 1px solid #e5e5e5;
}
.task-header h3 {
margin: 0;
font-size: 1.1rem;
font-weight: 600;
color: #1a1a1a;
}
.task-counter {
font-size: 0.875rem;
color: #666666;
background: #f5f5f5;
padding: 4px 10px;
border-radius: 20px;
}
.task-steps {
display: flex;
flex-direction: column;
gap: 8px;
}
.task-step {
display: flex;
align-items: flex-start;
gap: 12px;
padding: 10px 12px;
border-radius: 8px;
transition: all 0.3s ease;
}
.task-step.step-completed {
background: rgba(34, 197, 94, 0.1);
border-left: 3px solid #22c55e;
}
.task-step.step-current {
background: rgba(0, 120, 212, 0.1);
border-left: 3px solid #0078d4;
animation: pulse-border 2s infinite;
}
.task-step.step-pending {
background: #fafafa;
border-left: 3px solid #d1d1d1;
opacity: 0.7;
}
@keyframes pulse-border {
0%, 100% {
border-left-color: #0078d4;
box-shadow: 0 0 0 0 rgba(0, 120, 212, 0.4);
}
50% {
border-left-color: #50a0e8;
box-shadow: 0 0 8px 0 rgba(0, 120, 212, 0.2);
}
}
.step-icon {
flex-shrink: 0;
width: 24px;
height: 24px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
}
.check-icon {
color: #22c55e;
font-weight: bold;
font-size: 14px;
}
.current-icon {
color: #0078d4;
font-size: 12px;
animation: pulse 1.5s infinite;
}
.pending-icon {
color: #999999;
font-size: 12px;
}
@keyframes pulse {
0%, 100% {
opacity: 1;
transform: scale(1);
}
50% {
opacity: 0.6;
transform: scale(1.2);
}
}
.step-description {
flex: 1;
font-size: 0.9rem;
line-height: 1.5;
color: #424242;
}
.step-completed .step-description {
color: #166534;
}
.step-pending .step-description {
color: #666666;
}
@@ -1,62 +0,0 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@using Microsoft.AspNetCore.Components.AI
@using Microsoft.Agents.AI
@using Microsoft.Extensions.AI
@using Microsoft.Extensions.DependencyInjection
@using AGUIDojoClient.Components.Shared
@inject IServiceProvider ServiceProvider
<PageTitle>Backend Tool Rendering</PageTitle>
<div class="chat-layout">
<div class="chat-header">
<div class="chat-title">Backend Tool Rendering</div>
<button class="new-chat-button" @onclick="ResetConversation">
<span class="button-icon">+</span> New chat
</button>
</div>
<AgentBoundary Agent="@agent">
<div class="chat-content">
<Messages>
<ContentTemplates>
<TextTemplate />
<WeatherCallTemplate />
<FunctionCallTemplate />
<FunctionResultTemplate />
<ErrorTemplate />
<DataContentTemplate />
</ContentTemplates>
</Messages>
<AgentLoadingIndicator />
</div>
<div class="chat-input-container">
<AgentSuggestions Suggestions="@suggestions" />
<AgentInput Placeholder="Ask about the weather..." />
</div>
</AgentBoundary>
</div>
@code {
private AIAgent? agent;
private Suggestion[] suggestions = [
new Suggestion("Weather in San Francisco", new ChatMessage(ChatRole.User, "What's the weather like in San Francisco?")),
new Suggestion("Weather in New York", new ChatMessage(ChatRole.User, "What's the weather like in New York?")),
new Suggestion("Weather in Tokyo", new ChatMessage(ChatRole.User, "What's the weather like in Tokyo?"))
];
[Parameter]
public string ScenarioId { get; set; } = "backend_tool_rendering";
protected override void OnInitialized()
{
agent = ServiceProvider.GetRequiredKeyedService<AIAgent>("backend-tool-rendering");
}
private void ResetConversation()
{
// Reset would need to be implemented - for now just trigger re-render
StateHasChanged();
}
}
@@ -1,86 +0,0 @@
/* Copyright (c) Microsoft. All rights reserved. */
.chat-layout {
display: flex;
flex-direction: column;
height: 100%;
max-width: 1200px;
margin: 0 auto;
}
.chat-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 1.5rem 2rem;
border-bottom: 1px solid #e5e5e5;
}
.chat-title {
font-size: 1.5rem;
font-weight: 600;
color: #1a1a1a;
}
.new-chat-button {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 4px;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
.new-chat-button:hover {
background: #f5f5f5;
border-color: #b3b3b3;
}
.button-icon {
font-size: 1.2rem;
line-height: 1;
}
.chat-content {
flex: 1;
overflow-y: auto;
padding: 2rem;
display: flex;
flex-direction: column;
}
.chat-input-container {
padding: 1.5rem 2rem 2rem;
border-top: 1px solid #e5e5e5;
background: white;
}
::deep .agent-suggestions {
display: flex;
flex-wrap: wrap;
gap: 0.5rem;
justify-content: flex-start;
margin-bottom: 0.75rem;
}
::deep .suggestion-button {
padding: 0.5rem 1rem;
background: white;
border: 1px solid #d1d1d1;
border-radius: 1rem;
cursor: pointer;
font-size: 0.875rem;
color: #424242;
transition: all 0.2s;
}
::deep .suggestion-button:hover {
background: #f0f0f0;
border-color: #0078d4;
color: #0078d4;
}
@@ -1,59 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Extensions.AI;
namespace Microsoft.AspNetCore.Components.AI;
/// <summary>
/// Template for rendering weather function call content with its result.
/// Uses InvocationContext to access both the call arguments and result.
/// </summary>
public class WeatherCallTemplate : ContentTemplateBase
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
// This component never renders anything by itself.
this.ChildContent = this.RenderWeatherCall;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
/// <summary>
/// Determines if this template should handle the given content.
/// Matches FunctionCallContent for the get_weather function.
/// </summary>
public override bool When(ContentContext context)
{
// Only match FunctionCallContent for the get_weather function
return context.Content is FunctionCallContent call &&
string.Equals(call.Name, "get_weather", StringComparison.OrdinalIgnoreCase);
}
private RenderFragment RenderWeatherCall(ContentContext content) => builder =>
{
if (content.Content is FunctionCallContent call)
{
// Get the invocation context which tracks both call and result
var invocation = this.Context.GetOrCreateInvocation(call);
// Provide the invocation context to child components
builder.OpenComponent<CascadingValue<InvocationContext>>(0);
builder.AddComponentParameter(1, "Value", invocation);
builder.AddComponentParameter(2, "IsFixed", true);
builder.AddComponentParameter(3, "ChildContent", (RenderFragment)(innerBuilder =>
{
// Render the WeatherCard component which uses InvocationContext
innerBuilder.OpenComponent<AGUIDojoClient.Components.Demos.BackendToolRendering.WeatherCard>(0);
innerBuilder.CloseComponent();
}));
builder.CloseComponent();
}
};
}
@@ -1,117 +0,0 @@
@* Copyright (c) Microsoft. All rights reserved. *@
@using AGUIDojoClient.Components.Shared
@using Microsoft.AspNetCore.Components.AI
@if (Weather is null)
{
<div class="weather-card weather-loading">
<div class="weather-header">
<div class="weather-location">
<h3>@Location</h3>
<p>Loading weather...</p>
</div>
<span class="weather-icon skeleton-icon"></span>
</div>
<div class="weather-main">
<div class="temperature">
<span class="temp-value skeleton-text skeleton-temp"></span>
<span class="temp-fahrenheit skeleton-text skeleton-temp-f"></span>
</div>
<div class="conditions skeleton-text skeleton-conditions"></div>
</div>
<div class="weather-details">
<div class="detail-item">
<p class="detail-label">Humidity</p>
<p class="detail-value skeleton-text skeleton-detail"></p>
</div>
<div class="detail-item">
<p class="detail-label">Wind</p>
<p class="detail-value skeleton-text skeleton-detail"></p>
</div>
<div class="detail-item">
<p class="detail-label">Feels Like</p>
<p class="detail-value skeleton-text skeleton-detail"></p>
</div>
</div>
</div>
}
else
{
<div class="weather-card @GetConditionClass()">
<div class="weather-header">
<div class="weather-location">
<h3>@Location</h3>
<p>Current Weather</p>
</div>
<span class="weather-icon">@Weather.ConditionIcon</span>
</div>
<div class="weather-main">
<div class="temperature">
<span class="temp-value">@Weather.Temperature° C</span>
<span class="temp-fahrenheit">/ @Weather.TemperatureFahrenheit.ToString("F1")° F</span>
</div>
<div class="conditions">@Weather.Conditions</div>
</div>
<div class="weather-details">
<div class="detail-item">
<p class="detail-label">Humidity</p>
<p class="detail-value">@(Weather.Humidity)%</p>
</div>
<div class="detail-item">
<p class="detail-label">Wind</p>
<p class="detail-value">@Weather.WindSpeed mph</p>
</div>
<div class="detail-item">
<p class="detail-label">Feels Like</p>
<p class="detail-value">@(Weather.FeelsLike)°</p>
</div>
</div>
</div>
}
@code {
[CascadingParameter]
public InvocationContext Invocation { get; set; } = default!;
private string Location => Invocation?.GetArgument<string>("location") ?? "Unknown Location";
private WeatherInfo? Weather => Invocation?.HasResult == true
? Invocation.GetResult<WeatherInfo>()
: null;
protected override void OnInitialized()
{
if (Invocation is not null && !Invocation.HasResult)
{
Invocation.ResultArrived += OnResultArrived;
}
}
private void OnResultArrived()
{
InvokeAsync(StateHasChanged);
}
private string GetConditionClass()
{
if (Weather is null)
{
return "condition-default";
}
return Weather.Conditions.ToLowerInvariant() switch
{
"sunny" or "clear" => "condition-sunny",
"cloudy" or "overcast" => "condition-cloudy",
"rainy" or "rain" => "condition-rainy",
"stormy" or "thunderstorm" => "condition-stormy",
"snowy" or "snow" => "condition-snowy",
"foggy" or "fog" => "condition-foggy",
_ => "condition-default"
};
}
}
@@ -1,162 +0,0 @@
.weather-card {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 16px;
padding: 20px;
color: white;
max-width: 320px;
box-shadow: 0 10px 40px rgba(102, 126, 234, 0.4);
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}
.weather-card.condition-sunny {
background: linear-gradient(135deg, #f6d365 0%, #fda085 100%);
}
.weather-card.condition-cloudy {
background: linear-gradient(135deg, #bdc3c7 0%, #2c3e50 100%);
}
.weather-card.condition-rainy {
background: linear-gradient(135deg, #4facfe 0%, #00f2fe 100%);
}
.weather-card.condition-stormy {
background: linear-gradient(135deg, #373b44 0%, #4286f4 100%);
}
.weather-card.condition-snowy {
background: linear-gradient(135deg, #e6dada 0%, #274046 100%);
}
.weather-card.condition-foggy {
background: linear-gradient(135deg, #606c88 0%, #3f4c6b 100%);
}
.weather-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
margin-bottom: 20px;
}
.weather-location h3 {
margin: 0;
font-size: 1.5rem;
font-weight: 600;
}
.weather-location p {
margin: 4px 0 0 0;
font-size: 0.85rem;
opacity: 0.8;
}
.weather-icon {
font-size: 3rem;
}
.weather-main {
margin-bottom: 20px;
}
.temperature {
display: flex;
align-items: baseline;
gap: 8px;
}
.temp-value {
font-size: 3rem;
font-weight: 300;
line-height: 1;
}
.temp-fahrenheit {
font-size: 1rem;
opacity: 0.7;
}
.conditions {
font-size: 1.1rem;
text-transform: capitalize;
margin-top: 8px;
opacity: 0.9;
}
.weather-details {
display: flex;
justify-content: space-between;
padding-top: 16px;
border-top: 1px solid rgba(255, 255, 255, 0.2);
}
.detail-item {
text-align: center;
}
.detail-label {
margin: 0;
font-size: 0.75rem;
opacity: 0.7;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.detail-value {
margin: 4px 0 0 0;
font-size: 1.1rem;
font-weight: 500;
}
/* Loading/Skeleton state */
.weather-loading {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
}
.skeleton-text {
background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 25%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.2) 75%);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s infinite;
border-radius: 4px;
display: inline-block;
}
.skeleton-icon {
width: 48px;
height: 48px;
background: linear-gradient(90deg, rgba(255, 255, 255, 0.2) 25%, rgba(255, 255, 255, 0.4) 50%, rgba(255, 255, 255, 0.2) 75%);
background-size: 200% 100%;
animation: skeleton-shimmer 1.5s infinite;
border-radius: 8px;
display: inline-block;
}
.skeleton-temp {
width: 80px;
height: 3rem;
}
.skeleton-temp-f {
width: 60px;
height: 1rem;
}
.skeleton-conditions {
width: 100px;
height: 1.1rem;
margin-top: 8px;
}
.skeleton-detail {
width: 40px;
height: 1.1rem;
}
@keyframes skeleton-shimmer {
0% {
background-position: 200% 0;
}
100% {
background-position: -200% 0;
}
}
@@ -1,69 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.AI;
using Microsoft.Extensions.AI;
namespace AGUIDojoClient.Components.Demos.HumanInTheLoop;
/// <summary>
/// Template for rendering create_plan function call content.
/// The PlanCard will subscribe to events to track confirm_plan and update_plan_step calls.
/// </summary>
public class CreatePlanCallTemplate : ContentTemplateBase
{
[CascadingParameter] internal MessageListContext Context { get; set; } = default!;
public override void Attach(RenderHandle renderHandle)
{
// This component never renders anything by itself.
this.ChildContent = this.RenderCreatePlanCall;
}
public override Task SetParametersAsync(ParameterView parameters)
{
parameters.SetParameterProperties(this);
this.Context.RegisterContentTemplate(this);
return Task.CompletedTask;
}
/// <summary>
/// Determines if this template should handle the given content.
/// Matches FunctionCallContent for the create_plan function.
/// </summary>
public override bool When(ContentContext context)
{
// Only match FunctionCallContent for the create_plan function
return context.Content is FunctionCallContent call &&
string.Equals(call.Name, "create_plan", StringComparison.OrdinalIgnoreCase);
}
private RenderFragment RenderCreatePlanCall(ContentContext content) => builder =>
{
if (content.Content is FunctionCallContent call)
{
// Get the invocation context which tracks both call and result
var invocation = this.Context.GetOrCreateInvocation(call);
// Provide both the invocation context and message list context to PlanCard
builder.OpenComponent<CascadingValue<InvocationContext>>(0);
builder.AddComponentParameter(1, "Value", invocation);
builder.AddComponentParameter(2, "IsFixed", true);
builder.AddComponentParameter(3, "ChildContent", (RenderFragment)(innerBuilder =>
{
// Also cascade the MessageListContext so PlanCard can track other tool calls
innerBuilder.OpenComponent<CascadingValue<MessageListContext>>(0);
innerBuilder.AddComponentParameter(1, "Value", this.Context);
innerBuilder.AddComponentParameter(2, "IsFixed", true);
innerBuilder.AddComponentParameter(3, "ChildContent", (RenderFragment)(cardBuilder =>
{
// Render the PlanCard component
cardBuilder.OpenComponent<PlanCard>(0);
cardBuilder.CloseComponent();
}));
innerBuilder.CloseComponent();
}));
builder.CloseComponent();
}
};
}

Some files were not shown because too many files have changed in this diff Show More