Compare commits

..
Author SHA1 Message Date
+44 172423aab7 .NET: Python: Merge main into feature-durabletask branch (#3385)
* Python: Add factory pattern to concurrent orchestration builder (#2738)

* Add factory pattern to concurrent orchestration builder

* Update readme

* Address AI comments

* Fix unit tests

* Fix import

* Prevent multiple calls to set participants or factories

* Add comments

* Mitigate warnings

* Fix mypy

* Address comments

* Address Copilot comments

* Fix tests

* Python: fix: GroupChat ManagerSelectionResponse JSON Schema for OpenAI Structured Outpu… (#2750)

* fix: ManagerSelectionResponse JSON Schema for OpenAI Structured Output Strict Mode

* refactor: install pre-commit then commit again

* Capture file IDs from code interpreter in streaming responses (#2741)

* .NET: [BREAKING] Prevent nulls in AIAgent property (#2719)

* prevent nulls in AIAgent property

* address feedback

* code ql sm04598 (#2723)

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

* .NET: Add Conversation State Sample (Step05) (#2697)

* Initial plan

* Add Agent_OpenAI_Step05_Conversation sample for conversation state management

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

* Update Program.cs comment to accurately describe the sample

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

* Update the code to use the ConversationClient more in line with the samples in OpenAI

* Apply suggestions from code review

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

* Changing sample to use ChatClientAgent and conversationId in GetNewThread

---------

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: Copilot <175728472+Copilot@users.noreply.github.com>

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.7 to 4.0.4.11 (#2777)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.4.11
  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 Azure.Identity from 1.17.0 to 1.17.1 (#2780)

---
updated-dependencies:
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.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 Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2778)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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>

* Python: added more complete parsing for mcp tool arguments (#2756)

* added more complete parsing for mcp tool arguments

* fixed mypy

* added nonlocal model counter, and some fixes

* fixes in naming logic

* extracted json parsing function, added parametrized test and checked coverage

* Python: Updated package versions (#2784)

* Updated package versions

* Small fix

* Bump actions/checkout from 5 to 6 (#2404)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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>

* .NET: adds support for labels in edges,  fixes rendering of labels in dot a… (#1507)

* adds support for labels in edges,  fixes rendering of labels in dot and mermaid, adds rendering of labels in edges

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Visualization/WorkflowVisualizer.cs

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

* escaping edge labels, adding tests for labels containing strange characters that would break the diagram and enabling the previous signature so the API has backwards compatibility.

* Unify label in EdgeData

* Edge API adjustments, removed useless "sanitizer"

* fixed test

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Python: Added custom args and thread object to ai_function kwargs (#2769)

* Added an example of using kwargs in ai_function

* Added thread object to ai_function kwargs

* Updated docs

* Small fix

* Added thread parameter filtering

* Fix WorkflowAgent to include thread convo history. Enable checkpointing. (#2774)

* Update OpenAIResponses.yaml to match AgentSchema (#2598)

1. Update `connection` child types --  `kind: ApiKey` to `kind: key` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/apikeyconnection/

2.  Update `outputSchema`'s `PropertySchema` to be `kind` instead of `type` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/propertyschema/

* Python: Remove warnings from workflow builder on not using factories (#2808)

* Revert concurrent

* Fix comments

* Python: Filter framework kwargs from MCP tool invocations (#2870)

* Filter framework kwargs from MCP tool invocations

* Fixes

* Python: Fix WorkflowAgent to emit yield_output as agent response (#2866)

* Fix WorkflowAgent to emit yield_output as agent response

* use raw_representation

* Raw representation handling

* Python: Use agent description in HandoffBuilder auto-generated tools (#2713) (#2714)

## Summary
Enhanced `HandoffBuilder._apply_auto_tools` to use the target agent's
description when creating handoff tools, providing more informative tool
descriptions for LLMs.

## Changes
- Modified `_apply_auto_tools` to extract `description` from
  `AgentExecutor._agent` when available
- Updated iteration to use `.items()` for more efficient dict traversal
- Handoff tools now use agent descriptions instead of generic placeholders

## Example
Before: "Handoff to the refund_agent agent."
After: "You handle refund requests. Ask for order details and process refunds."

## Testing
- All handoff tests pass (20/20)
- No breaking changes to existing API

Fixes #2713

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

* Python: [BREAKING] Observability updates (#2782)

* fixes Python: Add env_file_path parameter to setup_observability() similar to AzureOpenAIChatClient
Fixes #2186

* WIP on updates using configure_azure_monitor

* improved setup and clarity

* fixed root .env.example

* revert changes

* updated files

* updated sample

* updated zero code

* test fixes and fixed links

* fix devui

* removed planning docs

* added enable method and updated readme and samples

* clarified docstring

* add return annotation

* updated naming

* update capatilized version

* updated readme and some fixes

* updated decorator name inline with the rest

* feedback from comments addressed

* Python: Fix middleware terminate flag to exit function calling loop immediately (#2868)

* Fix middleware terminate flag to exit function calling loop immediately

* Eliminating duck typing

* Improve function exec result handling

* Fix race condition

* Fix mypy issues

* Python: Fix context duplication in handoff workflows when restoring from checkpoint (#2867)

* Fix context duplication in handoff workflows when restoring from checkpoint

* Address Copilot PR review

* .NET: Update to latest Azure.AI.*, OpenAI, and M.E.AI* (#2850)

* Update to latest Azure.AI.*, OpenAI, and M.E.AI*

Absorb breaking changes in Responses surface area

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Program.cs

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

* Using patch to remove the model is necessary, updated the response client to actually use the the ForAgent

---------

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

* Bump actions/download-artifact from 6 to 7 (#2862)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/cache from 4 to 5 (#2861)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/upload-artifact from 5 to 6 (#2860)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Python : Ollama Connector for Agent Framework (#1104)

* Initial Commit for Olama Connector

* Added Olama Sample

* Add Sample & Fixed Open Telemetry

* Fixed Spelling from Olama to Ollama

* remove"opentelemetry-semantic-conventions-ai ~=0.4.13" since its handled in a different pr

* Added Tool Calling

* Finalizing test cases

* Adjust samples to be more reliable

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Update python/packages/ollama/pyproject.toml

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

* Update python/packages/ollama/tests/test_ollama_chat_client.py

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

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Improved Docstrings & Sample

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>

* Integrate PR Feedback
- Divided Streaming and Non-Streaming into independent Methods
- Catch Ollama Validation Error
- Add OTEL Provider Name
- Checked Ollama Messages
- Add Usage Statistics

* Revert setting, so it can be none

* Validate Message formatting between AF and Ollama

* Catch Ollama Error and raise a ServiceResponse Error

* Fix mypy error

* remove .vscode comma

* Add Reasoning support & adjust to new structure

* Add Ollama Multimodality and Reasoning

* Add test cases for reasoning

* Add Tests for Error Handling in Ollama Client

* Update python/samples/getting_started/multimodal_input/ollama_chat_multimodal.py

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

* Integrated Copilot Feedback

* Implement first PR Feedback

* Adjust Readme files for examples

* Adjust argument passing via additional chat options

* Implemented PR Feedback

* Removing Ollama Package from Core and moving samples

* Fix Link & Adding Samples to Main Sample Readme

* Fixing Links in Readme

* Moved Multimodal and Chat Example

* Fixed Link in ChatClient to Ollama

* Fix AgentFramework Links in Ollama Project

* Fix observability breaking change

---------

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

* Skip failing IT (#2904)

* .NET: Cosmos DB UT Fast Skip (For Non-Configured Local envs) (#2906)

* Cosmos DB UT Fast Skip (Non-Configured Local envs) + Long running UT skip in pipeline when no CosmosDB changes happened

* Force a CosmosDB source code change to trigger the pipeline

* Address possible string boolean mismatch

* Add debug

* Enabling emulator always when running IT

* .NET: Add TTLs to durable agent sessions (#2679)

* .NET: Add TTLs to durable agent sessions

* Remove unnecessary async

* PR feedback: clarify UTC

* PR feedback: limit minimum signal delay to <= 5 minutes

* PR feedback: Fix TTL disablement

* Linter: use auto-property

* Fix build break from OpenAI SDK change

* Updated CHANGELOG.md

* PR feedback

* Reduce default TTL to 14 days to work around DTS bug

* Python:  Update Mem0Provider to use v2 search API `filters` parameter (#2766)

* short fix to move id parameters to filters object

* added tests

* small fix

* mem0 dependency update

* Updated package versions (#2913)

* .NET: Switch to new "Run" method name. (#2843)

* Switch to new "RunAgent" method name.

* Try to disable false positive naming warning.

* Add comment about disabled warnings.

* Rename `RunAgent` to just `Run`.

* Update CHANGELOG.

* Python: Switch to new "run" method name. (#2890)

* Switch to `run` method.

* Add support for deprecated `run_agent`.

* Fix entity method name.

* Fix method name and improve tests.

* Update comment.

* Update Python CHANGELOG.

* [BREAKING] Python: Add factory pattern to handoff orchestration builder (#2844)

* WIP: Factory pattern to handoff

* Add factory pattern to concurrent orchestration builder; Next: tests and sample verification

* Add tests and improve comments

* Fix mypy

* Simplify handoff_simple.py

* Simplify handoff_autonoumous.py and bug fix

* Update readme

* Address Copilot comments

* Python: Flow custom kwargs to agents via Workflow SharedState (#2894)

* Flow custom kwargs to agents via SharedState

* Address Copilot feedback

* Improve sample typing

* Fix test

* Fix Pydantic error when using Literal type for tool params (#2893)

* Updated Ollama package version (#2920)

* Python: Azure AI Agent with Bing Grounding Citations Sample (#2892)

* bing grounding sample with citations

* small fix

* fix

* .NET: Make DelegatingAIAgent abstract (#2797)

* Initial plan

* Make DelegatingAIAgent abstract

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Added additional arguments for Azure AI agent (#2922)

* Python: Correction of MCP image type conversion in  _mcp.py (#2901)

* Correction of MCP image type conversion in  _mcp.py

* Added a new overload to the init function of the DataContent() type of the Agent Framework, edited the test case to correctly test the usage of the data and uri fields while using DataContent()

* Fixed tests related to the changes of the DataContent type, added testing for both string and byte representations

* Pass kwargs into subworkflows (#2923)

* Python: Move ollama samples to samples getting started dir (#2921)

* Move ollama samples to samples getting started dir

* Address feedback

* Python: fix: correct BadRequestError when using Pydantic model in response_fo… (#1843)

* fix: correct BadRequestError when using Pydantic model in response_format

* Fix lint

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>

* .NET: [Breaking] Delete display name property (#2758)

* delete the AIAgent.DisplayName property

* use agent name as a first value for activity display name

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs

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

---------

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

* Python: cleanup and refactoring of chat clients (#2937)

* refactoring and unifying naming schemes of internal methods of chat clients

* set tool_choice to auto

* fix for mypy

* added note on naming and fix #2951

* fix responses

* fixes in azure ai agents client

* Python: Workflow add option to visualize internal executors (#2917)

* Workflow add option to visualize internal executors

* Address Copilot comments

* Python: Fixes Run ID and Thread ID casing to align with AG-UI Typescript SDK (#2948)

* added camelCase input to run id and thread id aligning with @ag-ui/core

* fixed per copilot suggestions

* Python: Add workflow cancellation sample (#2732)

* Add workflow cancellation sample

Add sample demonstrating how to cancel a running workflow using asyncio
tasks. Shows both cancellation mid-execution and normal completion paths.
Useful for implementing timeouts, graceful shutdown, or A2A executors.

* update docstring

* .NET: Update Anthropic package to version 12.0.0 (#2914)

* Initial plan

* Update Anthropic package to version 12.0.0

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>

---------

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

* Python: Add Azure Managed Redis Support with Credential Provider (#2887)

* azure redis support

* small fixes

* azure managed redis sample

* fixes

* Bump CommunityToolkit.Aspire.OllamaSharp from 13.0.0-beta.440 to 13.0.0 (#2856)

---
updated-dependencies:
- dependency-name: CommunityToolkit.Aspire.OllamaSharp
  dependency-version: 13.0.0
  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 AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.11 to 4.0.5 (#2853)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5
  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: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>

* Bump Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2854)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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: Fix WorkflowAgent event handling and kwargs forwarding (#2946)

* Fix kwargs propagation through workflow.as_agent()

* Fix WorkflowAgent to respect AgentExecutor output_response setting

* .NET: Use GrpcEntityRunner instead of TaskEntityDispatcher (#2759)

* Use GrpcEntityRunner instead of TaskEntityDispatcher

* Pin to Durable worker 1.11.0

* Set the invocation result

* Update all Durable packages

* Update changelog, rename dispatcher to encondedEntityRequest

* Python: Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG (#2968)

* Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG

* update lock

* Fix formatting

* Fix ChatKit typing

* Python: Introducing Foundry Local Chat Clients (#2915)

* redo foundry local chat client

* fix mypy and spelling

* better docstring, updated sample

* fixed tests and added tests

* small sample update

* Updated package versions (#2978)

* Python: Added GitHub MCP sample with PAT (#2967)

* added github mcp sample with PAT

* addressed copilot fixes

* env fix

* Python: Preserve reasoning blocks with OpenRouter (#2950)

* Preserve reasoning blocks with OpenRouter

* Put encrypted reasoning in TextReasoningContent

* Remove unneccessary change

* Fix docs

* Support streaming

* Fix handling None in TextReasoningContent.text

* Python: Added response.created and response.in_progress event process to OpenAIBaseResponseClient (#2975)

* added response.created and response.in_progress to include response.id

* better doc string

* added tests for the new streaming event types

* Python: Introducing support for Bedrock-hosted models (Anthropic, Cohere, etc.) (#2610)

* Pushing the bedrock related changes to the new branch after addressing the review comments

* 2524 Addressed the second round review comments

* 2524 Addressed few more minor comments on the PR

* resolving the merge conflict

* 2524 resolved the uv.lock conflicts

* 2524 addressed more comments

* 2524 removed the print statement to fix the checks failure

* 2524 resolved the CI failure issues

* 2524 fixing the CI breaks

* 2524 Addressed the review comment

* 2524 resolved conflict

---------

Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>

* .NET: [Durable Agents] Reliable streaming sample (#2942)

* .NET: [Durable Agents] Reliable streaming sample

* Add automated validation for new sample

* Address Copilot PR feedback

* Fix typo in README.md about agent definitions (#2634)

* Fix typo in README.md about agent definitions

* Update agent-samples/README.md

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

---------

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

* Python: latency improvements (#3014)

* latency improvements

* fixed mypy, added coding standards and instructions

* slight logic improvement

* Python: Updated package versions (#3024)

* Updated package versions

* Updated changelog

* Python: add powerfx safe mode (#3028)

* add powerfx safe mode

* improved docstring and aligned env_file loading

* ensured test uses reset

* .NET: [Breaking] Introduce RunCoreAsync/RunCoreStreamingAsync delegation pattern in AIAgent (#2749)

* Initial plan

* Refactor AIAgent: Make RunAsync and RunStreamingAsync non-abstract, add RunCoreAsync and RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix infinite recursion in test implementations

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Make RunAsync and RunStreamingAsync non-virtual as requested

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix DelegatingAIAgent subclasses to use RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix XML documentation references in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Restore <see cref> tags with proper qualified signatures in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Rollback unnecessary XML documentation changes in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Remove pragma and update crefs to RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix EntityAgentWrapper to call base.RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* fix compilation issues

* fix compilatio issue

* fix tests

* fix unit tests

* fix unit test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* add issue template and additional labeling (#3006)

* fix and extra int test (#3037)

* .NET: [BREAKING] Refactor ChatMessageStore methods to be similar to AIContextProvider and add filtering support (#2604)

* Refactor ChatMessageStore methods to be similar to AIContextProvider

* Fix file encoding

* Ensure that AIContextProvider messages area also persisted.

* Update formatting and seal context classes

* Improve formatting

* Remove optional messages from constructor and add unit test

* Add ChatMessageStore filtering via a decorator

* Update sample and cosmos message store to store AIContextProvider messages in right order. Fix unit tests.

* Update Workflowmessage store to use aicontext provider messages.

* Apply suggestions from code review

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

* Apply suggestions from code review

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Improve xml docs messaging

* Address code review comments.

* Also notify message store on failure

---------

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

* [BREAKING] Remove unused AgentThreadMetadata (#3067)

* Remove unused AgentThreadMetadata

* Update DurableTask Changelog

* Python: Fix AzureAIClient failure when conversation history contains assistant messages (#3076)

* Fix AzureAIClient failure when conversation history contains assistant messages

* Address PR review feedback: improve docstring and test assertions

* Remove redundant cast

* Fix: Update OTLP exporter protocol conditions (#3070)

* Python: Fix ExecutorInvokedEvent and ExecutorCompletedEvent observability data (#3090)

* Fix ExecutorInvokedEvent.data mutation bug

* Fix bug related to not yielding output type

* .NET: Seal ChatClientAgentThread (#2842)

* Initial plan

* Seal ChatClientAgentThread class

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Fix broken strands urls. (#3102)

* Fix broken strands urls.

* Fix typos

* .NET: Fix message ordering inconsistency when using AIContextProvider (#2659)

* Initial plan

* Fix message ordering inconsistency when using AIContextProvider

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Revert to original message ordering: Input, AIContextProvider, Response

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Reorder messages to ChatClient to match MessageStore order: Existing, Input, AIContextProvider

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Remove redundant test methods as existing tests already verify the behavior

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* fix: tool_choice parameter not being honored when passed to agent.run() (#3095)

* sharepoint sample fix (#3108)

* Bump versions to 1.0.0b260106 for a release. Update CHANGELOG.md (#3109)

* Bump Bedrock version to latest (#3110)

* Python: Fix MCP tool result serialization for list[TextContent] (#2523)

* Fix MCP tool result serialization for list[TextContent]

When MCP tools return results containing list[TextContent], they were
incorrectly serialized to object repr strings like:
'[<agent_framework._types.TextContent object at 0x...>]'

This fix properly extracts text content from list items by:
1. Checking if items have a 'text' attribute (TextContent)
2. Using model_dump() for items that support it
3. Falling back to str() for other types
4. Joining single items as plain text, multiple items as JSON array

Fixes #2509

* Address PR review feedback for MCP tool result serialization

- Extract serialize_content_result() to shared _utils.py
- Fix logic: use texts[0] instead of join for single item
- Add type annotation: texts: list[str] = []
- Return empty string for empty list instead of '[]'
- Move import json to file top level
- Add comprehensive unit tests for serialization

* Address PR review feedback: fix type checking and double serialization

- Add isinstance(item.text, str) check to ensure text attribute is a string
- Fix double-serialization issue by keeping model_dump results as dicts
  until final json.dumps (removes escaped JSON strings in arrays)
- Improve docstring with detailed return value documentation
- Add test for non-string text attribute handling
- Add tests for list type tool results in _events.py path

* Simplify PR: minimal changes to fix MCP tool result serialization

Addresses reviewer feedback about excessive refactoring:
- Reset _events.py to original structure
- Only add import and use serialize_content_result in one location
- All review comments addressed in serialize_content_result():
  - Added isinstance(item.text, str) check
  - Use model_dump(mode="json") to avoid double-serialization
  - Improved docstring with explicit return value documentation
  - Empty list returns "" instead of "[]"

* Refactor: Move MCP TextContent serialization to core prepare_function_call_results

Per reviewer feedback, moved the TextContent serialization logic from
ag-ui's serialize_content_result to the core package's
prepare_function_call_results function.

Changes:
- Added handling for objects with 'text' attribute (like MCP TextContent)
  in _prepare_function_call_results_as_dumpable
- Removed serialize_content_result from ag-ui/_utils.py
- Updated _events.py and _message_adapters.py to use
  prepare_function_call_results from core package
- Updated tests to match the core function's behavior

* Fix failing tests for prepare_function_call_results behavior

- test_tool_result_with_none: Update expected value to 'null' (JSON serialization of None)
- test_tool_result_with_model_dump_objects: Use Pydantic BaseModel instead of plain class

* Fix B903 linter error: Convert MockTextContent to dataclass

The ruff linter was reporting B903 (class could be dataclass or namedtuple)
for the MockTextContent test helper classes. This commit converts them to
dataclasses to satisfy the linter check.

* Python: Improve DevUI, add Context Inspector view as new tab under traces (#2742)

* Improve DevUI, add Context Inspector view as new tab under traces

* fix mypy errors

* fix: Handle stale MCP connections in DevUI executor

MCP tools can become stale when HTTP streaming responses end - the underlying
stdio streams close but `is_connected` remains True. This causes subsequent
requests to fail with `ClosedResourceError`.

Add `_ensure_mcp_connections()` to detect and reconnect stale MCP tools before
agent execution. This is a workaround for an upstream Agent Framework issue
where connection state isn't properly tracked.

Fixes MCP tools failing on second HTTP request in DevUI.

fixes  #1476 #1515 #2865

* fix #1572 report import dependency errors more clearly

* Ensure there is streaming toggle where users can select streaming vs non streaming mode in devui . Fixes .NET: [Python] DevUI tool call rendering in non-streaming mode?

* remove unused dead code

* improve ux - workflows with agents show a chat component in execution timelien, also ensure magentic final output shows correctly

* update ui build

* update devui to use instrumentation instead of tracing, other instrumentation and type/instance check fixes

* .NET: Seal factory contexts and add non JSO deserialize overloads (#3066)

* Seal factory contexts and add non JSO deserialize overloads

* Apply suggestions from code review

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

---------

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

* Enable blank issues in issue template configuration

Need to re-enable creating blank issues

* updated templates (#3106)

* updated templates

* enabled blank and fixed triage

* made language optional and moved to the bottom for features

* Python: Streaming sample for azurefunctions (#3057)

* Streaming sample for azurefunctions

* Fixed links and sample name

* Addressed feedback

* Addressed feedback

* Fixed integration tests

* Updated test

* Python: fix(azure-ai): Fix response_format handling for structured outputs (#3114)

* fix(azure-ai): read response_format from chat_options instead of run_options

* refactor: use explicit None checks for response_format

* Fix mypy error

* Mypy fix

* Python: Bump python version to 1.0.0b260107 for a release (#3128)

* Bump python version to 1.0.0b260107 for a release

* Update changelog

* Make A2AAgent public, so that it's concrete implementation methods can be used. (#3119)

* .NET: Map additional props <-> A2A metadata (#3137)

* map additional props from agent run options to a2a request metadata

* small touches

* add unit tests for new extension methods

* Sort using

* add unit test

* add additiona unit tests

* special case json element to avoid unnecessary serialization

* Python: Fix Anthropic streaming response bugs (#3141)

* test commit identity

* fix(anthropic): fix raw_representation and finish_reason in streaming

* lint fix

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.5 to 4.0.5.1 (#2994)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Bump Anthropic from 12.0.0 to 12.0.1 (#2993)

---
updated-dependencies:
- dependency-name: Anthropic
  dependency-version: 12.0.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* .NET: [Breaking] Prevent loss of input messages & streamed updates when resuming streaming (#2748)

* save input messages and stream updates to the continuation token to be able to use them in the last successful stream resumption call.

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* fix typo

* init continuation token from chat response

* remove unnecessary types for source generation

* remove check for continuation token passed at initial run

* remove check for continuation token pass at initial run

* centralize continuation token parsing

* update xml comments

* use readonly collection instead of enumerable

---------

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

* .NET: fix: Expose WorkflowErrorEvent as ErrorContent (#2762)

* fix: Expose WorkflowErrorEvent as ErrorContent

When hosted using .AsAgent(), Workflows were not exposing inner errors coming as Exceptions (through the WorkflowErrorEvent)

The fix is to convert their message to an ErrorContent on the way out, rather than rely on the default "empty update" to collect the raw event.

* feat: Add a way to show/suppress exception information

* Bump Microsoft.Agents.AI.Workflows from 1.0.0-preview.251125.1 to 1.0.0-preview.251219.1 (#2997)

---
updated-dependencies:
- dependency-name: Microsoft.Agents.AI.Workflows
  dependency-version: 1.0.0-preview.251219.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* .NET: Add Run overloads to expose ChatClientAgentRunOptions in IntelliSense (#3115)

* Initial plan

* Add ChatClientAgentExtensions for improved discoverability of ChatClientAgentRunOptions

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Address code review feedback - use collection expression syntax

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Apply suggestion from @westey-m

* Fix issues with Copilot implementation

* Add additional tests for structured output overloads.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Python: Add tool call/result content types and update connectors and samples (#2971)

* Add new AI content types and image tool support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Add Python content types for tool calls/results and image generation tool support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Address review feedback for tool content and samples

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Tighten image generation typing and sample tools list

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Align image generation output typing

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Handle MCP naming, image options mapping, and connector tool content

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Allow MCP call in function approval request

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Remove raw image_generation tool remapping

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Restore Anthropic tool_use to function calls unless code execution

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix lint issues for hosted file docstring and MCP parsing

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Import ChatResponse types in Anthropic client

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix Anthropics citation type imports and MCP typing for handoff/tools

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Skip lightning tests without agentlightning and fix function call import

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* fix lint on lab package

* rebuilt anthropic parsing

* redid anthropic parsing

* typo

* updated parsing and added missing docstrings

* fix tests

* mypy fixes

* second mypy fix

* add new class to other samples

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>

* Bump Google.GenAI from 0.6.0 to 0.9.0 (#2995)

---
updated-dependencies:
- dependency-name: Google.GenAI
  dependency-version: 0.9.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Bump js-yaml from 4.1.0 to 4.1.1 in /python/packages/devui/frontend (#3123)

Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

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

* Updated package versions (#3144)

* .NET: Bump Microsoft.Agents.AI.OpenAI and Microsoft.Extensions.AI.OpenAI (#2996)

* Bump Microsoft.Agents.AI.OpenAI and Microsoft.Extensions.AI.OpenAI

Bumps Microsoft.Agents.AI.OpenAI from 1.0.0-preview.251125.1 to 1.0.0-preview.251219.1
Bumps Microsoft.Extensions.AI.OpenAI from 10.1.0-preview.1.25608.1 to 10.1.1-preview.1.25612.2

---
updated-dependencies:
- dependency-name: Microsoft.Agents.AI.OpenAI
  dependency-version: 1.0.0-preview.251219.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.AI.OpenAI
  dependency-version: 10.1.1-preview.1.25612.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Agents.AI.OpenAI
  dependency-version: 1.0.0-preview.251219.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.AI.OpenAI
  dependency-version: 10.1.1-preview.1.25612.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Fixed samples

---------

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>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>

* Python: fix(ag-ui): Execute tools with approval_mode, fix shared state, code cleanup  (#3079)

* fix(ag-ui): execute tools after approval in human-in-the-loop flow

* Fix shared state bug

* Bug fix finalized

* Refactoring to clean up code

* Code cleanup

* More fixes

* More code cleanup

* Add version detection in __init__.py to ruff ignore list

* Track agent name with updates for workflow agent (#3146)

* Python: Fix AzureAIClient tool call bug for AG-UI use (#3148)

* Fiz AzureAIClient tool call bug

* Address copilot feedback

* Python: multiple bug fixes (#3150)

* fix Python: kwargs are not passed to _prepare_thread_and_messages in ChatAgent.run
Fixes #3118

* fix Python: [Bug]: model_id versus model_deployment_name is confusing in Azure AI Agents
Fixes #3147

* add types

* fixed type and docstring

* fix(anthropic): fix duplicate ToolCallStartEvent in streaming tool calls (#3051)

When processing `input_json_delta` events, the Anthropic client was
passing the tool name from the previous `tool_use` event. This caused
ag-ui's `_handle_function_call_content` to emit a `ToolCallStartEvent`
for every streaming chunk (since it triggers on `if content.name:`).

This fix changes the behavior to pass an empty string for `name` in
`input_json_delta` events, matching OpenAI's behavior where streaming
argument chunks have `name=""`. The initial `tool_use` event still
provides the tool name, so only one `ToolCallStartEvent` is emitted.

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

* .NET: [BREAKING] Change GetNewThread and DeserializeThread to async (#3152)

* Change GetNewThread and DeserializeThread plus ChatMessageStore and AIContextProvider Factories to async

* Merge fixes

* Fix Ollama model env var in documentation (#3156)

Signed-off-by: Dina Suehiro Jones <dina.s.jones@intel.com>

* Python: Add Pydantic request model and OpenAPI tags support to AG-UI FastAPI endpoint (#2522)

* feat(ag-ui): Add Pydantic request model and OpenAPI tags support

- Add AGUIRequest Pydantic model in _types.py with field descriptions
- Update add_agent_framework_fastapi_endpoint() to accept tags parameter
- Use AGUIRequest model for automatic validation and OpenAPI schema generation
- Export AGUIRequest and DEFAULT_TAGS in __init__.py
- Update test_endpoint.py to expect 422 for invalid requests
- Add tests for OpenAPI schema, default tags, custom tags, and validation

Benefits:
- Better API documentation with complete request schema in Swagger UI
- Automatic request validation with Pydantic
- Organized endpoints under 'AG-UI' tag instead of 'default'
- Improved developer experience and type safety

Fixes #<issue-number>

* test(ag-ui): Add test for internal error handling to achieve 100% coverage

- Add test_endpoint_internal_error_handling() to cover exception handling code
- Mock copy.deepcopy to simulate internal error during default_state processing
- Add type: ignore for FastAPI tags parameter (known pyright compatibility issue)
- Achieves 100% test coverage for _endpoint.py (previously missing lines 103-105)

* .NET: Improve resolving `AITool` from DI (#3175)

* remove localagenttoolregistry

* also give the factory method API

* Python: Fix MCPStreamableHTTPTool to use new streamable_http_client API (#3088)

* Fix MCPStreamableHTTPTool to use new streamable_http_client API with proper httpx client cleanup

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Update docstring to reflect new streamable_http_client API usage

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Refactor MCPStreamableHTTPTool to accept optional http_client parameter and delegate client creation to streamable_http_client

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Update mcp package minimum version to 1.24.0 for streamable_http_client API support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix critical bugs: apply headers/timeout/sse_read_timeout when creating httpx client, add version constraint <2, and properly manage client lifecycle

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Simplify implementation: remove headers/timeout/sse_read_timeout params, remove kwargs, remove close() override per feedback

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Add back **kwargs parameter for backward compatibility (accepted but not used)

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Remove unused httpx import from test file

Note: The uv.lock file needs to be updated with 'uv sync' to reflect the mcp version constraint change (>=1.24.0,<2)

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* cicd fixes

* udpated samples with headers examples

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>

* azureai direct a2a endpoint support (#3127)

* Python: [BREAKING]: removed display_name, renamed context_providers, middleware and AggregateContextProvider (#3139)

* removed display_name, renamed context_providers, middleware and AggregateContextProvider

* fixes

* fixed test

* testfix

* removed mistakenly put back test

* updated new test

* rename middlewares to middleware

* middleware fixes

* Python: MCP Improvements: improved connection loss behavior, pagination for loading and a param to control representation (#3154)

* pagination support (#2848) added a parse_tool_result param and connection loss (#2884)

* fix #3153

* improved connection handling

* improved logic

* Python: Add declarative workflow runtime (#2815)

* Further support for declarative python workflows

* Add tests. Clean up for typing and formatting

* Improvements and cleanup

* Typing cleanup. Improve docstrings

* Proper code in docstrings

* Fix malformed code-block directive in docstring

* Remove dead links

* PR feedback

* Address PR feedback

* Address PR feedback

* Remove sl

* Update devui frontend

* More cleanup

* Fix uv lock

* Skip Py 3.14 tests as powerfx doesn't support it

* Fix mypy error

* Fix for tool calls

* Removed stale docstring

* Fix lint

* Standardize on .NET namespaces. Revert DevUI changes (bring in later)

* Implement remaining items for Python declarative support to match dotnet

* point URL to agent, not to agentcard (#3176)

* Python: [BREAKING]: Introducing Options as TypedDict and Generic (#3140)

* WIP typeddict for options

* updated all clients and ChatAgents

* updated everything

* added ADR

* fix mypy

* proper typevar imports

* fixed import

* fixed other imports

* slight update in the sample

* updated from feedback

* fixes

* fixed missing covariants and test fixes

* fixed typing

* updated anthropic thinking config

* ruff fixes

* fixed int tests

* fix tests and mypy

* updated integration tests

* updated docstring and test fix

* improved options handling in obser

* mypy fix

* updated a host of integration tests

* fix tests

* bedrock fix

* [BREAKING] Python: Refactor orchestrations (#3023)

* Group chat refactoring Part 1; Next: HIL and handoff

* Add agent approval flow; next samples

* WIP: samples

* WIP: HIL samples

* Group chat HIL working; next: handoff

* Fix group chat tool approval sample

* WIP: refactor handoff; next handoff handling

* Handoff done; next handoff samples and concurrent and sequential

* Handoff samples, concurrent, and sequential done; next Magentic

* WIP: magentic; next test with samples + HIL

* Magentic Working; next fix all samples and tests

* Fix handoff samples; next tests

* WIP: fixing tests; some orchestration as agent samples are failing

* Group chat unit tests done

* Handoff  unit tests done

* Remove old orchestration_request_info and fix related tests

* Magentic unit tests done

* Fix samples

* Fix test

* Fix test 2

* mypy

* Address comments

* Update readme

* Address comments

* Address comments 2

* Replace display name

* Python: ADR for create/get agent API (#2618)

* ADR for create/get agent API

* Updated ADR with implementation options

* Small updates

* Updated decision outcome section

* Updated broken links

* Small updates

* Fixed merge conflicts

* Small fix

* Updated decision outcome section

* Small fixes

* Updated provider naming based on client SDK

* Add ignored parameter for CodeQL in workflow (#3204)

* Implement IReadOnlyList on InMemoryChatMessageStore (#3205)

* .NET: Make ChatMessageStore and AIContextProvider context props settable (#3196)

* Make ChatMessageStore and AIContextProvider context props setable

* Add validation to preserve non-null requirement of certain properties.

* Fix broken tests.

* Python: Add dependencies param to ag-ui FastAPI endpoint (#3191)

* Add dependencies param to ag-ui FastAPI endpoint

* Address Copilot feedback

* renamed all (#3207)

* Python: ADR for simplified get response (#3098)

* ADR for simplified get response

* updated some language, added agent option and code comparison

* small update in sample

* added workflows and expanded some points

* changed decision and number

* updated with stream=False default

* .NET: [Breaking] Rename`AgentRunResponse` and `AgentRunResponseUpdate` classes (#3197)

* rename AgentRunResponse and AgentRunResponseUpdate classes - part1

* rename varialbles, parameters, methods and tests

* rollback unnecessary changes

* .NET: [Breaking] Rename AgentRunResponseEvent and AgentRunUpdateEvent classes (#3214)

* rename AgentRunResponseEvent and AgentRunUpdateEvent classes

* rollback unnecessary changes

* Python: Create/Get Agent API for Azure V2 (#3059)

* Added get_agent method to Azure AI V2

* Small fixes

* Small fix

* Removed AzureAIAgentProvider

* Added create_agent method

* Small fixes

* Fixed code interpreter tool mapping

* Added agent provider for V2 client

* Updated response format handling

* Added provider example

* Fixed errors

* Update python/samples/getting_started/agents/azure_ai/README.md

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

* Small fix

* Updates from merge

* Resolved comments

* Resolved comments

---------

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

* Python: Add more specific exceptions to Workflow (#3188)

* Add more specifc workflow exceptions

* Fix tests

* AI comments

* Misc

* Python: Added AzureAI sample for downloading code interpreter generated files (#3189)

* added azure ai code interpreter file download sample

* copilot fix suggestions

* function name fixes + readme update

* small fix

* update package versions (#3223)

Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>

* Python: fix(core): correct FunctionResultContent ordering in WorkflowAgent.merge_updates (#3168)

* fix(core): simplify FunctionResultContent ordering in WorkflowAgent.merge_updates

* improve comment

* Fix name

* fix(workflows): rename WorkflowOutputEvent.source_executor_id to executor_id for API consistency (#3166)

* Python: fix(ag-ui): add MCP tool support for AG-UI approval flows (#3212)

* add MCP tool support for AG-UI approval flows

* use attribute in place of property

* Python: Properly configure structured outputs based on new options dict (#3213)

* Properly configure structured outputs based on new options dict

* Fix mypy

* .NET: Merge AgentRunOptions.AdditionalProperties into ChatOptions.AdditionalProperties (#3184)

* Merge AgentRunOptions.AdditionalProperties into ChatOptions.AdditionalProperties

* Fix namespace and typo.

* .NET: Update Google.GenAI to 0.11.0 and remove polyfill implementations (#3232)

* Initial plan

* Update Google.GenAI to 0.11.0 and remove polyfill files

Co-authored-by: rogerbarreto <19890735+rogerbarreto@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>

* .NET: [BREAKING] Renamed CreateAIAgent/GetAIAgent to AsAIAgent (#3222)

* Renamed chat client extension method

* Additional renaming

* Updated documentation

* Fixed tests

* Small fix

* Small fix

* Updated DurableAIAgent and fixed integration tests (#3241)

* Python: Create/Get Agent API for Azure V1 (#3192)

* Added provider implementation for Azure AI V1

* Small fixes

* Fixed OpenAPI example

* Fixed local MCP example

* Fixed hosted MCP example

* Fixed file search sample

* Small fixes

* Resolved comments

* Doc updates

* Bump azure-core from 1.37.0 to 1.38.0 in /python (#3209)

Bumps [azure-core](https://github.com/Azure/azure-sdk-for-python) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-python/releases)
- [Commits](https://github.com/Azure/azure-sdk-for-python/compare/azure-core_1.37.0...azure-core_1.38.0)

---
updated-dependencies:
- dependency-name: azure-core
  dependency-version: 1.38.0
  dependency-type: indirect
...

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

* Python: Create/Get Agent API for OpenAI Assistants (#3208)

* Added provider implementation

* Added example with response format

* Small improvements

* Python: (AG-UI) Support service-managed thread on AG-UI  (#3136)

* added service thread support

* set service_thread_id to only supplied_thread_id

* uses raw_representation to extract the conversation_id

* removed accidental edit

* updated test to use raw_representation

* resolves copilot review feedback

* revert back StubAgent, since not used

* removed relative module import

* removed hasattr check per PR feedback

* Create/Get Agent API - fixes and example improvements (#3246)

* .NET Purview Middleware: Improve Background Job Runner Injection (#3256)

* Clean up background job dependency injection

* Fix xml documentation grammar

* Python: [BREAKING] Renamed create_agent to as_agent (#3249)

* Renamed create_agent to as_agent

* Override for as_agent

* Added override

* Python: Update package version (#3258)

* package version 260116

* removed name tags

* Python: Fixed Azure chat client for asynchronous filtering (#3260)

* Fixed Azure chat client for asynchronous filtering

* Updated test

* Python: Fixed use_agent_middleware calling private _normalize_messages (#3264)

* Fix use_agent_middleware calling private _normalize_messages

* Fixed A2A and Copilot Studio agent

* Python: Added rai_config to Azure AI agent creation (#3265)

* Add kwargs to create_agent method

* Added test for kwargs

* Addressed comment

* Added doc string

* Python: Filter conversation_id when passing kwargs to agent as tool (#3266)

* Filter conversation_id when passing kwargs to agent as tool

* Small fix

* Update python/samples/getting_started/agents/azure_ai/README.md

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

* Update python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py

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

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

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

---------

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

* Bump actions/setup-dotnet from 5.0.1 to 5.1.0 (#3273)

Bumps [actions/setup-dotnet](https://github.com/actions/setup-dotnet) from 5.0.1 to 5.1.0.
- [Release notes](https://github.com/actions/setup-dotnet/releases)
- [Commits](https://github.com/actions/setup-dotnet/compare/v5.0.1...v5.1.0)

---
updated-dependencies:
- dependency-name: actions/setup-dotnet
  dependency-version: 5.1.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>

* Update ignored checks in merge-gatekeeper workflow

* Python: [BREAKING] Make response_format validation errors visible to users (#3274)

* Make response_format validation errors visible to users

* Small fix

* Addressed comments

* Python: fix(declarative): Fix MCP tool connection not passed from YAML to Azure AI agent creation API (#3248)

* fix(declarative): Fix MCP tool connection not passed from YAML

* Add samples to README

* Fix mypy

* Fix mypy again

* Address PR comments

* fix #3171, ensure proper form rendering for int (#3201)

* Bump uv from 0.9.25 to 0.9.26 in /python (#3288)

Bumps [uv](https://github.com/astral-sh/uv) from 0.9.25 to 0.9.26.
- [Release notes](https://github.com/astral-sh/uv/releases)
- [Changelog](https://github.com/astral-sh/uv/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/uv/compare/0.9.25...0.9.26)

---
updated-dependencies:
- dependency-name: uv
  dependency-version: 0.9.26
  dependency-type: direct:development
  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 ruff from 0.14.11 to 0.14.13 in /python (#3287)

Bumps [ruff](https://github.com/astral-sh/ruff) from 0.14.11 to 0.14.13.
- [Release notes](https://github.com/astral-sh/ruff/releases)
- [Changelog](https://github.com/astral-sh/ruff/blob/main/CHANGELOG.md)
- [Commits](https://github.com/astral-sh/ruff/compare/0.14.11...0.14.13)

---
updated-dependencies:
- dependency-name: ruff
  dependency-version: 0.14.13
  dependency-type: direct:development
  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 tar from 7.4.3 to 7.5.3 in /python/packages/devui/frontend (#3267)

Bumps [tar](https://github.com/isaacs/node-tar) from 7.4.3 to 7.5.3.
- [Release notes](https://github.com/isaacs/node-tar/releases)
- [Changelog](https://github.com/isaacs/node-tar/blob/main/CHANGELOG.md)
- [Commits](https://github.com/isaacs/node-tar/compare/v7.4.3...v7.5.3)

---
updated-dependencies:
- dependency-name: tar
  dependency-version: 7.5.3
  dependency-type: indirect
...

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

* .NET: Delete sync extension methods for agent (#3291)

* Delete sync extension methods for agent

* Fix comments and obsolete attribute

* Remove more sync methods.

* Fix naming and comments.

* Fix unit tests

* Python: Fix: Add system_instructions to ChatClient LLM span tracing (#3164)

* Fix: Add system_instructions to ChatClient LLM span tracing

- Add system_instructions parameter to _capture_messages() calls in
  _trace_get_response() and _trace_get_streaming_response()
- Extract instructions from chat_options in kwargs
- Add unit tests to verify system_instructions are captured correctly

When using ChatClient with ChatOptions.instructions, the OpenTelemetry
LLM span was missing system messages in gen_ai.input.messages and the
gen_ai.system_instructions attribute was not being set.

This fix aligns the ChatClient-level tracing with the Agent-level
tracing which already correctly passes system_instructions.

Fixes #3163

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Add edge case tests for system_instructions

- Add test for empty string instructions (should not set attribute)
- Add test for list-type instructions (verify multiple items captured)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* Simplify: use options.get('instructions') directly instead of kwargs.get('chat_options')

Addresses reviewer feedback:
- Removed unnecessary chat_options variable from kwargs
- Directly access instructions from the options parameter
- Updated tests to use dict syntax for options (TypedDict convention)

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>

* Improve PR number handling in workflow (#3302)

* Improve PR number handling in workflow

Refine PR number extraction and validation method.

* Update .github/workflows/python-test-coverage-report.yml

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

* Fix error message for invalid PR number

---------

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

* .NET: Update Microsoft.Extensions.AI.* packages to 10.2.0 (#3211)

* Initial plan

* Update Microsoft.Extensions.AI.* to 10.2.0 and fix timestamp behavior tests

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>

---------

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

* .NET: Pass AdditionalProperties from parent to child when exposing an agent as a FunctionTool (#3219)

* Pass AdditionalProperties from parent to child when exposing an agent as a FunctionTool

* Rename variable to improve readability.

* Apply suggestions from code review

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

---------

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

* Python: [Breaking] Simplified Content types to a single class with classmethod constructors. (#3252)

* ported Content to a new model

* fixed linting

* fixes

* fixed data format handling

* fix for 3.10 mypy

* fix

* fix int test

* .NET: Durable Agent samples and automated validation for non-Azure Functions (#3042)

* Durable Agent samples and automated validation for non-Azure Functions

* Update test projects

* fix file encoding

* Remove AgentThreadMetadata usage

* Absorb breaking change from #3152

* Absorb newer breaking changes (AgentRunResponse --> AgentResponse)

* Absorb more breaking changes (see #3222)

* Improve integration test reliability (isolated task hubs, etc.)

* Fix flakey streaming test

---------

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

* Python: fix(core): handle anyio cancel scope errors during MCP connection cleanup (#3277)

* fix(core): handle anyio cancel scope errors during MCP connection cleanup

* Address Copilot feedback

* Python: fix(ag-ui): properly handle json serialize with handoff workflows as agent (#3275)

* fix(ag-ui): properly handle json serialize with handoff workflows as agent

* Other improvements around handling non-serializable objects

* Bump tomli from 2.3.0 to 2.4.0 in /python (#3182)

Bumps [tomli](https://github.com/hukkin/tomli) from 2.3.0 to 2.4.0.
- [Changelog](https://github.com/hukkin/tomli/blob/master/CHANGELOG.md)
- [Commits](https://github.com/hukkin/tomli/compare/2.3.0...2.4.0)

---
updated-dependencies:
- dependency-name: tomli
  dependency-version: 2.4.0
  dependency-type: direct:development
  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: Fix DebuggerDisplay attribute to reference existing property (#3326)

* Initial plan

* Fix DebuggerDisplay attribute to use Name instead of DisplayName

Co-authored-by: rogerbarreto <19890735+rogerbarreto@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>

* .NET: Add sample to show multiple AIContextProvider usage (#3284)

* Add sample to show multiple AIContextProvider usage

* Update comment.

* Update messaging in README.

* Address PR comments.

---------

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

* .NET: Update Conversation Sample to use Conversation Id instead (#3180)

* Update Conversation Sample to use conversation Id instead

* Remove Run infix

* Remove the sync GetAIAgent from sample

* Python: Fix local MCP tools with `AzureAIProjectAgentProvider` (#3315)

* azureai v2 local mcp fix

* addressed copilot comments

* .NET: Improve readme for agents V2 (#3285)

* Improve readme for agents V2

* Architectural justification

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

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

---------

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

* Bump pyasn1 from 0.6.1 to 0.6.2 in /python (#3257)

Bumps [pyasn1](https://github.com/pyasn1/pyasn1) from 0.6.1 to 0.6.2.
- [Release notes](https://github.com/pyasn1/pyasn1/releases)
- [Changelog](https://github.com/pyasn1/pyasn1/blob/main/CHANGES.rst)
- [Commits](https://github.com/pyasn1/pyasn1/compare/v0.6.1...v0.6.2)

---
updated-dependencies:
- dependency-name: pyasn1
  dependency-version: 0.6.2
  dependency-type: indirect
...

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

* .NET: Fix DebuggerDisplay attribute in AIAgent.cs to reference existing properties (#2985)

* Initial plan

* Fix DebuggerDisplay attribute in AIAgent.cs to reference existing properties

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>

* Python: feat(anthropic): Add response_format support for structured outputs (#3301)

* fix(anthropic): Add response_format support for structured outputs

* only use from options

* use native way of response format

* ruff lint fix

* address comment; handle dict

* Updated package versions (#3335)

* Set min version of dependent azure-ai-projects to 2.0.0b3 (#3347)

* Adding feature collections ADR (#3332)

* .NET: [Breaking] Allow passing auth token credential to cosmosdb extensions (#3250)

* allow passing token credentials to cosmosdb extensions

* Update dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBWorkflowExtensions.cs

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

* Update dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBWorkflowExtensions.cs

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

* Update dotnet/src/Microsoft.Agents.AI.CosmosNoSql/CosmosDBChatExtensions.cs

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

---------

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

* fix: Subworkflows do not work well with HostAsAgent (#3240)

Subworkflows run into issues with Checkpointing and the Chat Protocol:

* The concurrency rework made subtle changes in behaviour that introduced a hang when using subworkflows with ChatProtocol and streaming execution.
* The ResetAsync() implementation in WorkflowHostExecutor was improperly resetting the joinContext - this was happening on restore checkpoint _after_ the join context was attached when
* Subworkflows cannot be used as the start node when hosted AsAgent due to inability to treat Catch-All as a Chat Protocol
* Subworkflow ownership issue when used in non-concurrent mode after finishing a run

Also fixes:
* When ChatMessages are output by executors that are not agents, there is no corresponding AgentResponseUpdate/AgentResponse event

Breaking Changes
* [BREAKING CHANGE] It is possible to provide the wrong RunId when resuming from CheckpointInfo (even though the data already exists on CheckpointInfo)

* Python: .NET: Executor source gen for workflow executor routing (#3131)

* Roslyn Source Generators for Workflow Executor Routing.

* Update dotnet/src/Microsoft.Agents.AI.Workflows.Generators/ExecutorRouteGenerator.cs

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

* WIP.

* All fixed up except dangling sends/yields attriutes, working on that next.

* Add protocol-only generation for SendsMessage/YieldsOutput attributes

* Ensuring collections that can change order are sorted to enable pipeline caching.

* Improvents per PR feedback.

---------

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

* .NET: Joslat fix sample issue (#3270)

* adds support for labels in edges,  fixes rendering of labels in dot and mermaid, adds rendering of labels in edges

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Visualization/WorkflowVisualizer.cs

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

* escaping edge labels, adding tests for labels containing strange characters that would break the diagram and enabling the previous signature so the API has backwards compatibility.

* Unify label in EdgeData

* Edge API adjustments, removed useless "sanitizer"

* fixed test

* Fix in Sample

* update

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* .NET: Improve unit test coverage for Microsoft.Agents.AI.OpenAI (#3349)

* Initial plan

* Add unit tests for Microsoft.Agents.AI.OpenAI to improve code coverage

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

* Address code review feedback: remove unused using directives

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

* Fix format issues: file encoding and remove unused using directives

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

* Fix redundant cast error by using named parameter

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

* Remove excessive inline comments per PR review feedback

---------

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

* Revert to main

* Python: Fix: Verify types during checkpoint deserialization to prevent marker spoofing (#3243)

* Initial plan

* Add validation for reserved keywords in checkpoint encoding/decoding

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Refactor to eliminate duplicate code in model protocol detection

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Fix pyright type narrowing issue for dataclass check

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Add comprehensive unit tests for checkpoint encoding

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Remove serialization-time reserved keyword validation to fix failing tests

The serialization-time validation was too aggressive and blocked legitimate use cases
where encoded data was being re-encoded. Security is now enforced only at deserialization
time by validating that classes marked with DATACLASS_MARKER are actual dataclasses and
classes marked with MODEL_MARKER actually support the model protocol.

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Apply ruff formatting to checkpoint encoding file

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Changes before error encountered

Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>

* Revert "Changes before error encountered"

This reverts commit f515b880dc.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>

* Python: Fix azurefunctions MCP tool invocation to use correct agent  (#3339)

* MCP tool fix for azurefunctions

* Moving logic to check for thread id

* Adding ReflectExecutors method to Workflow. (#3389)

* Fix merge conflicts

* Python: [BREAKING] simplify ag-ui run logic, fix mcp bugs, fix anthropic client issues in ag-ui (#3322)

* Refactor ag-ui to simplify flow

* Refactoring

* Fix backend tool

* Update tests

* Improvements

* Fix mypy

* Fixes

* Fix json serialize errors

* Python: fix(core): filter out internal args when passing kwargs to MCP tools (#3292)

* fix(core): filter conversation_id when passing kwargs to MCP tools

* Filter out options too

* Fix uv.lock conflict

* Python: Added tests for OpenAI content types + Unit test improvement (#3259)

* added tests for content types+ unit test improvement

* small fixes

* small fix

* Python: Prefer runtime `kwargs` for `conversation_id` in OpenAI Responses client (#3312)

* prefer kwargs conversation_id over options

* addressed comments

* Python: Azure AI mapping HostedImageGenerationTool to ImageGenTool (#3263)

* azureai image gen sample fix

* mypy fixes

* addressed comments + mapping updates

* image model fix

* content type fix

* Python: add(azure-ai): support reasoning config for AzureAIClient (#3403)

* add(azure-ai): support reasoning config for AzureAIClient

* Update sample

* Merge main

* improvements

* improve sample

* .NET: Allow overriding the ChatMessageStore to be used per agent run. (#3330)

* Allow overriding the ChatMessageStore to be used per agent run.

* Fix typos

* Fix Add and add TryAdd, Contains and Remove

* Update instructions to require automatically building and formatting (#3412)

* .NET: Rename ChatMessageStore to ChatHistoryProvider (#3375)

* Rename ChatMessageStore to ChatHistoryProvider

* Fix merge issue

* Fixed PR comments

* Fix tests after property rename

* Add unit tests and fix merge issues

* Fix encoding

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dina Suehiro Jones <dina.s.jones@intel.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
Co-authored-by: Kurt <65111699+q33566@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: Korolev Dmitry <deagle.gross@gmail.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@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: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Jose Luis Latorre Millas <joslat@gmail.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Richard Ortega <richardjortega@gmail.com>
Co-authored-by: 刘邦学AI <lbbniu@gmail.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Nico Möller <nkm-moeller@mail.de>
Co-authored-by: Chris Gillum <cgillum@microsoft.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Phillip Hoff <phillip.hoff@gmail.com>
Co-authored-by: Ege Ozan Özyedek <36128615+egeozanozyedek@users.noreply.github.com>
Co-authored-by: samueljohnsiby <66901393+samueljohnsiby@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
Co-authored-by: Hao Luo <338265+howlowck@users.noreply.github.com>
Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Jacob Viau <javia@microsoft.com>
Co-authored-by: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Co-authored-by: Sunil Dutta <dutta.2003@gmail.com>
Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>
Co-authored-by: Syrine Chelly <62653967+SyChell@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: takanori-terai <123897708+takanori-terai@users.noreply.github.com>
Co-authored-by: claude89757 <138977524+claude89757@users.noreply.github.com>
Co-authored-by: Gavin Aguiar <80794152+gavin-aguiar@users.noreply.github.com>
Co-authored-by: Sukeesh <vsukeeshbabu@gmail.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>
Co-authored-by: Ao Chen <chenao3220@gmail.com>
Co-authored-by: Dina Suehiro Jones <dina.s.jones@intel.com>
Co-authored-by: eoindoherty1 <eoindoherty@microsoft.com>
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Darren Cohen <39422044+dargilco@users.noreply.github.com>
Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: TaoChenOSU <12570346+TaoChenOSU@users.noreply.github.com>
Co-authored-by: Shyju Krishnankutty <connectshyju@gmail.com>
2026-01-23 10:31:54 -08:00
Laveesh RohraandGitHub ff839435a2 Fix imports for durabletask (#3345) 2026-01-21 14:38:37 -08:00
Laveesh RohraandGitHub e032133748 Python: Add integration tests for durabletask package (#3317)
* Add integration tests

* Fix flaky test

* Fix env viz

* Fix tests and address feedback
2026-01-21 14:31:24 -08:00
+37 cd77193742 Python: Merge main into feature-durabletask-python branch (#3261)
* Python: Add factory pattern to concurrent orchestration builder (#2738)

* Add factory pattern to concurrent orchestration builder

* Update readme

* Address AI comments

* Fix unit tests

* Fix import

* Prevent multiple calls to set participants or factories

* Add comments

* Mitigate warnings

* Fix mypy

* Address comments

* Address Copilot comments

* Fix tests

* Python: fix: GroupChat ManagerSelectionResponse JSON Schema for OpenAI Structured Outpu… (#2750)

* fix: ManagerSelectionResponse JSON Schema for OpenAI Structured Output Strict Mode

* refactor: install pre-commit then commit again

* Capture file IDs from code interpreter in streaming responses (#2741)

* .NET: [BREAKING] Prevent nulls in AIAgent property (#2719)

* prevent nulls in AIAgent property

* address feedback

* code ql sm04598 (#2723)

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

* .NET: Add Conversation State Sample (Step05) (#2697)

* Initial plan

* Add Agent_OpenAI_Step05_Conversation sample for conversation state management

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

* Update Program.cs comment to accurately describe the sample

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

* Update the code to use the ConversationClient more in line with the samples in OpenAI

* Apply suggestions from code review

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

* Changing sample to use ChatClientAgent and conversationId in GetNewThread

---------

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: Copilot <175728472+Copilot@users.noreply.github.com>

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.7 to 4.0.4.11 (#2777)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.4.11
  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 Azure.Identity from 1.17.0 to 1.17.1 (#2780)

---
updated-dependencies:
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.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 Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2778)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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>

* Python: added more complete parsing for mcp tool arguments (#2756)

* added more complete parsing for mcp tool arguments

* fixed mypy

* added nonlocal model counter, and some fixes

* fixes in naming logic

* extracted json parsing function, added parametrized test and checked coverage

* Python: Updated package versions (#2784)

* Updated package versions

* Small fix

* Bump actions/checkout from 5 to 6 (#2404)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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>

* .NET: adds support for labels in edges,  fixes rendering of labels in dot a… (#1507)

* adds support for labels in edges,  fixes rendering of labels in dot and mermaid, adds rendering of labels in edges

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Visualization/WorkflowVisualizer.cs

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

* escaping edge labels, adding tests for labels containing strange characters that would break the diagram and enabling the previous signature so the API has backwards compatibility.

* Unify label in EdgeData

* Edge API adjustments, removed useless "sanitizer"

* fixed test

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Python: Added custom args and thread object to ai_function kwargs (#2769)

* Added an example of using kwargs in ai_function

* Added thread object to ai_function kwargs

* Updated docs

* Small fix

* Added thread parameter filtering

* Fix WorkflowAgent to include thread convo history. Enable checkpointing. (#2774)

* Update OpenAIResponses.yaml to match AgentSchema (#2598)

1. Update `connection` child types --  `kind: ApiKey` to `kind: key` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/apikeyconnection/

2.  Update `outputSchema`'s `PropertySchema` to be `kind` instead of `type` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/propertyschema/

* Python: Remove warnings from workflow builder on not using factories (#2808)

* Revert concurrent

* Fix comments

* Python: Filter framework kwargs from MCP tool invocations (#2870)

* Filter framework kwargs from MCP tool invocations

* Fixes

* Python: Fix WorkflowAgent to emit yield_output as agent response (#2866)

* Fix WorkflowAgent to emit yield_output as agent response

* use raw_representation

* Raw representation handling

* Python: Use agent description in HandoffBuilder auto-generated tools (#2713) (#2714)

## Summary
Enhanced `HandoffBuilder._apply_auto_tools` to use the target agent's
description when creating handoff tools, providing more informative tool
descriptions for LLMs.

## Changes
- Modified `_apply_auto_tools` to extract `description` from
  `AgentExecutor._agent` when available
- Updated iteration to use `.items()` for more efficient dict traversal
- Handoff tools now use agent descriptions instead of generic placeholders

## Example
Before: "Handoff to the refund_agent agent."
After: "You handle refund requests. Ask for order details and process refunds."

## Testing
- All handoff tests pass (20/20)
- No breaking changes to existing API

Fixes #2713

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

* Python: [BREAKING] Observability updates (#2782)

* fixes Python: Add env_file_path parameter to setup_observability() similar to AzureOpenAIChatClient
Fixes #2186

* WIP on updates using configure_azure_monitor

* improved setup and clarity

* fixed root .env.example

* revert changes

* updated files

* updated sample

* updated zero code

* test fixes and fixed links

* fix devui

* removed planning docs

* added enable method and updated readme and samples

* clarified docstring

* add return annotation

* updated naming

* update capatilized version

* updated readme and some fixes

* updated decorator name inline with the rest

* feedback from comments addressed

* Python: Fix middleware terminate flag to exit function calling loop immediately (#2868)

* Fix middleware terminate flag to exit function calling loop immediately

* Eliminating duck typing

* Improve function exec result handling

* Fix race condition

* Fix mypy issues

* Python: Fix context duplication in handoff workflows when restoring from checkpoint (#2867)

* Fix context duplication in handoff workflows when restoring from checkpoint

* Address Copilot PR review

* .NET: Update to latest Azure.AI.*, OpenAI, and M.E.AI* (#2850)

* Update to latest Azure.AI.*, OpenAI, and M.E.AI*

Absorb breaking changes in Responses surface area

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Program.cs

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

* Using patch to remove the model is necessary, updated the response client to actually use the the ForAgent

---------

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

* Bump actions/download-artifact from 6 to 7 (#2862)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/cache from 4 to 5 (#2861)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/upload-artifact from 5 to 6 (#2860)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Python : Ollama Connector for Agent Framework (#1104)

* Initial Commit for Olama Connector

* Added Olama Sample

* Add Sample & Fixed Open Telemetry

* Fixed Spelling from Olama to Ollama

* remove"opentelemetry-semantic-conventions-ai ~=0.4.13" since its handled in a different pr

* Added Tool Calling

* Finalizing test cases

* Adjust samples to be more reliable

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Update python/packages/ollama/pyproject.toml

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

* Update python/packages/ollama/tests/test_ollama_chat_client.py

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

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Improved Docstrings & Sample

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>

* Integrate PR Feedback
- Divided Streaming and Non-Streaming into independent Methods
- Catch Ollama Validation Error
- Add OTEL Provider Name
- Checked Ollama Messages
- Add Usage Statistics

* Revert setting, so it can be none

* Validate Message formatting between AF and Ollama

* Catch Ollama Error and raise a ServiceResponse Error

* Fix mypy error

* remove .vscode comma

* Add Reasoning support & adjust to new structure

* Add Ollama Multimodality and Reasoning

* Add test cases for reasoning

* Add Tests for Error Handling in Ollama Client

* Update python/samples/getting_started/multimodal_input/ollama_chat_multimodal.py

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

* Integrated Copilot Feedback

* Implement first PR Feedback

* Adjust Readme files for examples

* Adjust argument passing via additional chat options

* Implemented PR Feedback

* Removing Ollama Package from Core and moving samples

* Fix Link & Adding Samples to Main Sample Readme

* Fixing Links in Readme

* Moved Multimodal and Chat Example

* Fixed Link in ChatClient to Ollama

* Fix AgentFramework Links in Ollama Project

* Fix observability breaking change

---------

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

* Skip failing IT (#2904)

* .NET: Cosmos DB UT Fast Skip (For Non-Configured Local envs) (#2906)

* Cosmos DB UT Fast Skip (Non-Configured Local envs) + Long running UT skip in pipeline when no CosmosDB changes happened

* Force a CosmosDB source code change to trigger the pipeline

* Address possible string boolean mismatch

* Add debug

* Enabling emulator always when running IT

* .NET: Add TTLs to durable agent sessions (#2679)

* .NET: Add TTLs to durable agent sessions

* Remove unnecessary async

* PR feedback: clarify UTC

* PR feedback: limit minimum signal delay to <= 5 minutes

* PR feedback: Fix TTL disablement

* Linter: use auto-property

* Fix build break from OpenAI SDK change

* Updated CHANGELOG.md

* PR feedback

* Reduce default TTL to 14 days to work around DTS bug

* Python:  Update Mem0Provider to use v2 search API `filters` parameter (#2766)

* short fix to move id parameters to filters object

* added tests

* small fix

* mem0 dependency update

* Updated package versions (#2913)

* .NET: Switch to new "Run" method name. (#2843)

* Switch to new "RunAgent" method name.

* Try to disable false positive naming warning.

* Add comment about disabled warnings.

* Rename `RunAgent` to just `Run`.

* Update CHANGELOG.

* Python: Switch to new "run" method name. (#2890)

* Switch to `run` method.

* Add support for deprecated `run_agent`.

* Fix entity method name.

* Fix method name and improve tests.

* Update comment.

* Update Python CHANGELOG.

* [BREAKING] Python: Add factory pattern to handoff orchestration builder (#2844)

* WIP: Factory pattern to handoff

* Add factory pattern to concurrent orchestration builder; Next: tests and sample verification

* Add tests and improve comments

* Fix mypy

* Simplify handoff_simple.py

* Simplify handoff_autonoumous.py and bug fix

* Update readme

* Address Copilot comments

* Python: Flow custom kwargs to agents via Workflow SharedState (#2894)

* Flow custom kwargs to agents via SharedState

* Address Copilot feedback

* Improve sample typing

* Fix test

* Fix Pydantic error when using Literal type for tool params (#2893)

* Updated Ollama package version (#2920)

* Python: Azure AI Agent with Bing Grounding Citations Sample (#2892)

* bing grounding sample with citations

* small fix

* fix

* .NET: Make DelegatingAIAgent abstract (#2797)

* Initial plan

* Make DelegatingAIAgent abstract

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Added additional arguments for Azure AI agent (#2922)

* Python: Correction of MCP image type conversion in  _mcp.py (#2901)

* Correction of MCP image type conversion in  _mcp.py

* Added a new overload to the init function of the DataContent() type of the Agent Framework, edited the test case to correctly test the usage of the data and uri fields while using DataContent()

* Fixed tests related to the changes of the DataContent type, added testing for both string and byte representations

* Pass kwargs into subworkflows (#2923)

* Python: Move ollama samples to samples getting started dir (#2921)

* Move ollama samples to samples getting started dir

* Address feedback

* Python: fix: correct BadRequestError when using Pydantic model in response_fo… (#1843)

* fix: correct BadRequestError when using Pydantic model in response_format

* Fix lint

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>

* .NET: [Breaking] Delete display name property (#2758)

* delete the AIAgent.DisplayName property

* use agent name as a first value for activity display name

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs

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

---------

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

* Python: cleanup and refactoring of chat clients (#2937)

* refactoring and unifying naming schemes of internal methods of chat clients

* set tool_choice to auto

* fix for mypy

* added note on naming and fix #2951

* fix responses

* fixes in azure ai agents client

* Python: Workflow add option to visualize internal executors (#2917)

* Workflow add option to visualize internal executors

* Address Copilot comments

* Python: Fixes Run ID and Thread ID casing to align with AG-UI Typescript SDK (#2948)

* added camelCase input to run id and thread id aligning with @ag-ui/core

* fixed per copilot suggestions

* Python: Add workflow cancellation sample (#2732)

* Add workflow cancellation sample

Add sample demonstrating how to cancel a running workflow using asyncio
tasks. Shows both cancellation mid-execution and normal completion paths.
Useful for implementing timeouts, graceful shutdown, or A2A executors.

* update docstring

* .NET: Update Anthropic package to version 12.0.0 (#2914)

* Initial plan

* Update Anthropic package to version 12.0.0

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>

---------

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

* Python: Add Azure Managed Redis Support with Credential Provider (#2887)

* azure redis support

* small fixes

* azure managed redis sample

* fixes

* Bump CommunityToolkit.Aspire.OllamaSharp from 13.0.0-beta.440 to 13.0.0 (#2856)

---
updated-dependencies:
- dependency-name: CommunityToolkit.Aspire.OllamaSharp
  dependency-version: 13.0.0
  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 AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.11 to 4.0.5 (#2853)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5
  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: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>

* Bump Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2854)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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: Fix WorkflowAgent event handling and kwargs forwarding (#2946)

* Fix kwargs propagation through workflow.as_agent()

* Fix WorkflowAgent to respect AgentExecutor output_response setting

* .NET: Use GrpcEntityRunner instead of TaskEntityDispatcher (#2759)

* Use GrpcEntityRunner instead of TaskEntityDispatcher

* Pin to Durable worker 1.11.0

* Set the invocation result

* Update all Durable packages

* Update changelog, rename dispatcher to encondedEntityRequest

* Python: Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG (#2968)

* Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG

* update lock

* Fix formatting

* Fix ChatKit typing

* Python: Introducing Foundry Local Chat Clients (#2915)

* redo foundry local chat client

* fix mypy and spelling

* better docstring, updated sample

* fixed tests and added tests

* small sample update

* Updated package versions (#2978)

* Python: Added GitHub MCP sample with PAT (#2967)

* added github mcp sample with PAT

* addressed copilot fixes

* env fix

* Python: Preserve reasoning blocks with OpenRouter (#2950)

* Preserve reasoning blocks with OpenRouter

* Put encrypted reasoning in TextReasoningContent

* Remove unneccessary change

* Fix docs

* Support streaming

* Fix handling None in TextReasoningContent.text

* Python: Added response.created and response.in_progress event process to OpenAIBaseResponseClient (#2975)

* added response.created and response.in_progress to include response.id

* better doc string

* added tests for the new streaming event types

* Python: Introducing support for Bedrock-hosted models (Anthropic, Cohere, etc.) (#2610)

* Pushing the bedrock related changes to the new branch after addressing the review comments

* 2524 Addressed the second round review comments

* 2524 Addressed few more minor comments on the PR

* resolving the merge conflict

* 2524 resolved the uv.lock conflicts

* 2524 addressed more comments

* 2524 removed the print statement to fix the checks failure

* 2524 resolved the CI failure issues

* 2524 fixing the CI breaks

* 2524 Addressed the review comment

* 2524 resolved conflict

---------

Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>

* .NET: [Durable Agents] Reliable streaming sample (#2942)

* .NET: [Durable Agents] Reliable streaming sample

* Add automated validation for new sample

* Address Copilot PR feedback

* Fix typo in README.md about agent definitions (#2634)

* Fix typo in README.md about agent definitions

* Update agent-samples/README.md

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

---------

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

* Python: latency improvements (#3014)

* latency improvements

* fixed mypy, added coding standards and instructions

* slight logic improvement

* Python: Updated package versions (#3024)

* Updated package versions

* Updated changelog

* Python: add powerfx safe mode (#3028)

* add powerfx safe mode

* improved docstring and aligned env_file loading

* ensured test uses reset

* .NET: [Breaking] Introduce RunCoreAsync/RunCoreStreamingAsync delegation pattern in AIAgent (#2749)

* Initial plan

* Refactor AIAgent: Make RunAsync and RunStreamingAsync non-abstract, add RunCoreAsync and RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix infinite recursion in test implementations

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Make RunAsync and RunStreamingAsync non-virtual as requested

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix DelegatingAIAgent subclasses to use RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix XML documentation references in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Restore <see cref> tags with proper qualified signatures in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Rollback unnecessary XML documentation changes in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Remove pragma and update crefs to RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix EntityAgentWrapper to call base.RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* fix compilation issues

* fix compilatio issue

* fix tests

* fix unit tests

* fix unit test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* add issue template and additional labeling (#3006)

* fix and extra int test (#3037)

* .NET: [BREAKING] Refactor ChatMessageStore methods to be similar to AIContextProvider and add filtering support (#2604)

* Refactor ChatMessageStore methods to be similar to AIContextProvider

* Fix file encoding

* Ensure that AIContextProvider messages area also persisted.

* Update formatting and seal context classes

* Improve formatting

* Remove optional messages from constructor and add unit test

* Add ChatMessageStore filtering via a decorator

* Update sample and cosmos message store to store AIContextProvider messages in right order. Fix unit tests.

* Update Workflowmessage store to use aicontext provider messages.

* Apply suggestions from code review

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

* Apply suggestions from code review

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Improve xml docs messaging

* Address code review comments.

* Also notify message store on failure

---------

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

* [BREAKING] Remove unused AgentThreadMetadata (#3067)

* Remove unused AgentThreadMetadata

* Update DurableTask Changelog

* Python: Fix AzureAIClient failure when conversation history contains assistant messages (#3076)

* Fix AzureAIClient failure when conversation history contains assistant messages

* Address PR review feedback: improve docstring and test assertions

* Remove redundant cast

* Fix: Update OTLP exporter protocol conditions (#3070)

* Python: Fix ExecutorInvokedEvent and ExecutorCompletedEvent observability data (#3090)

* Fix ExecutorInvokedEvent.data mutation bug

* Fix bug related to not yielding output type

* .NET: Seal ChatClientAgentThread (#2842)

* Initial plan

* Seal ChatClientAgentThread class

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Fix broken strands urls. (#3102)

* Fix broken strands urls.

* Fix typos

* .NET: Fix message ordering inconsistency when using AIContextProvider (#2659)

* Initial plan

* Fix message ordering inconsistency when using AIContextProvider

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Revert to original message ordering: Input, AIContextProvider, Response

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Reorder messages to ChatClient to match MessageStore order: Existing, Input, AIContextProvider

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Remove redundant test methods as existing tests already verify the behavior

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* fix: tool_choice parameter not being honored when passed to agent.run() (#3095)

* sharepoint sample fix (#3108)

* Bump versions to 1.0.0b260106 for a release. Update CHANGELOG.md (#3109)

* Bump Bedrock version to latest (#3110)

* Python: Fix MCP tool result serialization for list[TextContent] (#2523)

* Fix MCP tool result serialization for list[TextContent]

When MCP tools return results containing list[TextContent], they were
incorrectly serialized to object repr strings like:
'[<agent_framework._types.TextContent object at 0x...>]'

This fix properly extracts text content from list items by:
1. Checking if items have a 'text' attribute (TextContent)
2. Using model_dump() for items that support it
3. Falling back to str() for other types
4. Joining single items as plain text, multiple items as JSON array

Fixes #2509

* Address PR review feedback for MCP tool result serialization

- Extract serialize_content_result() to shared _utils.py
- Fix logic: use texts[0] instead of join for single item
- Add type annotation: texts: list[str] = []
- Return empty string for empty list instead of '[]'
- Move import json to file top level
- Add comprehensive unit tests for serialization

* Address PR review feedback: fix type checking and double serialization

- Add isinstance(item.text, str) check to ensure text attribute is a string
- Fix double-serialization issue by keeping model_dump results as dicts
  until final json.dumps (removes escaped JSON strings in arrays)
- Improve docstring with detailed return value documentation
- Add test for non-string text attribute handling
- Add tests for list type tool results in _events.py path

* Simplify PR: minimal changes to fix MCP tool result serialization

Addresses reviewer feedback about excessive refactoring:
- Reset _events.py to original structure
- Only add import and use serialize_content_result in one location
- All review comments addressed in serialize_content_result():
  - Added isinstance(item.text, str) check
  - Use model_dump(mode="json") to avoid double-serialization
  - Improved docstring with explicit return value documentation
  - Empty list returns "" instead of "[]"

* Refactor: Move MCP TextContent serialization to core prepare_function_call_results

Per reviewer feedback, moved the TextContent serialization logic from
ag-ui's serialize_content_result to the core package's
prepare_function_call_results function.

Changes:
- Added handling for objects with 'text' attribute (like MCP TextContent)
  in _prepare_function_call_results_as_dumpable
- Removed serialize_content_result from ag-ui/_utils.py
- Updated _events.py and _message_adapters.py to use
  prepare_function_call_results from core package
- Updated tests to match the core function's behavior

* Fix failing tests for prepare_function_call_results behavior

- test_tool_result_with_none: Update expected value to 'null' (JSON serialization of None)
- test_tool_result_with_model_dump_objects: Use Pydantic BaseModel instead of plain class

* Fix B903 linter error: Convert MockTextContent to dataclass

The ruff linter was reporting B903 (class could be dataclass or namedtuple)
for the MockTextContent test helper classes. This commit converts them to
dataclasses to satisfy the linter check.

* Python: Improve DevUI, add Context Inspector view as new tab under traces (#2742)

* Improve DevUI, add Context Inspector view as new tab under traces

* fix mypy errors

* fix: Handle stale MCP connections in DevUI executor

MCP tools can become stale when HTTP streaming responses end - the underlying
stdio streams close but `is_connected` remains True. This causes subsequent
requests to fail with `ClosedResourceError`.

Add `_ensure_mcp_connections()` to detect and reconnect stale MCP tools before
agent execution. This is a workaround for an upstream Agent Framework issue
where connection state isn't properly tracked.

Fixes MCP tools failing on second HTTP request in DevUI.

fixes  #1476 #1515 #2865

* fix #1572 report import dependency errors more clearly

* Ensure there is streaming toggle where users can select streaming vs non streaming mode in devui . Fixes .NET: [Python] DevUI tool call rendering in non-streaming mode?

* remove unused dead code

* improve ux - workflows with agents show a chat component in execution timelien, also ensure magentic final output shows correctly

* update ui build

* update devui to use instrumentation instead of tracing, other instrumentation and type/instance check fixes

* .NET: Seal factory contexts and add non JSO deserialize overloads (#3066)

* Seal factory contexts and add non JSO deserialize overloads

* Apply suggestions from code review

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

---------

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

* Enable blank issues in issue template configuration

Need to re-enable creating blank issues

* updated templates (#3106)

* updated templates

* enabled blank and fixed triage

* made language optional and moved to the bottom for features

* Python: Streaming sample for azurefunctions (#3057)

* Streaming sample for azurefunctions

* Fixed links and sample name

* Addressed feedback

* Addressed feedback

* Fixed integration tests

* Updated test

* Python: fix(azure-ai): Fix response_format handling for structured outputs (#3114)

* fix(azure-ai): read response_format from chat_options instead of run_options

* refactor: use explicit None checks for response_format

* Fix mypy error

* Mypy fix

* Python: Bump python version to 1.0.0b260107 for a release (#3128)

* Bump python version to 1.0.0b260107 for a release

* Update changelog

* Make A2AAgent public, so that it's concrete implementation methods can be used. (#3119)

* .NET: Map additional props <-> A2A metadata (#3137)

* map additional props from agent run options to a2a request metadata

* small touches

* add unit tests for new extension methods

* Sort using

* add unit test

* add additiona unit tests

* special case json element to avoid unnecessary serialization

* Python: Fix Anthropic streaming response bugs (#3141)

* test commit identity

* fix(anthropic): fix raw_representation and finish_reason in streaming

* lint fix

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.5 to 4.0.5.1 (#2994)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Bump Anthropic from 12.0.0 to 12.0.1 (#2993)

---
updated-dependencies:
- dependency-name: Anthropic
  dependency-version: 12.0.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* .NET: [Breaking] Prevent loss of input messages & streamed updates when resuming streaming (#2748)

* save input messages and stream updates to the continuation token to be able to use them in the last successful stream resumption call.

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* fix typo

* init continuation token from chat response

* remove unnecessary types for source generation

* remove check for continuation token passed at initial run

* remove check for continuation token pass at initial run

* centralize continuation token parsing

* update xml comments

* use readonly collection instead of enumerable

---------

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

* .NET: fix: Expose WorkflowErrorEvent as ErrorContent (#2762)

* fix: Expose WorkflowErrorEvent as ErrorContent

When hosted using .AsAgent(), Workflows were not exposing inner errors coming as Exceptions (through the WorkflowErrorEvent)

The fix is to convert their message to an ErrorContent on the way out, rather than rely on the default "empty update" to collect the raw event.

* feat: Add a way to show/suppress exception information

* Bump Microsoft.Agents.AI.Workflows from 1.0.0-preview.251125.1 to 1.0.0-preview.251219.1 (#2997)

---
updated-dependencies:
- dependency-name: Microsoft.Agents.AI.Workflows
  dependency-version: 1.0.0-preview.251219.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* .NET: Add Run overloads to expose ChatClientAgentRunOptions in IntelliSense (#3115)

* Initial plan

* Add ChatClientAgentExtensions for improved discoverability of ChatClientAgentRunOptions

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Address code review feedback - use collection expression syntax

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Apply suggestion from @westey-m

* Fix issues with Copilot implementation

* Add additional tests for structured output overloads.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Python: Add tool call/result content types and update connectors and samples (#2971)

* Add new AI content types and image tool support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Add Python content types for tool calls/results and image generation tool support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Address review feedback for tool content and samples

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Tighten image generation typing and sample tools list

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Align image generation output typing

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Handle MCP naming, image options mapping, and connector tool content

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Allow MCP call in function approval request

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Remove raw image_generation tool remapping

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Restore Anthropic tool_use to function calls unless code execution

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix lint issues for hosted file docstring and MCP parsing

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Import ChatResponse types in Anthropic client

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix Anthropics citation type imports and MCP typing for handoff/tools

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Skip lightning tests without agentlightning and fix function call import

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* fix lint on lab package

* rebuilt anthropic parsing

* redid anthropic parsing

* typo

* updated parsing and added missing docstrings

* fix tests

* mypy fixes

* second mypy fix

* add new class to other samples

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>

* Bump Google.GenAI from 0.6.0 to 0.9.0 (#2995)

---
updated-dependencies:
- dependency-name: Google.GenAI
  dependency-version: 0.9.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Bump js-yaml from 4.1.0 to 4.1.1 in /python/packages/devui/frontend (#3123)

Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

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

* Updated package versions (#3144)

* .NET: Bump Microsoft.Agents.AI.OpenAI and Microsoft.Extensions.AI.OpenAI (#2996)

* Bump Microsoft.Agents.AI.OpenAI and Microsoft.Extensions.AI.OpenAI

Bumps Microsoft.Agents.AI.OpenAI from 1.0.0-preview.251125.1 to 1.0.0-preview.251219.1
Bumps Microsoft.Extensions.AI.OpenAI from 10.1.0-preview.1.25608.1 to 10.1.1-preview.1.25612.2

---
updated-dependencies:
- dependency-name: Microsoft.Agents.AI.OpenAI
  dependency-version: 1.0.0-preview.251219.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.AI.OpenAI
  dependency-version: 10.1.1-preview.1.25612.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Agents.AI.OpenAI
  dependency-version: 1.0.0-preview.251219.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.AI.OpenAI
  dependency-version: 10.1.1-preview.1.25612.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Fixed samples

---------

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>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>

* Python: fix(ag-ui): Execute tools with approval_mode, fix shared state, code cleanup  (#3079)

* fix(ag-ui): execute tools after approval in human-in-the-loop flow

* Fix shared state bug

* Bug fix finalized

* Refactoring to clean up code

* Code cleanup

* More fixes

* More code cleanup

* Add version detection in __init__.py to ruff ignore list

* Track agent name with updates for workflow agent (#3146)

* Python: Fix AzureAIClient tool call bug for AG-UI use (#3148)

* Fiz AzureAIClient tool call bug

* Address copilot feedback

* Python: multiple bug fixes (#3150)

* fix Python: kwargs are not passed to _prepare_thread_and_messages in ChatAgent.run
Fixes #3118

* fix Python: [Bug]: model_id versus model_deployment_name is confusing in Azure AI Agents
Fixes #3147

* add types

* fixed type and docstring

* fix(anthropic): fix duplicate ToolCallStartEvent in streaming tool calls (#3051)

When processing `input_json_delta` events, the Anthropic client was
passing the tool name from the previous `tool_use` event. This caused
ag-ui's `_handle_function_call_content` to emit a `ToolCallStartEvent`
for every streaming chunk (since it triggers on `if content.name:`).

This fix changes the behavior to pass an empty string for `name` in
`input_json_delta` events, matching OpenAI's behavior where streaming
argument chunks have `name=""`. The initial `tool_use` event still
provides the tool name, so only one `ToolCallStartEvent` is emitted.

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

* .NET: [BREAKING] Change GetNewThread and DeserializeThread to async (#3152)

* Change GetNewThread and DeserializeThread plus ChatMessageStore and AIContextProvider Factories to async

* Merge fixes

* Fix Ollama model env var in documentation (#3156)

Signed-off-by: Dina Suehiro Jones <dina.s.jones@intel.com>

* Python: Add Pydantic request model and OpenAPI tags support to AG-UI FastAPI endpoint (#2522)

* feat(ag-ui): Add Pydantic request model and OpenAPI tags support

- Add AGUIRequest Pydantic model in _types.py with field descriptions
- Update add_agent_framework_fastapi_endpoint() to accept tags parameter
- Use AGUIRequest model for automatic validation and OpenAPI schema generation
- Export AGUIRequest and DEFAULT_TAGS in __init__.py
- Update test_endpoint.py to expect 422 for invalid requests
- Add tests for OpenAPI schema, default tags, custom tags, and validation

Benefits:
- Better API documentation with complete request schema in Swagger UI
- Automatic request validation with Pydantic
- Organized endpoints under 'AG-UI' tag instead of 'default'
- Improved developer experience and type safety

Fixes #<issue-number>

* test(ag-ui): Add test for internal error handling to achieve 100% coverage

- Add test_endpoint_internal_error_handling() to cover exception handling code
- Mock copy.deepcopy to simulate internal error during default_state processing
- Add type: ignore for FastAPI tags parameter (known pyright compatibility issue)
- Achieves 100% test coverage for _endpoint.py (previously missing lines 103-105)

* .NET: Improve resolving `AITool` from DI (#3175)

* remove localagenttoolregistry

* also give the factory method API

* Python: Fix MCPStreamableHTTPTool to use new streamable_http_client API (#3088)

* Fix MCPStreamableHTTPTool to use new streamable_http_client API with proper httpx client cleanup

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Update docstring to reflect new streamable_http_client API usage

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Refactor MCPStreamableHTTPTool to accept optional http_client parameter and delegate client creation to streamable_http_client

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Update mcp package minimum version to 1.24.0 for streamable_http_client API support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix critical bugs: apply headers/timeout/sse_read_timeout when creating httpx client, add version constraint <2, and properly manage client lifecycle

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Simplify implementation: remove headers/timeout/sse_read_timeout params, remove kwargs, remove close() override per feedback

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Add back **kwargs parameter for backward compatibility (accepted but not used)

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Remove unused httpx import from test file

Note: The uv.lock file needs to be updated with 'uv sync' to reflect the mcp version constraint change (>=1.24.0,<2)

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* cicd fixes

* udpated samples with headers examples

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>

* azureai direct a2a endpoint support (#3127)

* Python: [BREAKING]: removed display_name, renamed context_providers, middleware and AggregateContextProvider (#3139)

* removed display_name, renamed context_providers, middleware and AggregateContextProvider

* fixes

* fixed test

* testfix

* removed mistakenly put back test

* updated new test

* rename middlewares to middleware

* middleware fixes

* Python: MCP Improvements: improved connection loss behavior, pagination for loading and a param to control representation (#3154)

* pagination support (#2848) added a parse_tool_result param and connection loss (#2884)

* fix #3153

* improved connection handling

* improved logic

* Python: Add declarative workflow runtime (#2815)

* Further support for declarative python workflows

* Add tests. Clean up for typing and formatting

* Improvements and cleanup

* Typing cleanup. Improve docstrings

* Proper code in docstrings

* Fix malformed code-block directive in docstring

* Remove dead links

* PR feedback

* Address PR feedback

* Address PR feedback

* Remove sl

* Update devui frontend

* More cleanup

* Fix uv lock

* Skip Py 3.14 tests as powerfx doesn't support it

* Fix mypy error

* Fix for tool calls

* Removed stale docstring

* Fix lint

* Standardize on .NET namespaces. Revert DevUI changes (bring in later)

* Implement remaining items for Python declarative support to match dotnet

* point URL to agent, not to agentcard (#3176)

* Python: [BREAKING]: Introducing Options as TypedDict and Generic (#3140)

* WIP typeddict for options

* updated all clients and ChatAgents

* updated everything

* added ADR

* fix mypy

* proper typevar imports

* fixed import

* fixed other imports

* slight update in the sample

* updated from feedback

* fixes

* fixed missing covariants and test fixes

* fixed typing

* updated anthropic thinking config

* ruff fixes

* fixed int tests

* fix tests and mypy

* updated integration tests

* updated docstring and test fix

* improved options handling in obser

* mypy fix

* updated a host of integration tests

* fix tests

* bedrock fix

* [BREAKING] Python: Refactor orchestrations (#3023)

* Group chat refactoring Part 1; Next: HIL and handoff

* Add agent approval flow; next samples

* WIP: samples

* WIP: HIL samples

* Group chat HIL working; next: handoff

* Fix group chat tool approval sample

* WIP: refactor handoff; next handoff handling

* Handoff done; next handoff samples and concurrent and sequential

* Handoff samples, concurrent, and sequential done; next Magentic

* WIP: magentic; next test with samples + HIL

* Magentic Working; next fix all samples and tests

* Fix handoff samples; next tests

* WIP: fixing tests; some orchestration as agent samples are failing

* Group chat unit tests done

* Handoff  unit tests done

* Remove old orchestration_request_info and fix related tests

* Magentic unit tests done

* Fix samples

* Fix test

* Fix test 2

* mypy

* Address comments

* Update readme

* Address comments

* Address comments 2

* Replace display name

* Python: ADR for create/get agent API (#2618)

* ADR for create/get agent API

* Updated ADR with implementation options

* Small updates

* Updated decision outcome section

* Updated broken links

* Small updates

* Fixed merge conflicts

* Small fix

* Updated decision outcome section

* Small fixes

* Updated provider naming based on client SDK

* Add ignored parameter for CodeQL in workflow (#3204)

* Implement IReadOnlyList on InMemoryChatMessageStore (#3205)

* .NET: Make ChatMessageStore and AIContextProvider context props settable (#3196)

* Make ChatMessageStore and AIContextProvider context props setable

* Add validation to preserve non-null requirement of certain properties.

* Fix broken tests.

* Python: Add dependencies param to ag-ui FastAPI endpoint (#3191)

* Add dependencies param to ag-ui FastAPI endpoint

* Address Copilot feedback

* renamed all (#3207)

* Python: ADR for simplified get response (#3098)

* ADR for simplified get response

* updated some language, added agent option and code comparison

* small update in sample

* added workflows and expanded some points

* changed decision and number

* updated with stream=False default

* .NET: [Breaking] Rename`AgentRunResponse` and `AgentRunResponseUpdate` classes (#3197)

* rename AgentRunResponse and AgentRunResponseUpdate classes - part1

* rename varialbles, parameters, methods and tests

* rollback unnecessary changes

* .NET: [Breaking] Rename AgentRunResponseEvent and AgentRunUpdateEvent classes (#3214)

* rename AgentRunResponseEvent and AgentRunUpdateEvent classes

* rollback unnecessary changes

* Python: Create/Get Agent API for Azure V2 (#3059)

* Added get_agent method to Azure AI V2

* Small fixes

* Small fix

* Removed AzureAIAgentProvider

* Added create_agent method

* Small fixes

* Fixed code interpreter tool mapping

* Added agent provider for V2 client

* Updated response format handling

* Added provider example

* Fixed errors

* Update python/samples/getting_started/agents/azure_ai/README.md

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

* Small fix

* Updates from merge

* Resolved comments

* Resolved comments

---------

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

* Python: Add more specific exceptions to Workflow (#3188)

* Add more specifc workflow exceptions

* Fix tests

* AI comments

* Misc

* Python: Added AzureAI sample for downloading code interpreter generated files (#3189)

* added azure ai code interpreter file download sample

* copilot fix suggestions

* function name fixes + readme update

* small fix

* update package versions (#3223)

Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>

* Python: fix(core): correct FunctionResultContent ordering in WorkflowAgent.merge_updates (#3168)

* fix(core): simplify FunctionResultContent ordering in WorkflowAgent.merge_updates

* improve comment

* Fix name

* fix(workflows): rename WorkflowOutputEvent.source_executor_id to executor_id for API consistency (#3166)

* Python: fix(ag-ui): add MCP tool support for AG-UI approval flows (#3212)

* add MCP tool support for AG-UI approval flows

* use attribute in place of property

* Python: Properly configure structured outputs based on new options dict (#3213)

* Properly configure structured outputs based on new options dict

* Fix mypy

* .NET: Merge AgentRunOptions.AdditionalProperties into ChatOptions.AdditionalProperties (#3184)

* Merge AgentRunOptions.AdditionalProperties into ChatOptions.AdditionalProperties

* Fix namespace and typo.

* .NET: Update Google.GenAI to 0.11.0 and remove polyfill implementations (#3232)

* Initial plan

* Update Google.GenAI to 0.11.0 and remove polyfill files

Co-authored-by: rogerbarreto <19890735+rogerbarreto@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>

* .NET: [BREAKING] Renamed CreateAIAgent/GetAIAgent to AsAIAgent (#3222)

* Renamed chat client extension method

* Additional renaming

* Updated documentation

* Fixed tests

* Small fix

* Small fix

* Updated DurableAIAgent and fixed integration tests (#3241)

* Python: Create/Get Agent API for Azure V1 (#3192)

* Added provider implementation for Azure AI V1

* Small fixes

* Fixed OpenAPI example

* Fixed local MCP example

* Fixed hosted MCP example

* Fixed file search sample

* Small fixes

* Resolved comments

* Doc updates

* Bump azure-core from 1.37.0 to 1.38.0 in /python (#3209)

Bumps [azure-core](https://github.com/Azure/azure-sdk-for-python) from 1.37.0 to 1.38.0.
- [Release notes](https://github.com/Azure/azure-sdk-for-python/releases)
- [Commits](https://github.com/Azure/azure-sdk-for-python/compare/azure-core_1.37.0...azure-core_1.38.0)

---
updated-dependencies:
- dependency-name: azure-core
  dependency-version: 1.38.0
  dependency-type: indirect
...

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

* Python: Create/Get Agent API for OpenAI Assistants (#3208)

* Added provider implementation

* Added example with response format

* Small improvements

* Python: (AG-UI) Support service-managed thread on AG-UI  (#3136)

* added service thread support

* set service_thread_id to only supplied_thread_id

* uses raw_representation to extract the conversation_id

* removed accidental edit

* updated test to use raw_representation

* resolves copilot review feedback

* revert back StubAgent, since not used

* removed relative module import

* removed hasattr check per PR feedback

* Create/Get Agent API - fixes and example improvements (#3246)

* Fix merge conflicts

---------

Signed-off-by: dependabot[bot] <support@github.com>
Signed-off-by: Dina Suehiro Jones <dina.s.jones@intel.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
Co-authored-by: Kurt <65111699+q33566@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: Korolev Dmitry <deagle.gross@gmail.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@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: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Jose Luis Latorre Millas <joslat@gmail.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Richard Ortega <richardjortega@gmail.com>
Co-authored-by: 刘邦学AI <lbbniu@gmail.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Nico Möller <nkm-moeller@mail.de>
Co-authored-by: Chris Gillum <cgillum@microsoft.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Phillip Hoff <phillip.hoff@gmail.com>
Co-authored-by: Ege Ozan Özyedek <36128615+egeozanozyedek@users.noreply.github.com>
Co-authored-by: samueljohnsiby <66901393+samueljohnsiby@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
Co-authored-by: Hao Luo <338265+howlowck@users.noreply.github.com>
Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Jacob Viau <javia@microsoft.com>
Co-authored-by: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Co-authored-by: Sunil Dutta <dutta.2003@gmail.com>
Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>
Co-authored-by: Syrine Chelly <62653967+SyChell@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: takanori-terai <123897708+takanori-terai@users.noreply.github.com>
Co-authored-by: claude89757 <138977524+claude89757@users.noreply.github.com>
Co-authored-by: Gavin Aguiar <80794152+gavin-aguiar@users.noreply.github.com>
Co-authored-by: Sukeesh <vsukeeshbabu@gmail.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>
Co-authored-by: Ao Chen <chenao3220@gmail.com>
Co-authored-by: Dina Suehiro Jones <dina.s.jones@intel.com>
2026-01-16 16:59:49 -08:00
Laveesh RohraandGitHub 3df916064c Python: Add Durabletask samples and minor fixes (#3157)
* Add samples and minor fixes

* Add redis sample and wait-for-completion

* Add wait-for-completion support

* ADd missing docs
2026-01-14 10:56:11 -08:00
+35 1e36ba33c4 .NET: Python: Merge main into feature-durabletask-python branch (#3160)
* Python: Add factory pattern to concurrent orchestration builder (#2738)

* Add factory pattern to concurrent orchestration builder

* Update readme

* Address AI comments

* Fix unit tests

* Fix import

* Prevent multiple calls to set participants or factories

* Add comments

* Mitigate warnings

* Fix mypy

* Address comments

* Address Copilot comments

* Fix tests

* Python: fix: GroupChat ManagerSelectionResponse JSON Schema for OpenAI Structured Outpu… (#2750)

* fix: ManagerSelectionResponse JSON Schema for OpenAI Structured Output Strict Mode

* refactor: install pre-commit then commit again

* Capture file IDs from code interpreter in streaming responses (#2741)

* .NET: [BREAKING] Prevent nulls in AIAgent property (#2719)

* prevent nulls in AIAgent property

* address feedback

* code ql sm04598 (#2723)

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

* .NET: Add Conversation State Sample (Step05) (#2697)

* Initial plan

* Add Agent_OpenAI_Step05_Conversation sample for conversation state management

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

* Update Program.cs comment to accurately describe the sample

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

* Update the code to use the ConversationClient more in line with the samples in OpenAI

* Apply suggestions from code review

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

* Changing sample to use ChatClientAgent and conversationId in GetNewThread

---------

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: Copilot <175728472+Copilot@users.noreply.github.com>

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.7 to 4.0.4.11 (#2777)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.4.11
  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 Azure.Identity from 1.17.0 to 1.17.1 (#2780)

---
updated-dependencies:
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.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 Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2778)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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>

* Python: added more complete parsing for mcp tool arguments (#2756)

* added more complete parsing for mcp tool arguments

* fixed mypy

* added nonlocal model counter, and some fixes

* fixes in naming logic

* extracted json parsing function, added parametrized test and checked coverage

* Python: Updated package versions (#2784)

* Updated package versions

* Small fix

* Bump actions/checkout from 5 to 6 (#2404)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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>

* .NET: adds support for labels in edges,  fixes rendering of labels in dot a… (#1507)

* adds support for labels in edges,  fixes rendering of labels in dot and mermaid, adds rendering of labels in edges

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Visualization/WorkflowVisualizer.cs

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

* escaping edge labels, adding tests for labels containing strange characters that would break the diagram and enabling the previous signature so the API has backwards compatibility.

* Unify label in EdgeData

* Edge API adjustments, removed useless "sanitizer"

* fixed test

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Python: Added custom args and thread object to ai_function kwargs (#2769)

* Added an example of using kwargs in ai_function

* Added thread object to ai_function kwargs

* Updated docs

* Small fix

* Added thread parameter filtering

* Fix WorkflowAgent to include thread convo history. Enable checkpointing. (#2774)

* Update OpenAIResponses.yaml to match AgentSchema (#2598)

1. Update `connection` child types --  `kind: ApiKey` to `kind: key` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/apikeyconnection/

2.  Update `outputSchema`'s `PropertySchema` to be `kind` instead of `type` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/propertyschema/

* Python: Remove warnings from workflow builder on not using factories (#2808)

* Revert concurrent

* Fix comments

* Python: Filter framework kwargs from MCP tool invocations (#2870)

* Filter framework kwargs from MCP tool invocations

* Fixes

* Python: Fix WorkflowAgent to emit yield_output as agent response (#2866)

* Fix WorkflowAgent to emit yield_output as agent response

* use raw_representation

* Raw representation handling

* Python: Use agent description in HandoffBuilder auto-generated tools (#2713) (#2714)

## Summary
Enhanced `HandoffBuilder._apply_auto_tools` to use the target agent's
description when creating handoff tools, providing more informative tool
descriptions for LLMs.

## Changes
- Modified `_apply_auto_tools` to extract `description` from
  `AgentExecutor._agent` when available
- Updated iteration to use `.items()` for more efficient dict traversal
- Handoff tools now use agent descriptions instead of generic placeholders

## Example
Before: "Handoff to the refund_agent agent."
After: "You handle refund requests. Ask for order details and process refunds."

## Testing
- All handoff tests pass (20/20)
- No breaking changes to existing API

Fixes #2713

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

* Python: [BREAKING] Observability updates (#2782)

* fixes Python: Add env_file_path parameter to setup_observability() similar to AzureOpenAIChatClient
Fixes #2186

* WIP on updates using configure_azure_monitor

* improved setup and clarity

* fixed root .env.example

* revert changes

* updated files

* updated sample

* updated zero code

* test fixes and fixed links

* fix devui

* removed planning docs

* added enable method and updated readme and samples

* clarified docstring

* add return annotation

* updated naming

* update capatilized version

* updated readme and some fixes

* updated decorator name inline with the rest

* feedback from comments addressed

* Python: Fix middleware terminate flag to exit function calling loop immediately (#2868)

* Fix middleware terminate flag to exit function calling loop immediately

* Eliminating duck typing

* Improve function exec result handling

* Fix race condition

* Fix mypy issues

* Python: Fix context duplication in handoff workflows when restoring from checkpoint (#2867)

* Fix context duplication in handoff workflows when restoring from checkpoint

* Address Copilot PR review

* .NET: Update to latest Azure.AI.*, OpenAI, and M.E.AI* (#2850)

* Update to latest Azure.AI.*, OpenAI, and M.E.AI*

Absorb breaking changes in Responses surface area

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Program.cs

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

* Using patch to remove the model is necessary, updated the response client to actually use the the ForAgent

---------

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

* Bump actions/download-artifact from 6 to 7 (#2862)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/cache from 4 to 5 (#2861)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/upload-artifact from 5 to 6 (#2860)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Python : Ollama Connector for Agent Framework (#1104)

* Initial Commit for Olama Connector

* Added Olama Sample

* Add Sample & Fixed Open Telemetry

* Fixed Spelling from Olama to Ollama

* remove"opentelemetry-semantic-conventions-ai ~=0.4.13" since its handled in a different pr

* Added Tool Calling

* Finalizing test cases

* Adjust samples to be more reliable

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Update python/packages/ollama/pyproject.toml

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

* Update python/packages/ollama/tests/test_ollama_chat_client.py

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

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Improved Docstrings & Sample

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>

* Integrate PR Feedback
- Divided Streaming and Non-Streaming into independent Methods
- Catch Ollama Validation Error
- Add OTEL Provider Name
- Checked Ollama Messages
- Add Usage Statistics

* Revert setting, so it can be none

* Validate Message formatting between AF and Ollama

* Catch Ollama Error and raise a ServiceResponse Error

* Fix mypy error

* remove .vscode comma

* Add Reasoning support & adjust to new structure

* Add Ollama Multimodality and Reasoning

* Add test cases for reasoning

* Add Tests for Error Handling in Ollama Client

* Update python/samples/getting_started/multimodal_input/ollama_chat_multimodal.py

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

* Integrated Copilot Feedback

* Implement first PR Feedback

* Adjust Readme files for examples

* Adjust argument passing via additional chat options

* Implemented PR Feedback

* Removing Ollama Package from Core and moving samples

* Fix Link & Adding Samples to Main Sample Readme

* Fixing Links in Readme

* Moved Multimodal and Chat Example

* Fixed Link in ChatClient to Ollama

* Fix AgentFramework Links in Ollama Project

* Fix observability breaking change

---------

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

* Skip failing IT (#2904)

* .NET: Cosmos DB UT Fast Skip (For Non-Configured Local envs) (#2906)

* Cosmos DB UT Fast Skip (Non-Configured Local envs) + Long running UT skip in pipeline when no CosmosDB changes happened

* Force a CosmosDB source code change to trigger the pipeline

* Address possible string boolean mismatch

* Add debug

* Enabling emulator always when running IT

* .NET: Add TTLs to durable agent sessions (#2679)

* .NET: Add TTLs to durable agent sessions

* Remove unnecessary async

* PR feedback: clarify UTC

* PR feedback: limit minimum signal delay to <= 5 minutes

* PR feedback: Fix TTL disablement

* Linter: use auto-property

* Fix build break from OpenAI SDK change

* Updated CHANGELOG.md

* PR feedback

* Reduce default TTL to 14 days to work around DTS bug

* Python:  Update Mem0Provider to use v2 search API `filters` parameter (#2766)

* short fix to move id parameters to filters object

* added tests

* small fix

* mem0 dependency update

* Updated package versions (#2913)

* .NET: Switch to new "Run" method name. (#2843)

* Switch to new "RunAgent" method name.

* Try to disable false positive naming warning.

* Add comment about disabled warnings.

* Rename `RunAgent` to just `Run`.

* Update CHANGELOG.

* Python: Switch to new "run" method name. (#2890)

* Switch to `run` method.

* Add support for deprecated `run_agent`.

* Fix entity method name.

* Fix method name and improve tests.

* Update comment.

* Update Python CHANGELOG.

* [BREAKING] Python: Add factory pattern to handoff orchestration builder (#2844)

* WIP: Factory pattern to handoff

* Add factory pattern to concurrent orchestration builder; Next: tests and sample verification

* Add tests and improve comments

* Fix mypy

* Simplify handoff_simple.py

* Simplify handoff_autonoumous.py and bug fix

* Update readme

* Address Copilot comments

* Python: Flow custom kwargs to agents via Workflow SharedState (#2894)

* Flow custom kwargs to agents via SharedState

* Address Copilot feedback

* Improve sample typing

* Fix test

* Fix Pydantic error when using Literal type for tool params (#2893)

* Updated Ollama package version (#2920)

* Python: Azure AI Agent with Bing Grounding Citations Sample (#2892)

* bing grounding sample with citations

* small fix

* fix

* .NET: Make DelegatingAIAgent abstract (#2797)

* Initial plan

* Make DelegatingAIAgent abstract

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Added additional arguments for Azure AI agent (#2922)

* Python: Correction of MCP image type conversion in  _mcp.py (#2901)

* Correction of MCP image type conversion in  _mcp.py

* Added a new overload to the init function of the DataContent() type of the Agent Framework, edited the test case to correctly test the usage of the data and uri fields while using DataContent()

* Fixed tests related to the changes of the DataContent type, added testing for both string and byte representations

* Pass kwargs into subworkflows (#2923)

* Python: Move ollama samples to samples getting started dir (#2921)

* Move ollama samples to samples getting started dir

* Address feedback

* Python: fix: correct BadRequestError when using Pydantic model in response_fo… (#1843)

* fix: correct BadRequestError when using Pydantic model in response_format

* Fix lint

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>

* .NET: [Breaking] Delete display name property (#2758)

* delete the AIAgent.DisplayName property

* use agent name as a first value for activity display name

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs

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

---------

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

* Python: cleanup and refactoring of chat clients (#2937)

* refactoring and unifying naming schemes of internal methods of chat clients

* set tool_choice to auto

* fix for mypy

* added note on naming and fix #2951

* fix responses

* fixes in azure ai agents client

* Python: Workflow add option to visualize internal executors (#2917)

* Workflow add option to visualize internal executors

* Address Copilot comments

* Python: Fixes Run ID and Thread ID casing to align with AG-UI Typescript SDK (#2948)

* added camelCase input to run id and thread id aligning with @ag-ui/core

* fixed per copilot suggestions

* Python: Add workflow cancellation sample (#2732)

* Add workflow cancellation sample

Add sample demonstrating how to cancel a running workflow using asyncio
tasks. Shows both cancellation mid-execution and normal completion paths.
Useful for implementing timeouts, graceful shutdown, or A2A executors.

* update docstring

* .NET: Update Anthropic package to version 12.0.0 (#2914)

* Initial plan

* Update Anthropic package to version 12.0.0

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>

---------

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

* Python: Add Azure Managed Redis Support with Credential Provider (#2887)

* azure redis support

* small fixes

* azure managed redis sample

* fixes

* Bump CommunityToolkit.Aspire.OllamaSharp from 13.0.0-beta.440 to 13.0.0 (#2856)

---
updated-dependencies:
- dependency-name: CommunityToolkit.Aspire.OllamaSharp
  dependency-version: 13.0.0
  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 AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.11 to 4.0.5 (#2853)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5
  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: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>

* Bump Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2854)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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: Fix WorkflowAgent event handling and kwargs forwarding (#2946)

* Fix kwargs propagation through workflow.as_agent()

* Fix WorkflowAgent to respect AgentExecutor output_response setting

* .NET: Use GrpcEntityRunner instead of TaskEntityDispatcher (#2759)

* Use GrpcEntityRunner instead of TaskEntityDispatcher

* Pin to Durable worker 1.11.0

* Set the invocation result

* Update all Durable packages

* Update changelog, rename dispatcher to encondedEntityRequest

* Python: Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG (#2968)

* Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG

* update lock

* Fix formatting

* Fix ChatKit typing

* Python: Introducing Foundry Local Chat Clients (#2915)

* redo foundry local chat client

* fix mypy and spelling

* better docstring, updated sample

* fixed tests and added tests

* small sample update

* Updated package versions (#2978)

* Python: Added GitHub MCP sample with PAT (#2967)

* added github mcp sample with PAT

* addressed copilot fixes

* env fix

* Python: Preserve reasoning blocks with OpenRouter (#2950)

* Preserve reasoning blocks with OpenRouter

* Put encrypted reasoning in TextReasoningContent

* Remove unneccessary change

* Fix docs

* Support streaming

* Fix handling None in TextReasoningContent.text

* Python: Added response.created and response.in_progress event process to OpenAIBaseResponseClient (#2975)

* added response.created and response.in_progress to include response.id

* better doc string

* added tests for the new streaming event types

* Python: Introducing support for Bedrock-hosted models (Anthropic, Cohere, etc.) (#2610)

* Pushing the bedrock related changes to the new branch after addressing the review comments

* 2524 Addressed the second round review comments

* 2524 Addressed few more minor comments on the PR

* resolving the merge conflict

* 2524 resolved the uv.lock conflicts

* 2524 addressed more comments

* 2524 removed the print statement to fix the checks failure

* 2524 resolved the CI failure issues

* 2524 fixing the CI breaks

* 2524 Addressed the review comment

* 2524 resolved conflict

---------

Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>

* .NET: [Durable Agents] Reliable streaming sample (#2942)

* .NET: [Durable Agents] Reliable streaming sample

* Add automated validation for new sample

* Address Copilot PR feedback

* Fix typo in README.md about agent definitions (#2634)

* Fix typo in README.md about agent definitions

* Update agent-samples/README.md

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

---------

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

* Python: latency improvements (#3014)

* latency improvements

* fixed mypy, added coding standards and instructions

* slight logic improvement

* Python: Updated package versions (#3024)

* Updated package versions

* Updated changelog

* Python: add powerfx safe mode (#3028)

* add powerfx safe mode

* improved docstring and aligned env_file loading

* ensured test uses reset

* .NET: [Breaking] Introduce RunCoreAsync/RunCoreStreamingAsync delegation pattern in AIAgent (#2749)

* Initial plan

* Refactor AIAgent: Make RunAsync and RunStreamingAsync non-abstract, add RunCoreAsync and RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix infinite recursion in test implementations

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Make RunAsync and RunStreamingAsync non-virtual as requested

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix DelegatingAIAgent subclasses to use RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix XML documentation references in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Restore <see cref> tags with proper qualified signatures in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Rollback unnecessary XML documentation changes in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Remove pragma and update crefs to RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix EntityAgentWrapper to call base.RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* fix compilation issues

* fix compilatio issue

* fix tests

* fix unit tests

* fix unit test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* add issue template and additional labeling (#3006)

* fix and extra int test (#3037)

* .NET: [BREAKING] Refactor ChatMessageStore methods to be similar to AIContextProvider and add filtering support (#2604)

* Refactor ChatMessageStore methods to be similar to AIContextProvider

* Fix file encoding

* Ensure that AIContextProvider messages area also persisted.

* Update formatting and seal context classes

* Improve formatting

* Remove optional messages from constructor and add unit test

* Add ChatMessageStore filtering via a decorator

* Update sample and cosmos message store to store AIContextProvider messages in right order. Fix unit tests.

* Update Workflowmessage store to use aicontext provider messages.

* Apply suggestions from code review

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

* Apply suggestions from code review

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Improve xml docs messaging

* Address code review comments.

* Also notify message store on failure

---------

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

* [BREAKING] Remove unused AgentThreadMetadata (#3067)

* Remove unused AgentThreadMetadata

* Update DurableTask Changelog

* Python: Fix AzureAIClient failure when conversation history contains assistant messages (#3076)

* Fix AzureAIClient failure when conversation history contains assistant messages

* Address PR review feedback: improve docstring and test assertions

* Remove redundant cast

* Fix: Update OTLP exporter protocol conditions (#3070)

* Python: Fix ExecutorInvokedEvent and ExecutorCompletedEvent observability data (#3090)

* Fix ExecutorInvokedEvent.data mutation bug

* Fix bug related to not yielding output type

* .NET: Seal ChatClientAgentThread (#2842)

* Initial plan

* Seal ChatClientAgentThread class

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Fix broken strands urls. (#3102)

* Fix broken strands urls.

* Fix typos

* .NET: Fix message ordering inconsistency when using AIContextProvider (#2659)

* Initial plan

* Fix message ordering inconsistency when using AIContextProvider

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Revert to original message ordering: Input, AIContextProvider, Response

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Reorder messages to ChatClient to match MessageStore order: Existing, Input, AIContextProvider

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Remove redundant test methods as existing tests already verify the behavior

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* fix: tool_choice parameter not being honored when passed to agent.run() (#3095)

* sharepoint sample fix (#3108)

* Bump versions to 1.0.0b260106 for a release. Update CHANGELOG.md (#3109)

* Bump Bedrock version to latest (#3110)

* Python: Fix MCP tool result serialization for list[TextContent] (#2523)

* Fix MCP tool result serialization for list[TextContent]

When MCP tools return results containing list[TextContent], they were
incorrectly serialized to object repr strings like:
'[<agent_framework._types.TextContent object at 0x...>]'

This fix properly extracts text content from list items by:
1. Checking if items have a 'text' attribute (TextContent)
2. Using model_dump() for items that support it
3. Falling back to str() for other types
4. Joining single items as plain text, multiple items as JSON array

Fixes #2509

* Address PR review feedback for MCP tool result serialization

- Extract serialize_content_result() to shared _utils.py
- Fix logic: use texts[0] instead of join for single item
- Add type annotation: texts: list[str] = []
- Return empty string for empty list instead of '[]'
- Move import json to file top level
- Add comprehensive unit tests for serialization

* Address PR review feedback: fix type checking and double serialization

- Add isinstance(item.text, str) check to ensure text attribute is a string
- Fix double-serialization issue by keeping model_dump results as dicts
  until final json.dumps (removes escaped JSON strings in arrays)
- Improve docstring with detailed return value documentation
- Add test for non-string text attribute handling
- Add tests for list type tool results in _events.py path

* Simplify PR: minimal changes to fix MCP tool result serialization

Addresses reviewer feedback about excessive refactoring:
- Reset _events.py to original structure
- Only add import and use serialize_content_result in one location
- All review comments addressed in serialize_content_result():
  - Added isinstance(item.text, str) check
  - Use model_dump(mode="json") to avoid double-serialization
  - Improved docstring with explicit return value documentation
  - Empty list returns "" instead of "[]"

* Refactor: Move MCP TextContent serialization to core prepare_function_call_results

Per reviewer feedback, moved the TextContent serialization logic from
ag-ui's serialize_content_result to the core package's
prepare_function_call_results function.

Changes:
- Added handling for objects with 'text' attribute (like MCP TextContent)
  in _prepare_function_call_results_as_dumpable
- Removed serialize_content_result from ag-ui/_utils.py
- Updated _events.py and _message_adapters.py to use
  prepare_function_call_results from core package
- Updated tests to match the core function's behavior

* Fix failing tests for prepare_function_call_results behavior

- test_tool_result_with_none: Update expected value to 'null' (JSON serialization of None)
- test_tool_result_with_model_dump_objects: Use Pydantic BaseModel instead of plain class

* Fix B903 linter error: Convert MockTextContent to dataclass

The ruff linter was reporting B903 (class could be dataclass or namedtuple)
for the MockTextContent test helper classes. This commit converts them to
dataclasses to satisfy the linter check.

* Python: Improve DevUI, add Context Inspector view as new tab under traces (#2742)

* Improve DevUI, add Context Inspector view as new tab under traces

* fix mypy errors

* fix: Handle stale MCP connections in DevUI executor

MCP tools can become stale when HTTP streaming responses end - the underlying
stdio streams close but `is_connected` remains True. This causes subsequent
requests to fail with `ClosedResourceError`.

Add `_ensure_mcp_connections()` to detect and reconnect stale MCP tools before
agent execution. This is a workaround for an upstream Agent Framework issue
where connection state isn't properly tracked.

Fixes MCP tools failing on second HTTP request in DevUI.

fixes  #1476 #1515 #2865

* fix #1572 report import dependency errors more clearly

* Ensure there is streaming toggle where users can select streaming vs non streaming mode in devui . Fixes .NET: [Python] DevUI tool call rendering in non-streaming mode?

* remove unused dead code

* improve ux - workflows with agents show a chat component in execution timelien, also ensure magentic final output shows correctly

* update ui build

* update devui to use instrumentation instead of tracing, other instrumentation and type/instance check fixes

* .NET: Seal factory contexts and add non JSO deserialize overloads (#3066)

* Seal factory contexts and add non JSO deserialize overloads

* Apply suggestions from code review

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

---------

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

* Enable blank issues in issue template configuration

Need to re-enable creating blank issues

* updated templates (#3106)

* updated templates

* enabled blank and fixed triage

* made language optional and moved to the bottom for features

* Python: Streaming sample for azurefunctions (#3057)

* Streaming sample for azurefunctions

* Fixed links and sample name

* Addressed feedback

* Addressed feedback

* Fixed integration tests

* Updated test

* Python: fix(azure-ai): Fix response_format handling for structured outputs (#3114)

* fix(azure-ai): read response_format from chat_options instead of run_options

* refactor: use explicit None checks for response_format

* Fix mypy error

* Mypy fix

* Python: Bump python version to 1.0.0b260107 for a release (#3128)

* Bump python version to 1.0.0b260107 for a release

* Update changelog

* Make A2AAgent public, so that it's concrete implementation methods can be used. (#3119)

* .NET: Map additional props <-> A2A metadata (#3137)

* map additional props from agent run options to a2a request metadata

* small touches

* add unit tests for new extension methods

* Sort using

* add unit test

* add additiona unit tests

* special case json element to avoid unnecessary serialization

* Python: Fix Anthropic streaming response bugs (#3141)

* test commit identity

* fix(anthropic): fix raw_representation and finish_reason in streaming

* lint fix

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.5 to 4.0.5.1 (#2994)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Bump Anthropic from 12.0.0 to 12.0.1 (#2993)

---
updated-dependencies:
- dependency-name: Anthropic
  dependency-version: 12.0.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* .NET: [Breaking] Prevent loss of input messages & streamed updates when resuming streaming (#2748)

* save input messages and stream updates to the continuation token to be able to use them in the last successful stream resumption call.

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/tests/Microsoft.Agents.AI.UnitTests/ChatClient/ChatClientAgent_BackgroundResponsesTests.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* Update dotnet/src/Microsoft.Agents.AI/ChatClient/ChatClientAgentContinuationToken.cs

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

* fix typo

* init continuation token from chat response

* remove unnecessary types for source generation

* remove check for continuation token passed at initial run

* remove check for continuation token pass at initial run

* centralize continuation token parsing

* update xml comments

* use readonly collection instead of enumerable

---------

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

* .NET: fix: Expose WorkflowErrorEvent as ErrorContent (#2762)

* fix: Expose WorkflowErrorEvent as ErrorContent

When hosted using .AsAgent(), Workflows were not exposing inner errors coming as Exceptions (through the WorkflowErrorEvent)

The fix is to convert their message to an ErrorContent on the way out, rather than rely on the default "empty update" to collect the raw event.

* feat: Add a way to show/suppress exception information

* Bump Microsoft.Agents.AI.Workflows from 1.0.0-preview.251125.1 to 1.0.0-preview.251219.1 (#2997)

---
updated-dependencies:
- dependency-name: Microsoft.Agents.AI.Workflows
  dependency-version: 1.0.0-preview.251219.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* .NET: Add Run overloads to expose ChatClientAgentRunOptions in IntelliSense (#3115)

* Initial plan

* Add ChatClientAgentExtensions for improved discoverability of ChatClientAgentRunOptions

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Address code review feedback - use collection expression syntax

Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Apply suggestion from @westey-m

* Fix issues with Copilot implementation

* Add additional tests for structured output overloads.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: westey-m <164392973+westey-m@users.noreply.github.com>

* Python: Add tool call/result content types and update connectors and samples (#2971)

* Add new AI content types and image tool support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Add Python content types for tool calls/results and image generation tool support

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Address review feedback for tool content and samples

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Tighten image generation typing and sample tools list

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Align image generation output typing

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Handle MCP naming, image options mapping, and connector tool content

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Allow MCP call in function approval request

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Remove raw image_generation tool remapping

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Restore Anthropic tool_use to function calls unless code execution

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix lint issues for hosted file docstring and MCP parsing

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Import ChatResponse types in Anthropic client

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Fix Anthropics citation type imports and MCP typing for handoff/tools

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* Skip lightning tests without agentlightning and fix function call import

Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>

* fix lint on lab package

* rebuilt anthropic parsing

* redid anthropic parsing

* typo

* updated parsing and added missing docstrings

* fix tests

* mypy fixes

* second mypy fix

* add new class to other samples

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>

* Bump Google.GenAI from 0.6.0 to 0.9.0 (#2995)

---
updated-dependencies:
- dependency-name: Google.GenAI
  dependency-version: 0.9.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>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Bump js-yaml from 4.1.0 to 4.1.1 in /python/packages/devui/frontend (#3123)

Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.1.0 to 4.1.1.
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/4.1.0...4.1.1)

---
updated-dependencies:
- dependency-name: js-yaml
  dependency-version: 4.1.1
  dependency-type: indirect
...

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

* Updated package versions (#3144)

* .NET: Bump Microsoft.Agents.AI.OpenAI and Microsoft.Extensions.AI.OpenAI (#2996)

* Bump Microsoft.Agents.AI.OpenAI and Microsoft.Extensions.AI.OpenAI

Bumps Microsoft.Agents.AI.OpenAI from 1.0.0-preview.251125.1 to 1.0.0-preview.251219.1
Bumps Microsoft.Extensions.AI.OpenAI from 10.1.0-preview.1.25608.1 to 10.1.1-preview.1.25612.2

---
updated-dependencies:
- dependency-name: Microsoft.Agents.AI.OpenAI
  dependency-version: 1.0.0-preview.251219.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.AI.OpenAI
  dependency-version: 10.1.1-preview.1.25612.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Agents.AI.OpenAI
  dependency-version: 1.0.0-preview.251219.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Microsoft.Extensions.AI.OpenAI
  dependency-version: 10.1.1-preview.1.25612.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

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

* Fixed samples

---------

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>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>

* Python: fix(ag-ui): Execute tools with approval_mode, fix shared state, code cleanup  (#3079)

* fix(ag-ui): execute tools after approval in human-in-the-loop flow

* Fix shared state bug

* Bug fix finalized

* Refactoring to clean up code

* Code cleanup

* More fixes

* More code cleanup

* Add version detection in __init__.py to ruff ignore list

* Track agent name with updates for workflow agent (#3146)

* Python: Fix AzureAIClient tool call bug for AG-UI use (#3148)

* Fiz AzureAIClient tool call bug

* Address copilot feedback

* Revert to match main

* revert file to main

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
Co-authored-by: Kurt <65111699+q33566@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: Korolev Dmitry <deagle.gross@gmail.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@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: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Jose Luis Latorre Millas <joslat@gmail.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Richard Ortega <richardjortega@gmail.com>
Co-authored-by: 刘邦学AI <lbbniu@gmail.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Nico Möller <nkm-moeller@mail.de>
Co-authored-by: Chris Gillum <cgillum@microsoft.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Phillip Hoff <phillip.hoff@gmail.com>
Co-authored-by: Ege Ozan Özyedek <36128615+egeozanozyedek@users.noreply.github.com>
Co-authored-by: samueljohnsiby <66901393+samueljohnsiby@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
Co-authored-by: Hao Luo <338265+howlowck@users.noreply.github.com>
Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Jacob Viau <javia@microsoft.com>
Co-authored-by: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Co-authored-by: Sunil Dutta <dutta.2003@gmail.com>
Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>
Co-authored-by: Syrine Chelly <62653967+SyChell@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: westey <164392973+westey-m@users.noreply.github.com>
Co-authored-by: takanori-terai <123897708+takanori-terai@users.noreply.github.com>
Co-authored-by: claude89757 <138977524+claude89757@users.noreply.github.com>
Co-authored-by: Gavin Aguiar <80794152+gavin-aguiar@users.noreply.github.com>
Co-authored-by: Sukeesh <vsukeeshbabu@gmail.com>
Co-authored-by: eavanvalkenburg <13749212+eavanvalkenburg@users.noreply.github.com>
Co-authored-by: eavanvalkenburg <github@vanvalkenburg.eu>
2026-01-09 15:58:49 -08:00
Laveesh RohraandGitHub e3eff65a6b Python: Complete durableagent package (#3058)
* Add worker and clients

* Clean code and refactor common code

* Implement sample

* Add sample

* Update readmes

* Fix tests

* Fix tests

* Update requirements

* Fix typo

* Address comments

* use response.text
2026-01-07 13:53:21 -08:00
+29 a5b36dc379 Python: [Durabletask] Update feature-durabletask-python branch with main (#3068)
* Python: Add factory pattern to concurrent orchestration builder (#2738)

* Add factory pattern to concurrent orchestration builder

* Update readme

* Address AI comments

* Fix unit tests

* Fix import

* Prevent multiple calls to set participants or factories

* Add comments

* Mitigate warnings

* Fix mypy

* Address comments

* Address Copilot comments

* Fix tests

* Python: fix: GroupChat ManagerSelectionResponse JSON Schema for OpenAI Structured Outpu… (#2750)

* fix: ManagerSelectionResponse JSON Schema for OpenAI Structured Output Strict Mode

* refactor: install pre-commit then commit again

* Capture file IDs from code interpreter in streaming responses (#2741)

* .NET: [BREAKING] Prevent nulls in AIAgent property (#2719)

* prevent nulls in AIAgent property

* address feedback

* code ql sm04598 (#2723)

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

* .NET: Add Conversation State Sample (Step05) (#2697)

* Initial plan

* Add Agent_OpenAI_Step05_Conversation sample for conversation state management

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

* Update Program.cs comment to accurately describe the sample

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

* Update the code to use the ConversationClient more in line with the samples in OpenAI

* Apply suggestions from code review

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

* Changing sample to use ChatClientAgent and conversationId in GetNewThread

---------

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: Copilot <175728472+Copilot@users.noreply.github.com>

* Bump AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.7 to 4.0.4.11 (#2777)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.4.11
  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 Azure.Identity from 1.17.0 to 1.17.1 (#2780)

---
updated-dependencies:
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.1
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.Identity
  dependency-version: 1.17.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 Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2778)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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>

* Python: added more complete parsing for mcp tool arguments (#2756)

* added more complete parsing for mcp tool arguments

* fixed mypy

* added nonlocal model counter, and some fixes

* fixes in naming logic

* extracted json parsing function, added parametrized test and checked coverage

* Python: Updated package versions (#2784)

* Updated package versions

* Small fix

* Bump actions/checkout from 5 to 6 (#2404)

Bumps [actions/checkout](https://github.com/actions/checkout) from 5 to 6.
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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>

* .NET: adds support for labels in edges,  fixes rendering of labels in dot a… (#1507)

* adds support for labels in edges,  fixes rendering of labels in dot and mermaid, adds rendering of labels in edges

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Visualization/WorkflowVisualizer.cs

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

* escaping edge labels, adding tests for labels containing strange characters that would break the diagram and enabling the previous signature so the API has backwards compatibility.

* Unify label in EdgeData

* Edge API adjustments, removed useless "sanitizer"

* fixed test

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Python: Added custom args and thread object to ai_function kwargs (#2769)

* Added an example of using kwargs in ai_function

* Added thread object to ai_function kwargs

* Updated docs

* Small fix

* Added thread parameter filtering

* Fix WorkflowAgent to include thread convo history. Enable checkpointing. (#2774)

* Update OpenAIResponses.yaml to match AgentSchema (#2598)

1. Update `connection` child types --  `kind: ApiKey` to `kind: key` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/apikeyconnection/

2.  Update `outputSchema`'s `PropertySchema` to be `kind` instead of `type` otherwise schema will fail: https://microsoft.github.io/AgentSchema/reference/propertyschema/

* Python: Remove warnings from workflow builder on not using factories (#2808)

* Revert concurrent

* Fix comments

* Python: Filter framework kwargs from MCP tool invocations (#2870)

* Filter framework kwargs from MCP tool invocations

* Fixes

* Python: Fix WorkflowAgent to emit yield_output as agent response (#2866)

* Fix WorkflowAgent to emit yield_output as agent response

* use raw_representation

* Raw representation handling

* Python: Use agent description in HandoffBuilder auto-generated tools (#2713) (#2714)

## Summary
Enhanced `HandoffBuilder._apply_auto_tools` to use the target agent's
description when creating handoff tools, providing more informative tool
descriptions for LLMs.

## Changes
- Modified `_apply_auto_tools` to extract `description` from
  `AgentExecutor._agent` when available
- Updated iteration to use `.items()` for more efficient dict traversal
- Handoff tools now use agent descriptions instead of generic placeholders

## Example
Before: "Handoff to the refund_agent agent."
After: "You handle refund requests. Ask for order details and process refunds."

## Testing
- All handoff tests pass (20/20)
- No breaking changes to existing API

Fixes #2713

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

* Python: [BREAKING] Observability updates (#2782)

* fixes Python: Add env_file_path parameter to setup_observability() similar to AzureOpenAIChatClient
Fixes #2186

* WIP on updates using configure_azure_monitor

* improved setup and clarity

* fixed root .env.example

* revert changes

* updated files

* updated sample

* updated zero code

* test fixes and fixed links

* fix devui

* removed planning docs

* added enable method and updated readme and samples

* clarified docstring

* add return annotation

* updated naming

* update capatilized version

* updated readme and some fixes

* updated decorator name inline with the rest

* feedback from comments addressed

* Python: Fix middleware terminate flag to exit function calling loop immediately (#2868)

* Fix middleware terminate flag to exit function calling loop immediately

* Eliminating duck typing

* Improve function exec result handling

* Fix race condition

* Fix mypy issues

* Python: Fix context duplication in handoff workflows when restoring from checkpoint (#2867)

* Fix context duplication in handoff workflows when restoring from checkpoint

* Address Copilot PR review

* .NET: Update to latest Azure.AI.*, OpenAI, and M.E.AI* (#2850)

* Update to latest Azure.AI.*, OpenAI, and M.E.AI*

Absorb breaking changes in Responses surface area

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/AgentWebChat/AgentWebChat.AgentHost/Utilities/ChatClientExtensions.cs

* Update dotnet/samples/GettingStarted/AgentWithOpenAI/Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/Program.cs

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

* Using patch to remove the model is necessary, updated the response client to actually use the the ForAgent

---------

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

* Bump actions/download-artifact from 6 to 7 (#2862)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 6 to 7.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/cache from 4 to 5 (#2861)

Bumps [actions/cache](https://github.com/actions/cache) from 4 to 5.
- [Release notes](https://github.com/actions/cache/releases)
- [Changelog](https://github.com/actions/cache/blob/main/RELEASES.md)
- [Commits](https://github.com/actions/cache/compare/v4...v5)

---
updated-dependencies:
- dependency-name: actions/cache
  dependency-version: '5'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Bump actions/upload-artifact from 5 to 6 (#2860)

Bumps [actions/upload-artifact](https://github.com/actions/upload-artifact) from 5 to 6.
- [Release notes](https://github.com/actions/upload-artifact/releases)
- [Commits](https://github.com/actions/upload-artifact/compare/v5...v6)

---
updated-dependencies:
- dependency-name: actions/upload-artifact
  dependency-version: '6'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

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

* Python : Ollama Connector for Agent Framework (#1104)

* Initial Commit for Olama Connector

* Added Olama Sample

* Add Sample & Fixed Open Telemetry

* Fixed Spelling from Olama to Ollama

* remove"opentelemetry-semantic-conventions-ai ~=0.4.13" since its handled in a different pr

* Added Tool Calling

* Finalizing test cases

* Adjust samples to be more reliable

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Update python/packages/ollama/pyproject.toml

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

* Update python/packages/ollama/tests/test_ollama_chat_client.py

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

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

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

* Improved Docstrings & Sample

* Update python/packages/ollama/agent_framework_ollama/_chat_client.py

Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>

* Integrate PR Feedback
- Divided Streaming and Non-Streaming into independent Methods
- Catch Ollama Validation Error
- Add OTEL Provider Name
- Checked Ollama Messages
- Add Usage Statistics

* Revert setting, so it can be none

* Validate Message formatting between AF and Ollama

* Catch Ollama Error and raise a ServiceResponse Error

* Fix mypy error

* remove .vscode comma

* Add Reasoning support & adjust to new structure

* Add Ollama Multimodality and Reasoning

* Add test cases for reasoning

* Add Tests for Error Handling in Ollama Client

* Update python/samples/getting_started/multimodal_input/ollama_chat_multimodal.py

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

* Integrated Copilot Feedback

* Implement first PR Feedback

* Adjust Readme files for examples

* Adjust argument passing via additional chat options

* Implemented PR Feedback

* Removing Ollama Package from Core and moving samples

* Fix Link & Adding Samples to Main Sample Readme

* Fixing Links in Readme

* Moved Multimodal and Chat Example

* Fixed Link in ChatClient to Ollama

* Fix AgentFramework Links in Ollama Project

* Fix observability breaking change

---------

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

* Skip failing IT (#2904)

* .NET: Cosmos DB UT Fast Skip (For Non-Configured Local envs) (#2906)

* Cosmos DB UT Fast Skip (Non-Configured Local envs) + Long running UT skip in pipeline when no CosmosDB changes happened

* Force a CosmosDB source code change to trigger the pipeline

* Address possible string boolean mismatch

* Add debug

* Enabling emulator always when running IT

* .NET: Add TTLs to durable agent sessions (#2679)

* .NET: Add TTLs to durable agent sessions

* Remove unnecessary async

* PR feedback: clarify UTC

* PR feedback: limit minimum signal delay to <= 5 minutes

* PR feedback: Fix TTL disablement

* Linter: use auto-property

* Fix build break from OpenAI SDK change

* Updated CHANGELOG.md

* PR feedback

* Reduce default TTL to 14 days to work around DTS bug

* Python:  Update Mem0Provider to use v2 search API `filters` parameter (#2766)

* short fix to move id parameters to filters object

* added tests

* small fix

* mem0 dependency update

* Updated package versions (#2913)

* .NET: Switch to new "Run" method name. (#2843)

* Switch to new "RunAgent" method name.

* Try to disable false positive naming warning.

* Add comment about disabled warnings.

* Rename `RunAgent` to just `Run`.

* Update CHANGELOG.

* Python: Switch to new "run" method name. (#2890)

* Switch to `run` method.

* Add support for deprecated `run_agent`.

* Fix entity method name.

* Fix method name and improve tests.

* Update comment.

* Update Python CHANGELOG.

* [BREAKING] Python: Add factory pattern to handoff orchestration builder (#2844)

* WIP: Factory pattern to handoff

* Add factory pattern to concurrent orchestration builder; Next: tests and sample verification

* Add tests and improve comments

* Fix mypy

* Simplify handoff_simple.py

* Simplify handoff_autonoumous.py and bug fix

* Update readme

* Address Copilot comments

* Python: Flow custom kwargs to agents via Workflow SharedState (#2894)

* Flow custom kwargs to agents via SharedState

* Address Copilot feedback

* Improve sample typing

* Fix test

* Fix Pydantic error when using Literal type for tool params (#2893)

* Updated Ollama package version (#2920)

* Python: Azure AI Agent with Bing Grounding Citations Sample (#2892)

* bing grounding sample with citations

* small fix

* fix

* .NET: Make DelegatingAIAgent abstract (#2797)

* Initial plan

* Make DelegatingAIAgent abstract

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

---------

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

* Added additional arguments for Azure AI agent (#2922)

* Python: Correction of MCP image type conversion in  _mcp.py (#2901)

* Correction of MCP image type conversion in  _mcp.py

* Added a new overload to the init function of the DataContent() type of the Agent Framework, edited the test case to correctly test the usage of the data and uri fields while using DataContent()

* Fixed tests related to the changes of the DataContent type, added testing for both string and byte representations

* Pass kwargs into subworkflows (#2923)

* Python: Move ollama samples to samples getting started dir (#2921)

* Move ollama samples to samples getting started dir

* Address feedback

* Python: fix: correct BadRequestError when using Pydantic model in response_fo… (#1843)

* fix: correct BadRequestError when using Pydantic model in response_format

* Fix lint

---------

Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>

* .NET: [Breaking] Delete display name property (#2758)

* delete the AIAgent.DisplayName property

* use agent name as a first value for activity display name

* Update dotnet/src/Microsoft.Agents.AI.Workflows/Specialized/HandoffAgentExecutor.cs

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

---------

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

* Python: cleanup and refactoring of chat clients (#2937)

* refactoring and unifying naming schemes of internal methods of chat clients

* set tool_choice to auto

* fix for mypy

* added note on naming and fix #2951

* fix responses

* fixes in azure ai agents client

* Python: Workflow add option to visualize internal executors (#2917)

* Workflow add option to visualize internal executors

* Address Copilot comments

* Python: Fixes Run ID and Thread ID casing to align with AG-UI Typescript SDK (#2948)

* added camelCase input to run id and thread id aligning with @ag-ui/core

* fixed per copilot suggestions

* Python: Add workflow cancellation sample (#2732)

* Add workflow cancellation sample

Add sample demonstrating how to cancel a running workflow using asyncio
tasks. Shows both cancellation mid-execution and normal completion paths.
Useful for implementing timeouts, graceful shutdown, or A2A executors.

* update docstring

* .NET: Update Anthropic package to version 12.0.0 (#2914)

* Initial plan

* Update Anthropic package to version 12.0.0

Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>

---------

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

* Python: Add Azure Managed Redis Support with Credential Provider (#2887)

* azure redis support

* small fixes

* azure managed redis sample

* fixes

* Bump CommunityToolkit.Aspire.OllamaSharp from 13.0.0-beta.440 to 13.0.0 (#2856)

---
updated-dependencies:
- dependency-name: CommunityToolkit.Aspire.OllamaSharp
  dependency-version: 13.0.0
  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 AWSSDK.Extensions.Bedrock.MEAI from 4.0.4.11 to 4.0.5 (#2853)

---
updated-dependencies:
- dependency-name: AWSSDK.Extensions.Bedrock.MEAI
  dependency-version: 4.0.5
  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: Mark Wallace <127216156+markwallace-microsoft@users.noreply.github.com>

* Bump Azure.AI.AgentServer.AgentFramework from 1.0.0-beta.4 to 1.0.0-beta.5 (#2854)

---
updated-dependencies:
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  dependency-type: direct:production
  update-type: version-update:semver-patch
- dependency-name: Azure.AI.AgentServer.AgentFramework
  dependency-version: 1.0.0-beta.5
  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: Fix WorkflowAgent event handling and kwargs forwarding (#2946)

* Fix kwargs propagation through workflow.as_agent()

* Fix WorkflowAgent to respect AgentExecutor output_response setting

* .NET: Use GrpcEntityRunner instead of TaskEntityDispatcher (#2759)

* Use GrpcEntityRunner instead of TaskEntityDispatcher

* Pin to Durable worker 1.11.0

* Set the invocation result

* Update all Durable packages

* Update changelog, rename dispatcher to encondedEntityRequest

* Python: Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG (#2968)

* Bump Py version to 1.0.0b251218 for a release. Update CHANGELOG

* update lock

* Fix formatting

* Fix ChatKit typing

* Python: Introducing Foundry Local Chat Clients (#2915)

* redo foundry local chat client

* fix mypy and spelling

* better docstring, updated sample

* fixed tests and added tests

* small sample update

* Updated package versions (#2978)

* Python: Added GitHub MCP sample with PAT (#2967)

* added github mcp sample with PAT

* addressed copilot fixes

* env fix

* Python: Preserve reasoning blocks with OpenRouter (#2950)

* Preserve reasoning blocks with OpenRouter

* Put encrypted reasoning in TextReasoningContent

* Remove unneccessary change

* Fix docs

* Support streaming

* Fix handling None in TextReasoningContent.text

* Python: Added response.created and response.in_progress event process to OpenAIBaseResponseClient (#2975)

* added response.created and response.in_progress to include response.id

* better doc string

* added tests for the new streaming event types

* Python: Introducing support for Bedrock-hosted models (Anthropic, Cohere, etc.) (#2610)

* Pushing the bedrock related changes to the new branch after addressing the review comments

* 2524 Addressed the second round review comments

* 2524 Addressed few more minor comments on the PR

* resolving the merge conflict

* 2524 resolved the uv.lock conflicts

* 2524 addressed more comments

* 2524 removed the print statement to fix the checks failure

* 2524 resolved the CI failure issues

* 2524 fixing the CI breaks

* 2524 Addressed the review comment

* 2524 resolved conflict

---------

Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>

* .NET: [Durable Agents] Reliable streaming sample (#2942)

* .NET: [Durable Agents] Reliable streaming sample

* Add automated validation for new sample

* Address Copilot PR feedback

* Fix typo in README.md about agent definitions (#2634)

* Fix typo in README.md about agent definitions

* Update agent-samples/README.md

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

---------

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

* Python: latency improvements (#3014)

* latency improvements

* fixed mypy, added coding standards and instructions

* slight logic improvement

* Python: Updated package versions (#3024)

* Updated package versions

* Updated changelog

* Python: add powerfx safe mode (#3028)

* add powerfx safe mode

* improved docstring and aligned env_file loading

* ensured test uses reset

* .NET: [Breaking] Introduce RunCoreAsync/RunCoreStreamingAsync delegation pattern in AIAgent (#2749)

* Initial plan

* Refactor AIAgent: Make RunAsync and RunStreamingAsync non-abstract, add RunCoreAsync and RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix infinite recursion in test implementations

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Make RunAsync and RunStreamingAsync non-virtual as requested

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix DelegatingAIAgent subclasses to use RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix XML documentation references in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Restore <see cref> tags with proper qualified signatures in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Rollback unnecessary XML documentation changes in AnonymousDelegatingAIAgent

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Remove pragma and update crefs to RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* Fix EntityAgentWrapper to call base.RunCoreAsync/RunCoreStreamingAsync

Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>

* fix compilation issues

* fix compilatio issue

* fix tests

* fix unit tests

* fix unit test

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>

* Remove from feature branch

* Remove ollama changes

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: Tao Chen <taochen@microsoft.com>
Co-authored-by: Kurt <65111699+q33566@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <68852919+SergeyMenshykh@users.noreply.github.com>
Co-authored-by: Korolev Dmitry <deagle.gross@gmail.com>
Co-authored-by: Mark Wallace <127216156+markwallace-microsoft@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: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
Co-authored-by: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com>
Co-authored-by: Chris <66376200+crickman@users.noreply.github.com>
Co-authored-by: Jose Luis Latorre Millas <joslat@gmail.com>
Co-authored-by: Jacob Alber <jaalber@microsoft.com>
Co-authored-by: Richard Ortega <richardjortega@gmail.com>
Co-authored-by: 刘邦学AI <lbbniu@gmail.com>
Co-authored-by: Stephen Toub <stoub@microsoft.com>
Co-authored-by: Nico Möller <nkm-moeller@mail.de>
Co-authored-by: Chris Gillum <cgillum@microsoft.com>
Co-authored-by: Giles Odigwe <79032838+giles17@users.noreply.github.com>
Co-authored-by: Phillip Hoff <phillip.hoff@gmail.com>
Co-authored-by: Ege Ozan Özyedek <36128615+egeozanozyedek@users.noreply.github.com>
Co-authored-by: samueljohnsiby <66901393+samueljohnsiby@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
Co-authored-by: Hao Luo <338265+howlowck@users.noreply.github.com>
Co-authored-by: Victor Dibia <chuvidi2003@gmail.com>
Co-authored-by: stephentoub <2642209+stephentoub@users.noreply.github.com>
Co-authored-by: Jacob Viau <javia@microsoft.com>
Co-authored-by: SuperKenVery <39673849+SuperKenVery@users.noreply.github.com>
Co-authored-by: Sunil Dutta <dutta.2003@gmail.com>
Co-authored-by: Sunil Dutta <sunil.dutta@penske.com>
Co-authored-by: budgetboardingai <apurva.sharma31@gmail.com>
Co-authored-by: Syrine Chelly <62653967+SyChell@users.noreply.github.com>
Co-authored-by: SergeyMenshykh <sergemenshikh@gmail.com>
2026-01-02 13:13:37 -08:00
Laveesh RohraandGitHub a02527f00a Python: Add Entity State Providers for DurableTask Package (#2981)
* Add Entity State Providers

* address comments

* Fix tests

* Fix tests

* Revert unrelated changes and remove thread_id

* Revert unrelated files
2025-12-22 12:54:24 -08:00
Laveesh RohraandGitHub 87a38bc7da Python: Rebase durable task feature branch with main (#2806) 2025-12-17 14:02:36 -08:00
Laveesh RohraandGitHub a48a8dd524 Python: Add initial scaffold for durabletask package (#2761)
* Add initial scaffold

* Update design

* Fix mypy and update design

* add additional style considered

* Address comments

* Fix test

* Update readmes
2025-12-17 12:46:08 -08:00
260 changed files with 11585 additions and 14121 deletions
+6 -5
View File
@@ -209,13 +209,14 @@ WARP.md
**/tmpclaude*
# 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
*/__azurite_db_blob__.json*
*/__azurite_db_blob_extent__.json*
*/__azurite_db_queue__.json*
*/__azurite_db_queue_extent__.json*
*/__azurite_db_table__.json*
*/__blobstorage__/
*/__queuestorage__/
*/AzuriteConfig
# Azure Functions local settings
local.settings.json
+5 -5
View File
@@ -112,14 +112,14 @@
<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.19.1" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.19.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.19.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.19.0" />
<PackageVersion Include="Microsoft.DurableTask.Client" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Client.AzureManaged" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker" Version="1.18.0" />
<PackageVersion Include="Microsoft.DurableTask.Worker.AzureManaged" Version="1.18.0" />
<!-- 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.13.1" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" Version="1.11.0" />
<PackageVersion Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" Version="1.0.1" />
<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" />
+1 -11
View File
@@ -34,10 +34,6 @@
<Project Path="samples/AzureFunctions/06_LongRunningTools/06_LongRunningTools.csproj" />
<Project Path="samples/AzureFunctions/07_AgentAsMcpTool/07_AgentAsMcpTool.csproj" />
<Project Path="samples/AzureFunctions/08_ReliableStreaming/08_ReliableStreaming.csproj" />
<Project Path="samples/AzureFunctions/09_Workflow/09_Workflow.csproj" />
<Project Path="samples/AzureFunctions/10_WorkflowConcurrent/10_WorkflowConcurrent.csproj" />
<Project Path="samples/AzureFunctions/11_WorkflowSharedState/11_WorkflowSharedState.csproj" />
<Project Path="samples/AzureFunctions/12_ConditionalEdges/12_ConditionalEdges.csproj" />
</Folder>
<Folder Name="/Samples/DurableAgents/">
<File Path="samples/DurableAgents/ConsoleApps/README.md" />
@@ -50,12 +46,6 @@
<Project Path="samples/DurableAgents/ConsoleApps/05_AgentOrchestration_HITL/05_AgentOrchestration_HITL.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/06_LongRunningTools/06_LongRunningTools.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/07_ReliableStreaming/07_ReliableStreaming.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/08_SingleWorkflow/08_SingleWorkflow.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/09_SubWorkflows/09_SubWorkflows.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/09_Workflow_Concurrency/09_Workflow_Concurrency.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/10_Workflow_HITL/10_Workflow_HITL.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/11_WorkflowEvents/11_WorkflowEvents.csproj" />
<Project Path="samples/DurableAgents/ConsoleApps/12_WorkflowLoop/12_WorkflowLoop.csproj" />
</Folder>
<Folder Name="/Samples/GettingStarted/">
<File Path="samples/GettingStarted/README.md" />
@@ -423,8 +413,8 @@
<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.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows/Microsoft.Agents.AI.Workflows.csproj" />
<Project Path="src/Microsoft.Agents.AI.Workflows.Generators/Microsoft.Agents.AI.Workflows.Generators.csproj" />
<Project Path="src/Microsoft.Agents.AI/Microsoft.Agents.AI.csproj" />
</Folder>
<Folder Name="/Tests/" />
-4
View File
@@ -3,14 +3,10 @@
<packageSources>
<clear />
<add key="nuget.org" value="https://api.nuget.org/v3/index.json" />
<add key="LocalNugetSource" value="C:\LocalNugetSource" />
</packageSources>
<packageSourceMapping>
<packageSource key="nuget.org">
<package pattern="*" />
</packageSource>
<packageSource key="LocalNugetSource">
<package pattern="*" />
</packageSource>
</packageSourceMapping>
</configuration>
@@ -8,6 +8,3 @@ dotnet_diagnostic.DURABLE0003.severity = none
dotnet_diagnostic.DURABLE0004.severity = none
dotnet_diagnostic.DURABLE0005.severity = none
dotnet_diagnostic.DURABLE0006.severity = none
# CA1812: Internal classes are instantiated via dependency injection or reflection in samples
dotnet_diagnostic.CA1812.severity = none
@@ -6,8 +6,8 @@
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- The Functions build tools don't like namespaces that start with a number -->
<AssemblyName>Workflow</AssemblyName>
<RootNamespace>Workflow</RootNamespace>
<AssemblyName>SingleAgent</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
@@ -5,4 +5,4 @@
POST {{authority}}/api/agents/Joker/run
Content-Type: text/plain
Hello world
Tell me a joke about a pirate.
@@ -1,44 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- The Functions build tools don't like namespaces that start with a number -->
<AssemblyName>SingleAgent</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<!-- Azure Functions packages -->
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,67 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Parses an Order ID from a string input and returns an Order object populated.
/// </summary>
internal sealed class OrderLookup() : Executor<string, Order>("OrderLookup")
{
public override async ValueTask<Order> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Populate Order information from OrderId.
return new Order(message, 100.0m);
}
}
/// <summary>
/// Enriches an Order object with additional information.
/// </summary>
internal sealed class OrderEnrich() : Executor<Order, Order>("EnrichOrder")
{
public override async ValueTask<Order> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
if (message.Customer is null)
{
// populate customer information for the order from database.
message.Customer = new Customer(1, "Jerry");
}
return message;
}
}
internal sealed class PaymentProcessor() : Executor<Order, Order>("ProcessPayment")
{
public override async ValueTask<Order> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
message.PaymentReferenceNumber = Guid.NewGuid().ToString()[^4..];
return message;
}
}
internal sealed class OrderCancel() : Executor<Order, string>("OrderCancel")
{
public override async ValueTask<string> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
return $"Order {message.Id} cancelled at {DateTime.UtcNow:g} UTC.";
}
}
internal sealed class Order
{
public Order(string id, decimal amount)
{
this.Id = id;
this.Amount = amount;
}
public string Id { get; }
public decimal Amount { get; }
public Customer? Customer { get; set; }
public string? PaymentReferenceNumber { get; set; }
}
public sealed record Customer(int Id, string Name);
@@ -1,48 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using SingleAgent;
Func<string, string> orderParserFunc = input =>
{
// We accept both short ordereId(Ex:12345) and long order reference number(MSFT12345)
// OrderId is the last 5 digigs of order reference number.
const int OrderIdPartLength = 5;
if (input.Length > OrderIdPartLength)
{
return input[^OrderIdPartLength..];
}
return input;
};
var orderParserExecutor = orderParserFunc.BindAsExecutor("ParseOrderId");
OrderLookup orderLookupExecutor = new();
OrderEnrich orderEnricherExeecutor = new();
PaymentProcessor paymentProcessorExecutor = new();
Workflow fulfillOrder = new WorkflowBuilder(orderParserExecutor)
.WithName("FulfillOrder")
.WithDescription("Looks up an order by ID and run payment processing")
.AddEdge(orderParserExecutor, orderLookupExecutor)
.AddEdge(orderLookupExecutor, orderEnricherExeecutor)
.AddEdge(orderEnricherExeecutor, paymentProcessorExecutor)
.Build();
//OrderCancel orderArchiverExecutor = new();
//Workflow cancelOrder = new WorkflowBuilder(orderParserExecutor)
// .WithName("CancelOrder")
// .WithDescription("Cancel an order")
// .AddEdge(orderParserExecutor, orderLookupExecutor)
// .AddEdge(orderLookupExecutor, orderArchiverExecutor)
// .Build();
var host = FunctionsApplication.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableOptions(options => options.Workflows.AddWorkflow(fulfillOrder))
.Build();
host.Run();
@@ -1,89 +0,0 @@
# Single Agent Sample
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
## Key Concepts Demonstrated
- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
- Registering agents with the Function app and running them using HTTP.
- Conversation management (via session IDs) for isolated interactions.
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
Bash (Linux/macOS/WSL):
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: text/plain" \
-d "Tell me a joke about a pirate."
```
PowerShell:
```powershell
Invoke-RestMethod -Method Post `
-Uri http://localhost:7071/api/agents/Joker/run `
-ContentType text/plain `
-Body "Tell me a joke about a pirate."
```
You can also send JSON requests:
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me a joke about a pirate."}'
```
To continue a conversation, include the `thread_id` in the query string or JSON body:
```bash
curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me another one."}'
```
The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
```text
Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
```
The expected `application/json` output will look something like:
```json
{
"status": 200,
"thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40",
"response": {
"Messages": [
{
"AuthorName": "Joker",
"CreatedAt": "2025-11-11T12:00:00.0000000Z",
"Role": "assistant",
"Contents": [
{
"Type": "text",
"Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
}
]
}
],
"Usage": {
"InputTokenCount": 78,
"OutputTokenCount": 36,
"TotalTokenCount": 114
}
}
}
```
@@ -1,14 +0,0 @@
# Default endpoint address for local testing
@authority=http://localhost:7071
### Look up a long order reference id
POST {{authority}}/api/workflows/FulfillOrder/run
Content-Type: text/plain
QWERTY80853
### Look up a short order id
POST {{authority}}/api/workflows/CancelOrder/run
Content-Type: text/plain
12345
@@ -1,20 +0,0 @@
{
"version": "2.0",
"logging": {
"logLevel": {
"Microsoft.Agents.AI.DurableTask": "Information",
"Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
"DurableTask": "Information",
"Microsoft.DurableTask": "Information"
}
},
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "AzureManaged",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
}
}
@@ -1,48 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- The Functions build tools don't like namespaces that start with a number -->
<AssemblyName>SingleAgent</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<None Include="local.settings.json" />
</ItemGroup>
<!-- Azure Functions packages -->
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,38 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
internal sealed class ConcurrentStartExecutor() : Executor<string, string>("ConcurrentStartExecutor")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// do some initial parsing and validation of the message.
// Return a polished version ith additional metadta.
if (!message.StartsWith("Query for the agent:", StringComparison.OrdinalIgnoreCase))
{
message = "Query for the agent: " + message;
}
return ValueTask.FromResult(message);
}
}
internal sealed class ResultAggregationExecutor() : Executor<string[], string>("ResultAggregationExecutor")
{
/// <summary>
/// Handles incoming messages from the agents and aggregates their responses.
/// </summary>
/// <param name="message">The messages from the parallel agents.</param>
/// <param name="context">Workflow context for accessing workflow services and adding events.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public override ValueTask<string> HandleAsync(string[] message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Aggregate all responses from parallel executors
string aggregatedResponse = string.Join("\n---\n", message);
return ValueTask.FromResult($"Aggregated {message.Length} responses:\n{aggregatedResponse}");
}
}
@@ -1,48 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Azure;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.Hosting;
using OpenAI.Chat;
using SingleAgent;
// Get the Azure OpenAI endpoint and deployment name from environment variables.
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT is not set.");
// Use Azure Key Credential if provided, otherwise use Azure CLI Credential.
string? azureOpenAiKey = System.Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
AzureOpenAIClient client = !string.IsNullOrEmpty(azureOpenAiKey)
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
AIAgent physicist = client.GetChatClient(deploymentName).AsAIAgent("You are an expert in physics. You answer questions from a physics perspective.", "Physicist");
AIAgent chemist = client.GetChatClient(deploymentName).AsAIAgent("You are an expert in chemistry. You answer questions from a chemistry perspective.", "Chemist");
var startExecutor = new ConcurrentStartExecutor();
var aggregationExecutor = new ResultAggregationExecutor();
var workflow = new WorkflowBuilder(startExecutor)
.WithName("ExpertReview")
.AddFanOutEdge(startExecutor, [physicist, chemist])
.AddFanInEdge([physicist, chemist], aggregationExecutor)
.Build();
var host = FunctionsApplication.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableOptions(options =>
{
// Configure workflows
options.Workflows.AddWorkflow(workflow);
})
.Build();
host.Run();
@@ -1,89 +0,0 @@
# Single Agent Sample
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
## Key Concepts Demonstrated
- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
- Registering agents with the Function app and running them using HTTP.
- Conversation management (via session IDs) for isolated interactions.
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
Bash (Linux/macOS/WSL):
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: text/plain" \
-d "Tell me a joke about a pirate."
```
PowerShell:
```powershell
Invoke-RestMethod -Method Post `
-Uri http://localhost:7071/api/agents/Joker/run `
-ContentType text/plain `
-Body "Tell me a joke about a pirate."
```
You can also send JSON requests:
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me a joke about a pirate."}'
```
To continue a conversation, include the `thread_id` in the query string or JSON body:
```bash
curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me another one."}'
```
The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
```text
Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
```
The expected `application/json` output will look something like:
```json
{
"status": 200,
"thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40",
"response": {
"Messages": [
{
"AuthorName": "Joker",
"CreatedAt": "2025-11-11T12:00:00.0000000Z",
"Role": "assistant",
"Contents": [
{
"Type": "text",
"Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
}
]
}
],
"Usage": {
"InputTokenCount": 78,
"OutputTokenCount": 36,
"TotalTokenCount": 114
}
}
}
```
@@ -1,27 +0,0 @@
//// Copyright (c) Microsoft. All rights reserved.
//using Microsoft.Agents.AI.Workflows;
//namespace SingleAgent;
///// <summary>
///// Routes survey responses to appropriate teams based on rating and category.
///// </summary>
//public sealed class ResponseRouterExecutor() : Executor<string, string>("ResponseRouterExecutor")
//{
// public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
// {
// if (message.Contains("billing", StringComparison.OrdinalIgnoreCase))
// {
// return ValueTask.FromResult("Routed to Billing Team");
// }
// else if (message.Contains("technical", StringComparison.OrdinalIgnoreCase))
// {
// return ValueTask.FromResult("Routed to Technical Support Team");
// }
// else
// {
// return ValueTask.FromResult("Routed to General Support Team");
// }
// }
//}
@@ -1,82 +0,0 @@
//// Copyright (c) Microsoft. All rights reserved.
//using System.Text.Json;
//using System.Text.RegularExpressions;
//using Microsoft.Agents.AI.Workflows;
//namespace SingleAgent;
///// <summary>
///// This executor parses survey responses and produces structured output.
///// Example input: "Rating: 8. The app is good but checkout process is confusing."
///// </summary>
//[System.Diagnostics.CodeAnalysis.SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Instantiated by workflow framework")]
//internal sealed partial class SurveyResponseParserExecutor() : Executor<string, string>("SurveyResponseParserExecutor")
//{
// private static readonly JsonSerializerOptions s_jsonOptions = new()
// {
// WriteIndented = true
// };
// [GeneratedRegex(@"Rating:\s*(\d+)", RegexOptions.IgnoreCase)]
// private static partial Regex RatingRegex();
// public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
// {
// SurveyResponse response = this.ParseSurveyResponse(message);
// string jsonResult = JsonSerializer.Serialize(response, s_jsonOptions);
// return ValueTask.FromResult(jsonResult);
// }
// private SurveyResponse ParseSurveyResponse(string message)
// {
// // Parse the message to extract rating and comment
// int? rating = null;
// string comment = message;
// // Try to extract rating using pattern "Rating: {number}"
// Match ratingMatch = RatingRegex().Match(message);
// if (ratingMatch.Success && int.TryParse(ratingMatch.Groups[1].Value, out int parsedRating))
// {
// rating = parsedRating;
// // Remove the rating part from the message to get the comment
// // Find the position after the rating number
// int ratingEndIndex = ratingMatch.Index + ratingMatch.Length;
// // Skip any separators (period, comma, dash, etc.) and whitespace
// while (ratingEndIndex < message.Length &&
// (char.IsWhiteSpace(message[ratingEndIndex]) ||
// message[ratingEndIndex] == '.' ||
// message[ratingEndIndex] == ',' ||
// message[ratingEndIndex] == '-'))
// {
// ratingEndIndex++;
// }
// if (ratingEndIndex < message.Length)
// {
// comment = message[ratingEndIndex..].Trim();
// }
// else
// {
// comment = string.Empty;
// }
// }
// // Create and return the structured response
// return new SurveyResponse
// {
// Rating = rating,
// Comment = comment,
// OriginalMessage = message
// };
// }
// private sealed class SurveyResponse
// {
// public int? Rating { get; set; }
// public string Comment { get; set; } = string.Empty;
// public string OriginalMessage { get; set; } = string.Empty;
// }
//}
@@ -1,8 +0,0 @@
# Default endpoint address for local testing
@authority=http://localhost:7071
### Start the workflow
POST {{authority}}/api/workflows/ExpertReview/run
Content-Type: text/plain
What is temperature?
@@ -1,20 +0,0 @@
{
"version": "2.0",
"logging": {
"logLevel": {
"Microsoft.Agents.AI.DurableTask": "Information",
"Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
"DurableTask": "Information",
"Microsoft.DurableTask": "Information"
}
},
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "AzureManaged",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
}
}
@@ -1,48 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- The Functions build tools don't like namespaces that start with a number -->
<AssemblyName>SingleAgent</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<None Include="local.settings.json" />
</ItemGroup>
<!-- Azure Functions packages -->
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,87 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use durable state management in Azure Functions workflows.
// The OrderIdParserExecutor writes a value to shared state, and the EmailSenderExecutor reads it back.
// The state is persisted durably using Durable Entities behind the scenes.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Constants for shared state scopes used across executors.
/// </summary>
internal static class SharedStateConstants
{
public const string MessageScope = "MessageState";
public const string ProcessedMessageKey = "ProcessedMessage";
}
public sealed class Order
{
public Order(string id, decimal amount)
{
this.Id = id;
this.Amount = amount;
}
public string Id { get; }
public decimal Amount { get; }
public string? PaymentReferenceNumber { get; set; }
}
/// <summary>
/// First executor that processes a message and stores the result in shared state.
/// </summary>
internal sealed class OrderIdParserExecutor() : Executor<string, Order>("OrderIdParserExecutor")
{
public override async ValueTask<Order> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Process the message
string processedMessage = $"Processed: {message}";
// Store the processed message in shared state for the next executor
await context.QueueStateUpdateAsync(
SharedStateConstants.ProcessedMessageKey,
processedMessage,
SharedStateConstants.MessageScope,
cancellationToken);
return GetOrder(message);
}
private static Order GetOrder(string id)
{
// Simulate fetching order details
return new Order(id, 100.0m);
}
}
/// <summary>
/// Second executor that reads the shared state and appends to the message.
/// </summary>
internal sealed class EmailSenderExecutor() : Executor<Order, string>("EmailSenderExecutor")
{
public override async ValueTask<string> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Read the processed message from shared state (written by OrderIdParserExecutor)
string? storedMessage = await context.ReadStateAsync<string>(
SharedStateConstants.ProcessedMessageKey,
SharedStateConstants.MessageScope,
cancellationToken);
// Combine with the input message
return storedMessage is not null
? $"From state: [{storedMessage}] | Input: [{message.Id}]"
: $"No state found | Input: [{message.Id}]";
}
}
internal sealed class PaymentProcesserExecutor() : Executor<Order, Order>("PaymentProcesserExecutor")
{
public override async ValueTask<Order> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Call payment gateway.
message.PaymentReferenceNumber = Guid.NewGuid().ToString().Substring(0, 4);
return message;
}
}
@@ -1,23 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using SingleAgent;
// Set up an AI agent following the standard Microsoft Agent Framework pattern.
OrderIdParserExecutor orderParser = new();
PaymentProcesserExecutor paymentProcessor = new();
EmailSenderExecutor emailSender = new();
WorkflowBuilder builder = new(orderParser);
builder.AddEdge(orderParser, paymentProcessor);
builder.AddEdge(paymentProcessor, emailSender).WithOutputFrom(emailSender);
var workflow = builder.WithName("ProcessOrder").Build();
FunctionsApplication.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableOptions(options => options.Workflows.AddWorkflow(workflow))
.Build().Run();
@@ -1,89 +0,0 @@
# Single Agent Sample
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
## Key Concepts Demonstrated
- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
- Registering agents with the Function app and running them using HTTP.
- Conversation management (via session IDs) for isolated interactions.
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
Bash (Linux/macOS/WSL):
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: text/plain" \
-d "Tell me a joke about a pirate."
```
PowerShell:
```powershell
Invoke-RestMethod -Method Post `
-Uri http://localhost:7071/api/agents/Joker/run `
-ContentType text/plain `
-Body "Tell me a joke about a pirate."
```
You can also send JSON requests:
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me a joke about a pirate."}'
```
To continue a conversation, include the `thread_id` in the query string or JSON body:
```bash
curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me another one."}'
```
The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
```text
Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
```
The expected `application/json` output will look something like:
```json
{
"status": 200,
"thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40",
"response": {
"Messages": [
{
"AuthorName": "Joker",
"CreatedAt": "2025-11-11T12:00:00.0000000Z",
"Role": "assistant",
"Contents": [
{
"Type": "text",
"Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
}
]
}
],
"Usage": {
"InputTokenCount": 78,
"OutputTokenCount": 36,
"TotalTokenCount": 114
}
}
}
```
@@ -1,14 +0,0 @@
# Default endpoint address for local testing
@authority=http://localhost:7071
### Start the workflow
POST {{authority}}/api/workflows/ProcessOrder/run
Content-Type: text/plain
123
### Start second workflow
POST {{authority}}/api/workflows/ProcessOrder/run
Content-Type: text/plain
456
@@ -1,20 +0,0 @@
{
"version": "2.0",
"logging": {
"logLevel": {
"Microsoft.Agents.AI.DurableTask": "Information",
"Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
"DurableTask": "Information",
"Microsoft.DurableTask": "Information"
}
},
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "AzureManaged",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
}
}
@@ -1,51 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<AzureFunctionsVersion>v4</AzureFunctionsVersion>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<!-- The Functions build tools don't like namespaces that start with a number -->
<AssemblyName>SingleAgent</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App" />
</ItemGroup>
<ItemGroup>
<None Include="local.settings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
</None>
</ItemGroup>
<!-- Azure Functions packages -->
<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.DurableTask.AzureManaged" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http.AspNetCore" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Sdk" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Azure.AI.OpenAI" />
<PackageReference Include="Azure.Identity" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.Hosting.AzureFunctions" />
<PackageReference Include="Microsoft.Agents.AI.OpenAI" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,98 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use durable state management in Azure Functions workflows.
// The OrderIdParserExecutor writes a value to shared state, and the FraudValidation reads it back.
// The state is persisted durably using Durable Entities behind the scenes.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Constants for shared state scopes used across executors.
/// </summary>
internal static class SharedStateConstants
{
public const string MessageScope = "MessageState";
public const string ProcessedMessageKey = "ProcessedMessage";
}
internal sealed class Order
{
public Order(string id, decimal amount)
{
this.Id = id;
this.Amount = amount;
}
public string Id { get; }
public decimal Amount { get; }
public Customer? Customer { get; set; }
public string? PaymentReferenceNumber { get; set; }
}
public sealed record Customer(int Id, string Name, bool IsBlocked);
internal sealed class OrderIdParserExecutor() : Executor<string, Order>("OrderIdParserExecutor")
{
public override async ValueTask<Order> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
return GetOrder(message);
}
private static Order GetOrder(string id)
{
// Simulate fetching order details
return new Order(id, 100.0m);
}
}
internal sealed class OrderEnrich() : Executor<Order, Order>("EnrichOrder")
{
public override async ValueTask<Order> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
message.Customer = GetCustomerForOrder(message.Id);
return message;
}
private static Customer GetCustomerForOrder(string orderId)
{
if (orderId.Contains('B'))
{
return new Customer(101, "George", true);
}
return new Customer(201, "Jerry", false);
}
}
internal sealed class PaymentProcesserExecutor() : Executor<Order, Order>("PaymentProcesserExecutor")
{
public override async ValueTask<Order> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Call payment gateway.
message.PaymentReferenceNumber = Guid.NewGuid().ToString().Substring(0, 4);
return message;
}
}
internal sealed class NotifyFraudExecutor() : Executor<Order, string>("NotifyFraud")
{
public override async ValueTask<string> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Notify fraud team.
return $"Order {message.Id} flagged as fraudulent for customer {message.Customer?.Name}.";
}
}
internal static class OrderRouteConditions
{
/// <summary>
/// Returns a condition that evaluates to true when the customer is blocked.
/// </summary>
internal static Func<Order?, bool> WhenBlocked() => order => order?.Customer?.IsBlocked == true;
/// <summary>
/// Returns a condition that evaluates to true when the customer is not blocked.
/// </summary>
internal static Func<Order?, bool> WhenNotBlocked() => order => order?.Customer?.IsBlocked == false;
}
@@ -1,26 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Hosting.AzureFunctions;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Extensions.Hosting;
using SingleAgent;
OrderIdParserExecutor orderParser = new();
OrderEnrich orderEnrich = new();
PaymentProcesserExecutor paymentProcessor = new();
NotifyFraudExecutor notifyFraud = new();
WorkflowBuilder builder = new(orderParser);
builder
.AddEdge(orderParser, orderEnrich)
.AddEdge(orderEnrich, notifyFraud, condition: OrderRouteConditions.WhenBlocked())
.AddEdge(orderEnrich, paymentProcessor, condition: OrderRouteConditions.WhenNotBlocked());
var workflow = builder.WithName("AuditOrder").Build();
FunctionsApplication.CreateBuilder(args)
.ConfigureFunctionsWebApplication()
.ConfigureDurableOptions(options => options.Workflows.AddWorkflow(workflow))
.Build()
.Run();
@@ -1,89 +0,0 @@
# Single Agent Sample
This sample demonstrates how to use the Durable Agent Framework (DAFx) to create a simple Azure Functions app that hosts a single AI agent and provides direct HTTP API access for interactive conversations.
## Key Concepts Demonstrated
- Using the Microsoft Agent Framework to define a simple AI agent with a name and instructions.
- Registering agents with the Function app and running them using HTTP.
- Conversation management (via session IDs) for isolated interactions.
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup and function app running, you can test the sample by sending an HTTP request to the agent endpoint.
You can use the `demo.http` file to send a message to the agent, or a command line tool like `curl` as shown below:
Bash (Linux/macOS/WSL):
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: text/plain" \
-d "Tell me a joke about a pirate."
```
PowerShell:
```powershell
Invoke-RestMethod -Method Post `
-Uri http://localhost:7071/api/agents/Joker/run `
-ContentType text/plain `
-Body "Tell me a joke about a pirate."
```
You can also send JSON requests:
```bash
curl -X POST http://localhost:7071/api/agents/Joker/run \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me a joke about a pirate."}'
```
To continue a conversation, include the `thread_id` in the query string or JSON body:
```bash
curl -X POST "http://localhost:7071/api/agents/Joker/run?thread_id=your-thread-id" \
-H "Content-Type: application/json" \
-H "Accept: application/json" \
-d '{"message": "Tell me another one."}'
```
The response from the agent will be displayed in the terminal where you ran `func start`. The expected `text/plain` output will look something like:
```text
Why don't pirates ever learn the alphabet? Because they always get stuck at "C"!
```
The expected `application/json` output will look something like:
```json
{
"status": 200,
"thread_id": "ee6e47a0-f24b-40b1-ade8-16fcebb9eb40",
"response": {
"Messages": [
{
"AuthorName": "Joker",
"CreatedAt": "2025-11-11T12:00:00.0000000Z",
"Role": "assistant",
"Contents": [
{
"Type": "text",
"Text": "Why don't pirates ever learn the alphabet? Because they always get stuck at 'C'!"
}
]
}
],
"Usage": {
"InputTokenCount": 78,
"OutputTokenCount": 36,
"TotalTokenCount": 114
}
}
}
```
@@ -1,14 +0,0 @@
# Default endpoint address for local testing
@authority=http://localhost:7071
### Start the workflow
POST {{authority}}/api/workflows/ProcessOrder/run
Content-Type: text/plain
B123
### Start second workflow
POST {{authority}}/api/workflows/ProcessOrder/run
Content-Type: text/plain
456
@@ -1,20 +0,0 @@
{
"version": "2.0",
"logging": {
"logLevel": {
"Microsoft.Agents.AI.DurableTask": "Information",
"Microsoft.Agents.AI.Hosting.AzureFunctions": "Information",
"DurableTask": "Information",
"Microsoft.DurableTask": "Information"
}
},
"extensions": {
"durableTask": {
"hubName": "default",
"storageProvider": {
"type": "AzureManaged",
"connectionStringName": "DURABLE_TASK_SCHEDULER_CONNECTION_STRING"
}
}
}
}
@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SingleWorkflow</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,128 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Represents an order in the system.
/// </summary>
internal sealed class Order
{
public required string Id { get; set; }
public DateTime OrderDate { get; set; }
public bool IsCancelled { get; set; }
public required Customer Customer { get; set; }
}
/// <summary>
/// Represents a customer associated with an order.
/// </summary>
internal sealed class Customer
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
/// <summary>
/// Looks up an order by its ID.
/// This activity simulates a database lookup with a 2 second delay.
/// </summary>
internal sealed class OrderLookup() : Executor<string, Order>("OrderLookup")
{
public override async ValueTask<Order> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Log that this activity is executing (not replaying)
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] OrderLookup: Starting lookup for order '{message}'");
Console.ResetColor();
// Simulate database lookup with delay
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
Order order = new()
{
Id = message,
OrderDate = DateTime.UtcNow.AddDays(-1),
IsCancelled = false,
Customer = new Customer { Name = "Jerry", Email = "jerry@example.com" }
};
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($"│ [Activity] OrderLookup: Found order '{message}' for customer '{order.Customer.Name}'");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();
return order;
}
}
/// <summary>
/// Cancels an order.
/// This activity simulates a slow cancellation process with a 5 second delay.
/// Try pressing Ctrl+C during this activity to see durability in action!
/// </summary>
internal sealed class OrderCancel() : Executor<Order, Order>("OrderCancel")
{
public override async ValueTask<Order> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Log that this activity is executing (not replaying)
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] OrderCancel: Starting cancellation for order '{message.Id}'");
Console.WriteLine("│ [Activity] OrderCancel: ⚠️ This takes 5 seconds - try Ctrl+C!");
Console.ResetColor();
// Simulate a slow cancellation process (e.g., calling external payment system)
// This is where you can kill the process to test durability
for (int i = 1; i <= 10; i++)
{
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($"│ [Activity] OrderCancel: Processing... {i}/10 seconds");
Console.ResetColor();
}
// Mark the order as cancelled
message.IsCancelled = true;
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($"│ [Activity] OrderCancel: ✓ Order '{message.Id}' has been cancelled");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();
return message;
}
}
/// <summary>
/// Sends a cancellation confirmation email to the customer.
/// This activity simulates sending an email with a 1 second delay.
/// </summary>
internal sealed class SendEmail() : Executor<Order, string>("SendEmail")
{
public override async ValueTask<string> HandleAsync(Order message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Log that this activity is executing (not replaying)
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Activity] SendEmail: Sending email to '{message.Customer.Email}'...");
Console.ResetColor();
// Simulate email sending delay
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
string result = $"Cancellation email sent to {message.Customer.Email} for order {message.Id}.";
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("│ [Activity] SendEmail: ✓ Email sent successfully!");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();
return result;
}
}
@@ -1,108 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to run a workflow as a durable orchestration from a console application.
// The workflow consists of three executors: OrderLookup -> OrderCancel -> SendEmail.
// It uses the DurableExecutionEnvironment which is injected via DI.
//
// DURABILITY DEMONSTRATION:
// - Each activity has artificial delays to simulate real-world operations
// - Stop the app (Ctrl+C or stop debugging) during the OrderCancel activity (5 seconds)
// - Restart the application - the workflow will automatically resume!
// - The Durable Task Framework will skip already-completed activities (OrderLookup)
// and continue from where it left off (OrderCancel)
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SingleAgent;
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Define executors for the workflow
OrderLookup orderLookup = new();
OrderCancel orderCancel = new();
SendEmail sendEmail = new();
// Build the CancelOrder workflow: OrderLookup -> OrderCancel -> SendEmail
Workflow cancelOrder = new WorkflowBuilder(orderLookup)
.WithName("CancelOrder")
.WithDescription("Cancel an order and notify the customer")
.AddEdge(orderLookup, orderCancel)
.AddEdge(orderCancel, sendEmail)
.Build();
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(cancelOrder),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
Console.WriteLine("Durable Workflow Sample");
Console.WriteLine("Workflow: OrderLookup (2s) -> OrderCancel (5s) -> SendEmail (1s)");
Console.WriteLine();
Console.WriteLine("TIP: Stop the app during OrderCancel to test durability.");
Console.WriteLine(" Restart - it will resume from where it left off.");
Console.WriteLine();
Console.WriteLine("Checking for pending workflows...");
await Task.Delay(TimeSpan.FromSeconds(2));
Console.WriteLine();
Console.WriteLine("Enter an order ID (or 'exit'):");
while (true)
{
Console.Write("> ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
try
{
await StartNewWorkflowAsync(input, cancelOrder, workflowClient);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
}
await host.StopAsync();
// Start a new workflow using IWorkflowClient (no DurableTaskClient needed)
async Task StartNewWorkflowAsync(string orderId, Workflow workflow, IWorkflowClient client)
{
Console.WriteLine($"Starting workflow for order '{orderId}'...");
// RunAsync returns IRun, cast to DurableRun for durable-specific features like WaitForCompletionAsync
await using DurableRun run = (DurableRun)await client.RunAsync(workflow, orderId);
Console.WriteLine($"Instance ID: {run.InstanceId}");
try
{
string? result = await run.WaitForCompletionAsync();
Console.WriteLine($"Completed: {result}");
}
catch (InvalidOperationException ex)
{
Console.WriteLine($"Failed: {ex.Message}");
}
}
@@ -1,156 +0,0 @@
# Single Workflow Console Sample
This sample demonstrates how to run a workflow as a durable orchestration from a console application using the Durable Task Framework. It showcases the **durability** aspect - if the process crashes mid-execution, the workflow can be resumed without re-executing completed activities.
## Overview
The sample implements an order cancellation workflow with three executors, each with artificial delays to simulate real-world operations:
1. **OrderLookup** (2 seconds) - Looks up an order by its ID
2. **OrderCancel** (5 seconds) - Marks the order as cancelled
3. **SendEmail** (1 second) - Sends a cancellation confirmation email
## Durability Demonstration
The key feature of Durable Task Framework is **durability**:
- **Activity results are persisted**: When an activity completes, its result is saved
- **Orchestrations are replayed**: On restart, the orchestration replays from the beginning
- **Completed activities are skipped**: The framework uses cached results for completed activities
- **Failed activities are retried**: If an activity was interrupted, it runs again
- **Automatic resume**: When the worker starts, it automatically picks up any pending work!
### Try It Yourself
1. Start the application and enter an order ID (e.g., `12345`)
2. Stop the app (Ctrl+C or stop debugging) during the `OrderCancel` activity (5 seconds)
3. Restart the application
4. **Watch for automatic resume!** The worker automatically picks up the interrupted workflow
5. Observe that `OrderLookup` is NOT re-executed (its result was cached)
6. `OrderCancel` restarts from the beginning (it didn't complete)
7. `SendEmail` runs after `OrderCancel` completes
The durability is completely automatic - no manual intervention needed!
## Workflow Flow
```
User Input (Order ID)
?
?
???????????????????
? OrderLookup ? ? 2 second delay (database lookup)
? (2 seconds) ?
???????????????????
?
?
???????????????????
? OrderCancel ? ? 5 second delay - TRY INTERRUPTING HERE!
? (5 seconds) ?
???????????????????
?
?
???????????????????
? SendEmail ? ? 1 second delay (email sending)
? (1 second) ?
???????????????????
?
?
Result
```
## Key Concepts Demonstrated
- **ConfigureDurableWorkflows** - Simplified API for registering workflows
- **DurableExecution.RunAsync** - Start a new workflow (similar to InProcessExecution)
- **DurableRun** - Handle to monitor and interact with a running workflow
- **Automatic Resume** - Interrupted workflows continue automatically on restart
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup, you can run the sample:
```bash
cd dotnet/samples/DurableAgents/ConsoleApps/08_SingleWorkflow
dotnet run --framework net10.0
```
### Sample Session
```text
??????????????????????????????????????????????????????????????????????
? Durable Workflow Console Sample ?
??????????????????????????????????????????????????????????????????????
? This sample demonstrates durability in workflows. ?
? Workflow: OrderLookup (2s) -> OrderCancel (5s) -> SendEmail (1s) ?
??????????????????????????????????????????????????????????????????????
?? TIP: Stop the app during OrderCancel (5 seconds) to test durability!
Restart the app - it will automatically resume from where it left off.
? Checking for pending workflows...
Enter an order ID to start a new workflow (or 'exit' to quit):
Order ID: 12345
Starting workflow for order '12345'...
Instance ID: abc123-def456-...
???????????????????????????????????????????????????????????????????
? [Activity] OrderLookup: Starting lookup for order '12345'
? [Activity] OrderLookup: Found order '12345' for customer 'Jerry'
???????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????????
? [Activity] OrderCancel: Starting cancellation for order '12345'
? [Activity] OrderCancel: ?? This takes 5 seconds - try Ctrl+C!
? [Activity] OrderCancel: Processing... 1/5 seconds
? [Activity] OrderCancel: Processing... 2/5 seconds
^C <-- User stops the app here
[After restart...]
? Checking for pending workflows...
???????????????????????????????????????????????????????????????????
? [Activity] OrderCancel: Starting cancellation for order '12345' <-- Auto-resumed!
? [Activity] OrderCancel: ?? This takes 5 seconds - try Ctrl+C!
? [Activity] OrderCancel: Processing... 1/5 seconds
...
? [Activity] OrderCancel: ? Order '12345' has been cancelled
???????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????????
? [Activity] SendEmail: Sending email to 'jerry@example.com'...
? [Activity] SendEmail: ? Email sent successfully!
???????????????????????????????????????????????????????????????????
Enter an order ID to start a new workflow (or 'exit' to quit):
Order ID: _
```
Notice that when resumed:
- `OrderLookup` was **NOT re-executed** (result was cached by Durable Task)
- `OrderCancel` **restarted automatically** (it was interrupted before completing)
- `SendEmail` ran normally after `OrderCancel` completed
## Viewing Workflow State
You can view the state of the workflow in the Durable Task Scheduler dashboard:
1. Open your browser and navigate to `http://localhost:8082`
2. In the dashboard, you can view the state of the orchestration, including activity history and current state
## Related Samples
- [01_SingleAgent](../01_SingleAgent) - Single agent console sample
- [02_AgentOrchestration_Chaining](../02_AgentOrchestration_Chaining) - Agent chaining with durable orchestration
- [05_AgentOrchestration_HITL](../05_AgentOrchestration_HITL) - Human-in-the-loop orchestration
- [09_Workflow](../../AzureFunctions/09_Workflow) - Azure Functions version of workflow hosting
@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SubWorkflows</AssemblyName>
<RootNamespace>SubWorkflows</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,266 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SubWorkflows;
// ============================================
// Order Processing Models
// ============================================
/// <summary>
/// Represents an order being processed.
/// </summary>
internal sealed class OrderInfo
{
public required string OrderId { get; set; }
public decimal Amount { get; set; }
public string? PaymentTransactionId { get; set; }
public string? InventoryReservationId { get; set; }
public string? TrackingNumber { get; set; }
public string? Carrier { get; set; }
}
// ============================================
// Main Workflow Executors
// ============================================
/// <summary>
/// Entry point executor that receives the order ID and creates an OrderInfo object.
/// </summary>
internal sealed class OrderReceived() : Executor<string, OrderInfo>("OrderReceived")
{
public override ValueTask<OrderInfo> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine($"[OrderReceived] Processing order '{message}'");
Console.ResetColor();
OrderInfo order = new()
{
OrderId = message,
Amount = 99.99m // Simulated order amount
};
return ValueTask.FromResult(order);
}
}
/// <summary>
/// Final executor that outputs the completed order summary.
/// </summary>
internal sealed class OrderCompleted() : Executor<OrderInfo, string>("OrderCompleted")
{
public override ValueTask<string> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [OrderCompleted] Order '{message.OrderId}' successfully processed!");
Console.WriteLine($"│ Payment: {message.PaymentTransactionId}");
Console.WriteLine($"│ Inventory: {message.InventoryReservationId}");
Console.WriteLine($"│ Shipping: {message.Carrier} - {message.TrackingNumber}");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();
return ValueTask.FromResult($"Order {message.OrderId} completed. Tracking: {message.TrackingNumber}");
}
}
// ============================================
// Payment Sub-Workflow Executors
// ============================================
/// <summary>
/// Validates payment information for an order.
/// </summary>
internal sealed class ValidatePayment() : Executor<OrderInfo, OrderInfo>("ValidatePayment")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" [Payment/ValidatePayment] Validating payment for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" [Payment/ValidatePayment] Payment validated for ${message.Amount}");
Console.ResetColor();
return message;
}
}
// ============================================
// Fraud Check Sub-Sub-Workflow Executors (Level 2 nesting)
// ============================================
/// <summary>
/// Analyzes transaction patterns for potential fraud.
/// </summary>
internal sealed class AnalyzePatterns() : Executor<OrderInfo, OrderInfo>("AnalyzePatterns")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($" [Payment/FraudCheck/AnalyzePatterns] Analyzing patterns for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine(" [Payment/FraudCheck/AnalyzePatterns] ✓ Pattern analysis complete");
Console.ResetColor();
return message;
}
}
/// <summary>
/// Calculates a risk score for the transaction.
/// </summary>
internal sealed class CalculateRiskScore() : Executor<OrderInfo, OrderInfo>("CalculateRiskScore")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($" [Payment/FraudCheck/CalculateRiskScore] Calculating risk score for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
int riskScore = new Random().Next(1, 100);
Console.ForegroundColor = ConsoleColor.DarkYellow;
Console.WriteLine($" [Payment/FraudCheck/CalculateRiskScore] ✓ Risk score: {riskScore}/100 (Low risk)");
Console.ResetColor();
return message;
}
}
/// <summary>
/// Charges the payment for an order.
/// </summary>
internal sealed class ChargePayment() : Executor<OrderInfo, OrderInfo>("ChargePayment")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" [Payment/ChargePayment] Charging ${message.Amount} for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
message.PaymentTransactionId = $"TXN-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
Console.ForegroundColor = ConsoleColor.Yellow;
Console.WriteLine($" [Payment/ChargePayment] ✓ Payment processed: {message.PaymentTransactionId}");
Console.ResetColor();
return message;
}
}
// ============================================
// Inventory Sub-Workflow Executors
// ============================================
/// <summary>
/// Checks inventory availability for an order.
/// </summary>
internal sealed class CheckInventory() : Executor<OrderInfo, OrderInfo>("CheckInventory")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($" [Inventory/CheckInventory] Checking inventory for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine(" [Inventory/CheckInventory] ✓ Items available in stock");
Console.ResetColor();
return message;
}
}
/// <summary>
/// Reserves inventory for an order.
/// </summary>
internal sealed class ReserveInventory() : Executor<OrderInfo, OrderInfo>("ReserveInventory")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($" [Inventory/ReserveInventory] Reserving items for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
message.InventoryReservationId = $"RES-{Guid.NewGuid().ToString("N")[..8].ToUpperInvariant()}";
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine($" [Inventory/ReserveInventory] ✓ Reserved: {message.InventoryReservationId}");
Console.ResetColor();
return message;
}
}
// ============================================
// Shipping Sub-Workflow Executors
// ============================================
/// <summary>
/// Selects a shipping carrier for an order.
/// </summary>
internal sealed class SelectCarrier() : Executor<OrderInfo, OrderInfo>("SelectCarrier")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($" [Shipping/SelectCarrier] Selecting carrier for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
message.Carrier = message.Amount > 50 ? "Express" : "Standard";
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($" [Shipping/SelectCarrier] ✓ Selected carrier: {message.Carrier}");
Console.ResetColor();
return message;
}
}
/// <summary>
/// Creates shipment and generates tracking number.
/// </summary>
internal sealed class CreateShipment() : Executor<OrderInfo, OrderInfo>("CreateShipment")
{
public override async ValueTask<OrderInfo> HandleAsync(OrderInfo message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($" [Shipping/CreateShipment] Creating shipment for order '{message.OrderId}'...");
Console.ResetColor();
await Task.Delay(TimeSpan.FromSeconds(2), cancellationToken);
message.TrackingNumber = $"TRACK-{Guid.NewGuid().ToString("N")[..10].ToUpperInvariant()}";
Console.ForegroundColor = ConsoleColor.Blue;
Console.WriteLine($" [Shipping/CreateShipment] ✓ Shipment created: {message.TrackingNumber}");
Console.ResetColor();
return message;
}
}
@@ -1,197 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use sub-workflows within a durable orchestration.
// Sub-workflows allow you to compose complex workflows from simpler, reusable components.
//
// The sample implements an order processing workflow with three sub-workflows:
// 1. PaymentProcessing - Validates and processes payment
// - Contains a nested FraudCheck sub-workflow (Level 2 nesting)
// 2. InventoryManagement - Checks and reserves inventory
// 3. ShippingArrangement - Arranges shipping and generates tracking
//
// The FraudCheck sub-workflow demonstrates Level 2 nesting (sub-workflow within sub-workflow):
// - AnalyzePatterns - Analyzes transaction patterns
// - CalculateRiskScore - Calculates fraud risk score
//
// Each sub-workflow runs as a separate orchestration instance, visible in the DTS dashboard.
// This provides:
// - Modular, reusable workflow components
// - Independent checkpointing and replay
// - Hierarchical visualization in the dashboard (including nested sub-workflows)
// - Failure isolation between parent and child workflows
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SubWorkflows;
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// ============================================
// Step 1: Build the Fraud Check sub-sub-workflow (Level 2 nesting)
// ============================================
AnalyzePatterns analyzePatterns = new();
CalculateRiskScore calculateRiskScore = new();
Workflow fraudCheckWorkflow = new WorkflowBuilder(analyzePatterns)
.WithName("SubFraudCheck")
.WithDescription("Analyzes transaction patterns and calculates risk score")
.AddEdge(analyzePatterns, calculateRiskScore)
.Build();
// ============================================
// Step 2: Build the Payment Processing sub-workflow (now with nested sub-workflow)
// ============================================
ValidatePayment validatePayment = new();
ExecutorBinding fraudCheckExecutor = fraudCheckWorkflow.BindAsExecutor("FraudCheck");
ChargePayment chargePayment = new();
Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
.WithName("SubPaymentProcessing")
.WithDescription("Validates and processes payment for an order")
.AddEdge(validatePayment, fraudCheckExecutor)
.AddEdge(fraudCheckExecutor, chargePayment)
.Build();
// ============================================
// Step 3: Build the Inventory Management sub-workflow
// ============================================
CheckInventory checkInventory = new();
ReserveInventory reserveInventory = new();
Workflow inventoryWorkflow = new WorkflowBuilder(checkInventory)
.WithName("SubInventoryManagement")
.WithDescription("Checks availability and reserves inventory")
.AddEdge(checkInventory, reserveInventory)
.Build();
// ============================================
// Step 4: Build the Shipping Arrangement sub-workflow
// ============================================
SelectCarrier selectCarrier = new();
CreateShipment createShipment = new();
Workflow shippingWorkflow = new WorkflowBuilder(selectCarrier)
.WithName("SubShippingArrangement")
.WithDescription("Selects carrier and creates shipment")
.AddEdge(selectCarrier, createShipment)
.Build();
// ============================================
// Step 5: Build the Main Order Processing workflow using sub-workflows
// ============================================
// Bind sub-workflows as executors for use in the main workflow
ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
ExecutorBinding inventoryExecutor = inventoryWorkflow.BindAsExecutor("Inventory");
ExecutorBinding shippingExecutor = shippingWorkflow.BindAsExecutor("Shipping");
// Create entry and exit executors for the main workflow
OrderReceived orderReceived = new();
OrderCompleted orderCompleted = new();
// Build the main workflow: OrderReceived -> Payment -> Inventory -> Shipping -> OrderCompleted
Workflow orderProcessingWorkflow = new WorkflowBuilder(orderReceived)
.WithName("OrderProcessing")
.WithDescription("Processes an order through payment, inventory, and shipping")
.AddEdge(orderReceived, paymentExecutor)
.AddEdge(paymentExecutor, inventoryExecutor)
.AddEdge(inventoryExecutor, shippingExecutor)
.AddEdge(shippingExecutor, orderCompleted)
.Build();
// ============================================
// Step 6: Configure and start the host
// ============================================
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
// Register only the main workflow - sub-workflows are discovered automatically!
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(orderProcessingWorkflow),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
Console.WriteLine("╔══════════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ Durable Sub-Workflows Sample ║");
Console.WriteLine("╠══════════════════════════════════════════════════════════════════╣");
Console.WriteLine("║ Main Workflow: OrderProcessing ║");
Console.WriteLine("║ ├── Payment (sub-workflow) ║");
Console.WriteLine("║ │ ├── ValidatePayment (1s) ║");
Console.WriteLine("║ │ ├── FraudCheck (sub-sub-workflow) ← Level 2 nesting! ║");
Console.WriteLine("║ │ │ ├── AnalyzePatterns (1s) ║");
Console.WriteLine("║ │ │ └── CalculateRiskScore (1s) ║");
Console.WriteLine("║ │ └── ChargePayment (2s) ║");
Console.WriteLine("║ ├── Inventory (sub-workflow) ║");
Console.WriteLine("║ │ ├── CheckInventory (1s) ║");
Console.WriteLine("║ │ └── ReserveInventory (2s) ║");
Console.WriteLine("║ └── Shipping (sub-workflow) ║");
Console.WriteLine("║ ├── SelectCarrier (1s) ║");
Console.WriteLine("║ └── CreateShipment (2s) ║");
Console.WriteLine("╚══════════════════════════════════════════════════════════════════╝");
Console.WriteLine();
Console.WriteLine("Open the DTS dashboard at http://localhost:8080 to see the");
Console.WriteLine("parent-child orchestration hierarchy in the Timeline view!");
Console.WriteLine();
Console.WriteLine("Enter an order ID (or 'exit'):");
while (true)
{
Console.Write("> ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
try
{
await StartNewWorkflowAsync(input, orderProcessingWorkflow, workflowClient);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
}
await host.StopAsync();
// Start a new workflow using IWorkflowClient
async Task StartNewWorkflowAsync(string orderId, Workflow workflow, IWorkflowClient client)
{
Console.WriteLine($"\nStarting order processing for '{orderId}'...");
await using DurableRun run = (DurableRun)await client.RunAsync(workflow, orderId);
Console.WriteLine($"Instance ID: {run.InstanceId}");
Console.WriteLine("Check the DTS dashboard Timeline tab to see sub-orchestrations!");
Console.WriteLine();
try
{
string? result = await run.WaitForCompletionAsync();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine($"✓ Order completed: {result}");
Console.ResetColor();
}
catch (InvalidOperationException ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"✗ Failed: {ex.Message}");
Console.ResetColor();
}
}
@@ -1,153 +0,0 @@
# Sub-Workflows Console Sample
This sample demonstrates how to compose workflows hierarchically by using sub-workflows within a durable orchestration. Sub-workflows are executed as separate orchestration instances, providing modularity, reusability, and excellent visibility in the Durable Task dashboard.
## Overview
The sample implements an order processing system with three sub-workflows:
```
OrderProcessing (Main Workflow)
??? OrderReceived
??? Payment (Sub-Workflow)
? ??? ValidatePayment (1s)
? ??? ChargePayment (2s)
??? Inventory (Sub-Workflow)
? ??? CheckInventory (1s)
? ??? ReserveInventory (2s)
??? Shipping (Sub-Workflow)
? ??? SelectCarrier (1s)
? ??? CreateShipment (2s)
??? OrderCompleted
```
## Key Concepts
### Sub-Workflow Benefits
1. **Modularity**: Each sub-workflow encapsulates related logic (payment, inventory, shipping)
2. **Reusability**: Sub-workflows can be used in multiple parent workflows
3. **Independent Execution**: Each sub-workflow runs as a separate orchestration instance
4. **Dashboard Visibility**: Sub-workflows appear in the Timeline view with parent-child relationships
5. **Failure Isolation**: A failure in a sub-workflow doesn't corrupt the parent's state
### How Sub-Workflows Work
```csharp
// Step 1: Build a sub-workflow
Workflow paymentWorkflow = new WorkflowBuilder(validatePayment)
.WithName("PaymentProcessing")
.AddEdge(validatePayment, chargePayment)
.Build();
// Step 2: Bind it as an executor for use in a parent workflow
ExecutorBinding paymentExecutor = paymentWorkflow.BindAsExecutor("Payment");
// Step 3: Use the sub-workflow executor in the main workflow
Workflow mainWorkflow = new WorkflowBuilder(orderReceived)
.AddEdge(orderReceived, paymentExecutor) // Sub-workflow as an edge target
.AddEdge(paymentExecutor, inventoryExecutor)
.Build();
// Step 4: Register only the main workflow - sub-workflows are discovered automatically!
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(mainWorkflow),
...);
```
### Dashboard Visualization
Open the DTS dashboard at `http://localhost:8080` after running a workflow:
1. Click on the main orchestration instance
2. Switch to the **Timeline** tab
3. You'll see a hierarchical view showing:
- `OrderProcessing` (parent orchestration)
- `PaymentProcessing` (sub-orchestration)
- `InventoryManagement` (sub-orchestration)
- `ShippingArrangement` (sub-orchestration)
Each sub-orchestration has its own instance ID and can be inspected independently.
## Environment Setup
See the [README.md](../README.md) file in the parent directory for information on:
- Installing prerequisites (.NET 10+, Docker)
- Starting the Durable Task Scheduler emulator
- Configuring environment variables
## Running the Sample
```bash
# Start the DTS emulator (if not already running)
docker run -d --name dts-emulator -p 8080:8080 -p 8082:8082 mcr.microsoft.com/dts/dts-emulator:latest
# Run the sample
cd dotnet/samples/DurableAgents/ConsoleApps/09_SubWorkflows
dotnet run --framework net10.0
```
### Sample Session
```text
????????????????????????????????????????????????????????????????????
? Durable Sub-Workflows Sample ?
????????????????????????????????????????????????????????????????????
? Main Workflow: OrderProcessing ?
? ??? Payment (sub-workflow) ?
? ? ??? ValidatePayment (1s) ?
? ? ??? ChargePayment (2s) ?
? ??? Inventory (sub-workflow) ?
? ? ??? CheckInventory (1s) ?
? ? ??? ReserveInventory (2s) ?
? ??? Shipping (sub-workflow) ?
? ??? SelectCarrier (1s) ?
? ??? CreateShipment (2s) ?
????????????????????????????????????????????????????????????????????
Open the DTS dashboard at http://localhost:8080 to see the
parent-child orchestration hierarchy in the Timeline view!
Enter an order ID (or 'exit'):
> ORD-12345
Starting order processing for 'ORD-12345'...
Instance ID: abc123def456
Check the DTS dashboard Timeline tab to see sub-orchestrations!
[OrderReceived] Processing order 'ORD-12345'
[Payment/ValidatePayment] Validating payment for order 'ORD-12345'...
[Payment/ValidatePayment] Payment validated for $99.99
[Payment/ChargePayment] Charging $99.99 for order 'ORD-12345'...
[Payment/ChargePayment] ? Payment processed: TXN-A1B2C3D4
[Inventory/CheckInventory] Checking inventory for order 'ORD-12345'...
[Inventory/CheckInventory] ? Items available in stock
[Inventory/ReserveInventory] Reserving items for order 'ORD-12345'...
[Inventory/ReserveInventory] ? Reserved: RES-E5F6G7H8
[Shipping/SelectCarrier] Selecting carrier for order 'ORD-12345'...
[Shipping/SelectCarrier] ? Selected carrier: Express
[Shipping/CreateShipment] Creating shipment for order 'ORD-12345'...
[Shipping/CreateShipment] ? Shipment created: TRACK-I9J0K1L2M3
???????????????????????????????????????????????????????????????????
? [OrderCompleted] Order 'ORD-12345' successfully processed!
? Payment: TXN-A1B2C3D4
? Inventory: RES-E5F6G7H8
? Shipping: Express - TRACK-I9J0K1L2M3
???????????????????????????????????????????????????????????????????
? Order completed. Tracking: TRACK-I9J0K1L2M3
```
## Comparison with In-Process Sub-Workflows
| Feature | In-Process | Durable |
|---------|------------|---------|
| Execution | Same process, synchronized supersteps | Separate orchestration instances |
| Visibility | Single workflow view | Hierarchical dashboard view |
| Checkpointing | Parent checkpoints include child state | Independent checkpoints per sub-workflow |
| Failure Recovery | Parent must handle child failures | Automatic retry with state preservation |
| Scalability | Single process | Can scale across workers |
## Related Samples
- [06_SubWorkflows (In-Process)](../../../GettingStarted/Workflows/_Foundational/06_SubWorkflows) - In-process sub-workflow execution
- [08_SingleWorkflow](../08_SingleWorkflow) - Basic durable workflow without sub-workflows
@@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SingleWorkflow</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Azure.AI.OpenAI" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,67 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace WorkflowConcurrency;
/// <summary>
/// Parses and validates the incoming question before sending to AI agents.
/// </summary>
internal sealed class ParseQuestionExecutor() : Executor<string, string>("ParseQuestion")
{
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Magenta;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine("│ [ParseQuestion] Preparing question for AI agents...");
string formattedQuestion = message.Trim();
if (!formattedQuestion.EndsWith('?'))
{
formattedQuestion += "?";
}
Console.WriteLine($"│ [ParseQuestion] Question: \"{formattedQuestion}\"");
Console.WriteLine("│ [ParseQuestion] → Sending to Physicist and Chemist in PARALLEL...");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();
return ValueTask.FromResult(formattedQuestion);
}
}
/// <summary>
/// Aggregates responses from all AI agents into a comprehensive answer.
/// This is the Fan-in point where parallel results are collected.
/// </summary>
internal sealed class AggregatorExecutor() : Executor<string[], string>("Aggregator")
{
public override ValueTask<string> HandleAsync(string[] message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
Console.WriteLine();
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("┌─────────────────────────────────────────────────────────────────┐");
Console.WriteLine($"│ [Aggregator] 📋 Received {message.Length} AI agent responses");
Console.WriteLine("│ [Aggregator] Combining into comprehensive answer...");
Console.WriteLine("│ [Aggregator] ✓ Aggregation complete!");
Console.WriteLine("└─────────────────────────────────────────────────────────────────┘");
Console.ResetColor();
string aggregatedResult = "═══════════════════════════════════════════════════════════════\n" +
" AI EXPERT PANEL RESPONSES\n" +
"═══════════════════════════════════════════════════════════════\n\n";
for (int i = 0; i < message.Length; i++)
{
string expertLabel = i == 0 ? "⚛️ PHYSICIST" : "🧪 CHEMIST";
aggregatedResult += $"{expertLabel}:\n{message[i]}\n\n";
}
aggregatedResult += "═══════════════════════════════════════════════════════════════\n" +
$"Summary: Received perspectives from {message.Length} AI experts.\n" +
"═══════════════════════════════════════════════════════════════";
return ValueTask.FromResult(aggregatedResult);
}
}
@@ -1,136 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates the Fan-out/Fan-in pattern in a durable workflow.
// The workflow uses 4 executors: 2 class-based executors and 2 AI agents.
//
// WORKFLOW PATTERN (4 Executors):
//
// ┌──────────────────┐
// │ ParseQuestion │ ← Class-based Executor
// └────────┬─────────┘
// │
// ┌────────┴────────┐
// ▼ ▼
// ┌──────────┐ ┌──────────┐
// │ Physicist│ │ Chemist │ ← AI Agents (parallel)
// └────┬─────┘ └────┬─────┘
// │ │
// └──────┬───────┘
// ▼
// ┌──────────────────┐
// │ Aggregator │ ← Class-based Executor
// └──────────────────┘
using Azure;
using Azure.AI.OpenAI;
using Azure.Identity;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using OpenAI.Chat;
using WorkflowConcurrency;
// Configuration
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
string endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")
?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
string deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT")
?? throw new InvalidOperationException("AZURE_OPENAI_DEPLOYMENT is not set.");
string? azureOpenAiKey = Environment.GetEnvironmentVariable("AZURE_OPENAI_KEY");
// Create Azure OpenAI client
AzureOpenAIClient openAiClient = !string.IsNullOrEmpty(azureOpenAiKey)
? new AzureOpenAIClient(new Uri(endpoint), new AzureKeyCredential(azureOpenAiKey))
: new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential());
ChatClient chatClient = openAiClient.GetChatClient(deploymentName);
// Define the 4 executors for the workflow
ParseQuestionExecutor parseQuestion = new(); // Executor 1: Class-based
AIAgent physicist = chatClient.AsAIAgent("You are a physics expert. Be concise (2-3 sentences).", "Physicist"); // Executor 2: AI Agent
AIAgent chemist = chatClient.AsAIAgent("You are a chemistry expert. Be concise (2-3 sentences).", "Chemist"); // Executor 3: AI Agent
AggregatorExecutor aggregator = new(); // Executor 4: Class-based
// Build workflow: ParseQuestion → [Physicist, Chemist] (parallel) → Aggregator
Workflow workflow = new WorkflowBuilder(parseQuestion)
.WithName("ExpertReview")
.AddFanOutEdge(parseQuestion, [physicist, chemist])
.AddFanInEdge([physicist, chemist], aggregator)
.Build();
// Configure and start the host
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(workflow),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
// Console UI
Console.ForegroundColor = ConsoleColor.Cyan;
Console.WriteLine("╔═══════════════════════════════════════════════════════════════════════╗");
Console.WriteLine("║ Fan-out/Fan-in Workflow Sample (4 Executors) ║");
Console.WriteLine("║ ║");
Console.WriteLine("║ ParseQuestion → [Physicist, Chemist] → Aggregator ║");
Console.WriteLine("║ (class-based) (AI agents, parallel) (class-based) ║");
Console.WriteLine("╚═══════════════════════════════════════════════════════════════════════╝");
Console.ResetColor();
Console.WriteLine();
await Task.Delay(TimeSpan.FromSeconds(2)); // Allow pending workflows to resume
Console.WriteLine("Enter a science question (or 'exit' to quit):");
Console.WriteLine();
while (true)
{
Console.ForegroundColor = ConsoleColor.Yellow;
Console.Write("Question: ");
Console.ResetColor();
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
try
{
// Cast to DurableRun for durable-specific features like InstanceId and WaitForCompletionAsync
await using DurableRun run = (DurableRun)await workflowClient.RunAsync(workflow, input);
Console.ForegroundColor = ConsoleColor.Gray;
Console.WriteLine($"Instance: {run.InstanceId}");
Console.ResetColor();
string? result = await run.WaitForCompletionAsync();
Console.ForegroundColor = ConsoleColor.Green;
Console.WriteLine("\n✓ Workflow completed!\n");
Console.ResetColor();
Console.WriteLine(result);
}
catch (Exception ex)
{
Console.ForegroundColor = ConsoleColor.Red;
Console.WriteLine($"✗ Error: {ex.Message}");
Console.ResetColor();
}
Console.WriteLine();
}
await host.StopAsync();
@@ -1,160 +0,0 @@
# Fan-out/Fan-in Workflow with AI Agents
This sample demonstrates the **Fan-out/Fan-in pattern** using real AI agents in a durable workflow. A question is sent to multiple AI "expert" agents in parallel, and their responses are aggregated into a final result.
## Overview
The sample implements an expert consultation workflow using Azure OpenAI:
- A question is parsed and prepared
- The question is sent to **2 AI agents in parallel** (Fan-out)
- AI agent responses are **collected and aggregated** (Fan-in)
### Components
| Component | Type | Description |
|-----------|------|-------------|
| ParseQuestion | Executor | Validates and formats the incoming question |
| Physicist | AI Agent | Azure OpenAI agent with physics expertise |
| Chemist | AI Agent | Azure OpenAI agent with chemistry expertise |
| Aggregator | Executor | Combines all AI agent responses |
## Workflow Pattern
```
???????????????????
? ParseQuestion ?
???????????????????
?
???????????????????????????????
? ?
? ?
??????????????????? ???????????????????
? Physicist ? ? Chemist ?
? (AI Agent) ? ? (AI Agent) ?
??????????????????? ???????????????????
? ?
? PARALLEL EXECUTION ?
? ?
???????????????????????????????
?
?
???????????????????
? Aggregator ?
???????????????????
```
## Key Concepts Demonstrated
- **Fan-out (AddFanOutEdge)**: One executor sends to multiple AI agents in parallel
- **Fan-in (AddFanInEdge)**: Multiple AI agent results are collected into an array
- **AI Agents in Workflows**: Using Azure OpenAI agents as workflow executors
- **Durability**: If interrupted, completed AI agent responses are preserved on restart
## Configuration
When using AI agents in durable workflows, `ConfigureDurableWorkflows` automatically registers any AI agents found in the workflow. You only need a single configuration call:
```csharp
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(expertReview),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
```
This is similar to how `ConfigureDurableOptions` works in Azure Functions samples.
## Environment Setup
This sample requires:
1. **Durable Task Scheduler** - See the [parent README](../README.md) for setup instructions
2. **Azure OpenAI** - You need an Azure OpenAI resource with a deployed model
### Environment Variables
| Variable | Description |
|----------|-------------|
| `DURABLE_TASK_SCHEDULER_CONNECTION_STRING` | Connection string for DTS (defaults to local emulator) |
| `AZURE_OPENAI_ENDPOINT` | Your Azure OpenAI endpoint URL |
| `AZURE_OPENAI_DEPLOYMENT` | Name of your deployed model (e.g., `gpt-4`) |
| `AZURE_OPENAI_KEY` | (Optional) API key - if not set, uses Azure CLI credential |
## Running the Sample
```bash
cd dotnet/samples/DurableAgents/ConsoleApps/09_Workflow_Concurrency
dotnet run --framework net10.0
```
### Sample Session
```text
??????????????????????????????????????????????????????????????????????
? Fan-out/Fan-in Workflow with AI Agents ?
??????????????????????????????????????????????????????????????????????
? This sample demonstrates parallel AI agent consultation. ?
? ?
? Workflow: Question ? [Physicist, Chemist] ? Aggregator ?
? (AI agents run in parallel) ?
??????????????????????????????????????????????????????????????????????
?? TIP: Stop during execution to test durability - completed agent responses are preserved!
? Checking for pending workflows...
Enter a science question (or 'exit' to quit):
Example: "What is water?"
Question: What is water
Starting expert review workflow...
Instance ID: abc123-def456-...
???????????????????????????????????????????????????????????????????
? [ParseQuestion] Preparing question for AI agents...
? [ParseQuestion] Question: "What is water?"
? [ParseQuestion] ? Sending to Physicist and Chemist in PARALLEL...
???????????????????????????????????????????????????????????????????
... (AI agents process in parallel)
???????????????????????????????????????????????????????????????????
? [Aggregator] ?? Received 2 AI agent responses
? [Aggregator] Combining into comprehensive answer...
? [Aggregator] ? Aggregation complete!
???????????????????????????????????????????????????????????????????
??????????????????????????????????????????????????????????????????????
? ? Expert review completed! ?
??????????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????
AI EXPERT PANEL RESPONSES
???????????????????????????????????????????????????????????????
?? PHYSICIST:
Water is H2O - two hydrogen atoms bonded to one oxygen atom. From a physics
perspective, water exhibits unique properties like high specific heat capacity
and surface tension due to hydrogen bonding.
?? CHEMIST:
Water (H2O) is a polar molecule formed by covalent bonds. Its bent molecular
geometry creates a dipole moment, making it an excellent solvent for ionic
and polar compounds.
???????????????????????????????????????????????????????????????
Summary: Received perspectives from 2 AI experts.
???????????????????????????????????????????????????????????????
```
## Durability with AI Agents
If you stop the application while one AI agent is processing:
- The completed agent's response is **preserved** (cached by Durable Task)
- On restart, only the incomplete agent **re-runs**
- The aggregator waits for all agents to complete
## Related Samples
- [08_SingleWorkflow](../08_SingleWorkflow) - Sequential workflow with durability demonstration
- [10_WorkflowConcurrent](../../AzureFunctions/10_WorkflowConcurrent) - Azure Functions version
- [02_AgentOrchestration_Chaining](../02_AgentOrchestration_Chaining) - Agent chaining with durable orchestration
@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SingleWorkflow</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,27 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
public record ApprovalRequest(string ExpenseId, decimal Amount, string EmployeeName);
public record ApprovalResponse(bool Approved, string? Comments);
internal sealed class CreateApprovalRequest() : Executor<string, ApprovalRequest>("RetrieveRequest")
{
public override async ValueTask<ApprovalRequest> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Get request details from db.
return new ApprovalRequest(message, 1500.00m, "Jerry");
}
}
internal sealed class ExpenseReimburse() : Executor<ApprovalResponse, string>("Reimburse")
{
public override async ValueTask<string> HandleAsync(ApprovalResponse message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Simulate payment processing.
await Task.Delay(1000, cancellationToken);
return $"Expense reimbursed at {DateTime.Now.ToUniversalTime()}";
}
}
@@ -1,105 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates a Human-in-the-Loop (HITL) workflow using Durable Tasks.
// The workflow creates an expense approval request, waits for manager approval via an external event,
// and then processes the expense reimbursement based on the approval response.
// This sample mirrors the pattern used in the in-process HumanInTheLoopBasic sample.
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SingleAgent;
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Define executors for the workflow
CreateApprovalRequest createRequest = new();
RequestPort<ApprovalRequest, ApprovalResponse> managerApproval = RequestPort.Create<ApprovalRequest, ApprovalResponse>("ManagerApproval");
ExpenseReimburse reimburse = new();
Workflow expenseApproval = new WorkflowBuilder(createRequest)
.WithName("ExpenseReImbursement")
.WithDescription("Expense ReImbursement")
.AddEdge(createRequest, managerApproval)
.AddEdge(managerApproval, reimburse)
.Build();
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(expenseApproval),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
// Start the workflow with an expense ID as input
string expenseId = "EXP-2025-001";
Console.WriteLine($"Starting expense reimbursement workflow for expense: {expenseId}");
// Start the workflow and get a streaming handle
// Cast to DurableStreamingRun for durable-specific features like InstanceId and SendResponseAsync
await using DurableStreamingRun run = (DurableStreamingRun)await workflowClient.StreamAsync(expenseApproval, expenseId);
Console.WriteLine($"Workflow started with instance ID: {run.InstanceId}");
Console.WriteLine("Watching for workflow events...\n");
// Watch for workflow events - similar pattern to InProcessExecution.StreamAsync
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
switch (evt)
{
case DurableRequestInfoEvent requestEvent:
// Handle request for external input (human-in-the-loop)
Console.WriteLine($"Workflow is waiting for input at RequestPort: {requestEvent.RequestPortId}");
Console.WriteLine($" Input data: {requestEvent.Input}");
Console.WriteLine($" Expected response type: {requestEvent.ResponseType}");
// Simulate manager approval
ApprovalResponse response = HandleApprovalRequest(requestEvent);
await run.SendResponseAsync(requestEvent, response);
Console.WriteLine($" Response sent: Approved={response.Approved}\n");
break;
case DurableWorkflowCompletedEvent completedEvent:
// The workflow has completed
Console.WriteLine($"Workflow completed with result: {completedEvent.Result}");
break;
case DurableWorkflowFailedEvent failedEvent:
// The workflow has failed
Console.WriteLine($"Workflow failed: {failedEvent.ErrorMessage}");
break;
}
}
Console.ReadLine();
await host.StopAsync();
// Handler for approval requests - similar to HandleExternalRequest in the in-process sample
static ApprovalResponse HandleApprovalRequest(DurableRequestInfoEvent requestEvent)
{
// In a real scenario, this would involve human interaction (e.g., a web UI)
// For this sample, we simulate automatic approval
ApprovalRequest? request = requestEvent.GetInputAs<ApprovalRequest>();
if (request is not null)
{
Console.WriteLine($" Approval request for: {request.EmployeeName}, Amount: {request.Amount:C}");
}
return new ApprovalResponse(Approved: true, Comments: "Approved by manager. Looks good!");
}
@@ -1,96 +0,0 @@
# Workflow Human-in-the-Loop (HITL) Sample
This sample demonstrates a **Human-in-the-Loop** pattern in durable workflows using `RequestPort`. The workflow pauses execution to wait for external input (e.g., manager approval) and resumes when the response is provided.
## Overview
The sample implements an expense approval workflow:
1. **CreateApprovalRequest** - Retrieves expense details and creates an approval request
2. **ManagerApproval** (RequestPort) - Pauses workflow to wait for manager approval
3. **ExpenseReimburse** - Processes the reimbursement based on approval response
## Workflow Flow
```
User Input (Expense ID)
|
v
+---------------------+
| CreateApprovalRequest| Creates ApprovalRequest with expense details
+---------------------+
|
v
+---------------------+
| ManagerApproval | RequestPort - PAUSES here waiting for external input
| (RequestPort) | Workflow is durable while waiting
+---------------------+
|
v (ApprovalResponse)
+---------------------+
| ExpenseReimburse | Processes reimbursement if approved
+---------------------+
|
v
Result
```
## Key Concepts
- **RequestPort** - A special executor that pauses the workflow and waits for external input
- **DurableRequestInfoEvent** - Event emitted when the workflow reaches a RequestPort
- **SendResponseAsync** - Method to provide the response and resume the workflow
- **Durability** - The workflow can survive process restarts while waiting for human input
## Code Highlights
### Defining the RequestPort
```csharp
RequestPort<ApprovalRequest, ApprovalResponse> managerApproval =
RequestPort.Create<ApprovalRequest, ApprovalResponse>("ManagerApproval");
```
### Handling the Request Event
```csharp
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
switch (evt)
{
case DurableRequestInfoEvent requestEvent:
// Workflow is waiting for input
ApprovalResponse response = HandleApprovalRequest(requestEvent);
await run.SendResponseAsync(requestEvent, response);
break;
// ... other events
}
}
```
## Environment Setup
See the [README.md](../README.md) file in the parent directory for environment configuration.
## Running the Sample
```bash
cd dotnet/samples/DurableAgents/ConsoleApps/10_Workflow_HITL
dotnet run --framework net10.0
```
### Sample Output
```
Starting expense reimbursement workflow for expense: EXP-2025-001
Workflow started with instance ID: abc123...
Watching for workflow events...
Workflow is waiting for input at RequestPort: ManagerApproval
Input data: {"ExpenseId":"EXP-2025-001","Amount":1500.00,"EmployeeName":"Jerry"}
Expected response type: SingleAgent.ApprovalResponse
Approval request for: Jerry, Amount: $1,500.00
Response sent: Approved=True
Workflow completed with result: Expense reimbursed at 1/23/2025 5:30:00 PM
```
@@ -1,29 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SingleWorkflow</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,16 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Event emitted to report cancellation progress.
/// </summary>
public sealed class CancellationProgressEvent(string orderId, int percentComplete, string status)
: WorkflowEvent($"Cancellation {percentComplete}%: {status}")
{
public string OrderId { get; } = orderId;
public int PercentComplete { get; } = percentComplete;
public string Status { get; } = status;
}
@@ -1,14 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Event emitted when an email is sent.
/// </summary>
public sealed class EmailSentEvent(string email, string subject) : WorkflowEvent($"Email sent to {email}")
{
public string Email { get; } = email;
public string Subject { get; } = subject;
}
@@ -1,117 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to use IWorkflowContext methods in your executors:
// - AddEventAsync: Emit custom events that can be observed by the workflow caller
// - YieldOutputAsync: Stream intermediate outputs during execution
//
// These features enable rich observability and control over workflow execution.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
#region Domain Models
/// <summary>
/// Represents an order in the system.
/// </summary>
public sealed class Order
{
public required string Id { get; set; }
public DateTime OrderDate { get; set; }
public bool IsCancelled { get; set; }
public required Customer Customer { get; set; }
}
/// <summary>
/// Represents a customer associated with an order.
/// </summary>
public sealed class Customer
{
public string Name { get; set; } = string.Empty;
public string Email { get; set; } = string.Empty;
}
#endregion
#region Custom Workflow Events
#endregion
#region Executors
/// <summary>
/// Looks up an order by its ID. Demonstrates AddEventAsync for custom events.
/// </summary>
internal sealed class OrderLookup() : Executor<string, Order>("OrderLookup")
{
public override async ValueTask<Order> HandleAsync(
string message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
await context.AddEventAsync(new OrderLookupStartedEvent(message), cancellationToken);
await Task.Delay(500, cancellationToken);
Order order = new()
{
Id = message,
OrderDate = DateTime.UtcNow.AddDays(-3),
IsCancelled = false,
Customer = new Customer { Name = "Jerry", Email = "jerry@example.com" }
};
await context.AddEventAsync(new OrderFoundEvent(order), cancellationToken);
return order;
}
}
/// <summary>
/// Cancels an order with progress reporting.
/// Demonstrates AddEventAsync for progress events and YieldOutputAsync for streaming outputs.
/// </summary>
internal sealed class OrderCancel() : Executor<Order, Order>("OrderCancel")
{
public override async ValueTask<Order> HandleAsync(
Order message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
// Simulate cancellation steps with progress events
string[] steps = ["Validating", "Processing refund", "Finalizing"];
for (int i = 0; i < steps.Length; i++)
{
await Task.Delay(500, cancellationToken);
int percent = (i + 1) * 33;
// Emit progress event (callers can observe this in real-time)
await context.AddEventAsync(new CancellationProgressEvent(message.Id, percent, steps[i]), cancellationToken);
// YieldOutputAsync streams intermediate results matching the executor's return type
await context.YieldOutputAsync(message, cancellationToken);
}
message.IsCancelled = true;
await context.AddEventAsync(new OrderCancelledEvent(message.Id), cancellationToken);
return message;
}
}
/// <summary>
/// Sends a cancellation confirmation email. Demonstrates AddEventAsync for completion events.
/// </summary>
internal sealed class SendEmail() : Executor<Order, string>("SendEmail")
{
public override async ValueTask<string> HandleAsync(
Order message,
IWorkflowContext context,
CancellationToken cancellationToken = default)
{
await Task.Delay(500, cancellationToken);
string email = message.Customer.Email;
await context.AddEventAsync(new EmailSentEvent(email, $"Order {message.Id} Cancelled"), cancellationToken);
return $"Email sent to {email}";
}
}
#endregion
@@ -1,13 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Event emitted when an order is successfully cancelled.
/// </summary>
public sealed class OrderCancelledEvent(string orderId) : WorkflowEvent($"Order {orderId} has been cancelled")
{
public string OrderId { get; } = orderId;
}
@@ -1,13 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Event emitted when an order is found.
/// </summary>
public sealed class OrderFoundEvent(Order order) : WorkflowEvent($"Found order {order.Id} for {order.Customer.Name}")
{
public Order Order { get; } = order;
}
@@ -1,13 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace SingleAgent;
/// <summary>
/// Event emitted when an order lookup starts.
/// </summary>
public sealed class OrderLookupStartedEvent(string orderId) : WorkflowEvent($"Looking up order {orderId}")
{
public string OrderId { get; } = orderId;
}
@@ -1,139 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// ═══════════════════════════════════════════════════════════════════════════════
// SAMPLE: Workflow Events and IWorkflowContext Features
// ═══════════════════════════════════════════════════════════════════════════════
//
// This sample demonstrates how to use IWorkflowContext methods in executors:
//
// 1. AddEventAsync - Emit custom events that callers can observe in real-time
// 2. YieldOutputAsync - Stream intermediate outputs during long-running operations
//
// The sample uses DurableExecutionEnvironment.StreamAsync to observe events as they occur,
// showing how callers can receive real-time updates from the workflow.
//
// Workflow: OrderLookup -> OrderCancel -> SendEmail
// ═══════════════════════════════════════════════════════════════════════════════
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SingleAgent;
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
// Define executors and build workflow
OrderLookup orderLookup = new();
OrderCancel orderCancel = new();
SendEmail sendEmail = new();
Workflow cancelOrder = new WorkflowBuilder(orderLookup)
.WithName("CancelOrder")
.WithDescription("Cancel an order and notify the customer")
.AddEdge(orderLookup, orderCancel)
.AddEdge(orderCancel, sendEmail)
.Build();
// Configure host with durable workflow support
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(cancelOrder),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
Console.WriteLine("Workflow Events Demo - Enter order ID (or 'exit'):");
while (true)
{
Console.Write("> ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
try
{
await RunWorkflowWithStreamingAsync(input, cancelOrder, workflowClient);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
}
await host.StopAsync();
// Runs a workflow and streams events as they occur
async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, IWorkflowClient client)
{
// StreamAsync starts the workflow and returns a handle for observing events
// Cast to DurableStreamingRun for durable-specific features like InstanceId
await using DurableStreamingRun run = (DurableStreamingRun)await client.StreamAsync(workflow, orderId);
Console.WriteLine($"Started: {run.InstanceId}");
// WatchStreamAsync yields events as they're emitted by executors
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
// Always print the event type name
Console.WriteLine($" Event: {evt.GetType().Name}");
switch (evt)
{
// Custom domain events (emitted via AddEventAsync)
case OrderLookupStartedEvent e:
WriteColored($" [Lookup] Looking up order {e.OrderId}", ConsoleColor.Cyan);
break;
case OrderFoundEvent e:
WriteColored($" [Lookup] Found: {e.Order.Customer.Name}", ConsoleColor.Cyan);
break;
case CancellationProgressEvent e:
WriteColored($" [Cancel] {e.PercentComplete}% - {e.Status}", ConsoleColor.Yellow);
break;
case OrderCancelledEvent e:
WriteColored(" [Cancel] Done", ConsoleColor.Yellow);
break;
case EmailSentEvent e:
WriteColored($" [Email] Sent to {e.Email}", ConsoleColor.Magenta);
break;
// Yielded outputs (emitted via YieldOutputAsync)
case DurableYieldedOutputEvent e:
WriteColored($" [Output] {e.ExecutorId}", ConsoleColor.DarkGray);
break;
// Workflow completion
case DurableWorkflowCompletedEvent e:
WriteColored($" Completed: {e.Result}", ConsoleColor.Green);
break;
case DurableWorkflowFailedEvent e:
WriteColored($" Failed: {e.ErrorMessage}", ConsoleColor.Red);
break;
}
}
}
void WriteColored(string message, ConsoleColor color)
{
Console.ForegroundColor = color;
Console.WriteLine(message);
Console.ResetColor();
}
@@ -1,156 +0,0 @@
# Single Workflow Console Sample
This sample demonstrates how to run a workflow as a durable orchestration from a console application using the Durable Task Framework. It showcases the **durability** aspect - if the process crashes mid-execution, the workflow can be resumed without re-executing completed activities.
## Overview
The sample implements an order cancellation workflow with three executors, each with artificial delays to simulate real-world operations:
1. **OrderLookup** (2 seconds) - Looks up an order by its ID
2. **OrderCancel** (5 seconds) - Marks the order as cancelled
3. **SendEmail** (1 second) - Sends a cancellation confirmation email
## Durability Demonstration
The key feature of Durable Task Framework is **durability**:
- **Activity results are persisted**: When an activity completes, its result is saved
- **Orchestrations are replayed**: On restart, the orchestration replays from the beginning
- **Completed activities are skipped**: The framework uses cached results for completed activities
- **Failed activities are retried**: If an activity was interrupted, it runs again
- **Automatic resume**: When the worker starts, it automatically picks up any pending work!
### Try It Yourself
1. Start the application and enter an order ID (e.g., `12345`)
2. Stop the app (Ctrl+C or stop debugging) during the `OrderCancel` activity (5 seconds)
3. Restart the application
4. **Watch for automatic resume!** The worker automatically picks up the interrupted workflow
5. Observe that `OrderLookup` is NOT re-executed (its result was cached)
6. `OrderCancel` restarts from the beginning (it didn't complete)
7. `SendEmail` runs after `OrderCancel` completes
The durability is completely automatic - no manual intervention needed!
## Workflow Flow
```
User Input (Order ID)
?
?
???????????????????
? OrderLookup ? ? 2 second delay (database lookup)
? (2 seconds) ?
???????????????????
?
?
???????????????????
? OrderCancel ? ? 5 second delay - TRY INTERRUPTING HERE!
? (5 seconds) ?
???????????????????
?
?
???????????????????
? SendEmail ? ? 1 second delay (email sending)
? (1 second) ?
???????????????????
?
?
Result
```
## Key Concepts Demonstrated
- **ConfigureDurableWorkflows** - Simplified API for registering workflows
- **DurableExecution.RunAsync** - Start a new workflow (similar to InProcessExecution)
- **DurableRun** - Handle to monitor and interact with a running workflow
- **Automatic Resume** - Interrupted workflows continue automatically on restart
## Environment Setup
See the [README.md](../README.md) file in the parent directory for more information on how to configure the environment, including how to install and run common sample dependencies.
## Running the Sample
With the environment setup, you can run the sample:
```bash
cd dotnet/samples/DurableAgents/ConsoleApps/08_SingleWorkflow
dotnet run --framework net10.0
```
### Sample Session
```text
??????????????????????????????????????????????????????????????????????
? Durable Workflow Console Sample ?
??????????????????????????????????????????????????????????????????????
? This sample demonstrates durability in workflows. ?
? Workflow: OrderLookup (2s) -> OrderCancel (5s) -> SendEmail (1s) ?
??????????????????????????????????????????????????????????????????????
?? TIP: Stop the app during OrderCancel (5 seconds) to test durability!
Restart the app - it will automatically resume from where it left off.
? Checking for pending workflows...
Enter an order ID to start a new workflow (or 'exit' to quit):
Order ID: 12345
Starting workflow for order '12345'...
Instance ID: abc123-def456-...
???????????????????????????????????????????????????????????????????
? [Activity] OrderLookup: Starting lookup for order '12345'
? [Activity] OrderLookup: Found order '12345' for customer 'Jerry'
???????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????????
? [Activity] OrderCancel: Starting cancellation for order '12345'
? [Activity] OrderCancel: ?? This takes 5 seconds - try Ctrl+C!
? [Activity] OrderCancel: Processing... 1/5 seconds
? [Activity] OrderCancel: Processing... 2/5 seconds
^C <-- User stops the app here
[After restart...]
? Checking for pending workflows...
???????????????????????????????????????????????????????????????????
? [Activity] OrderCancel: Starting cancellation for order '12345' <-- Auto-resumed!
? [Activity] OrderCancel: ?? This takes 5 seconds - try Ctrl+C!
? [Activity] OrderCancel: Processing... 1/5 seconds
...
? [Activity] OrderCancel: ? Order '12345' has been cancelled
???????????????????????????????????????????????????????????????????
???????????????????????????????????????????????????????????????????
? [Activity] SendEmail: Sending email to 'jerry@example.com'...
? [Activity] SendEmail: ? Email sent successfully!
???????????????????????????????????????????????????????????????????
Enter an order ID to start a new workflow (or 'exit' to quit):
Order ID: _
```
Notice that when resumed:
- `OrderLookup` was **NOT re-executed** (result was cached by Durable Task)
- `OrderCancel` **restarted automatically** (it was interrupted before completing)
- `SendEmail` ran normally after `OrderCancel` completed
## Viewing Workflow State
You can view the state of the workflow in the Durable Task Scheduler dashboard:
1. Open your browser and navigate to `http://localhost:8082`
2. In the dashboard, you can view the state of the orchestration, including activity history and current state
## Related Samples
- [01_SingleAgent](../01_SingleAgent) - Single agent console sample
- [02_AgentOrchestration_Chaining](../02_AgentOrchestration_Chaining) - Agent chaining with durable orchestration
- [05_AgentOrchestration_HITL](../05_AgentOrchestration_HITL) - Human-in-the-loop orchestration
- [09_Workflow](../../AzureFunctions/09_Workflow) - Azure Functions version of workflow hosting
@@ -1,31 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFrameworks>net10.0</TargetFrameworks>
<OutputType>Exe</OutputType>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AssemblyName>SingleWorkflow</AssemblyName>
<RootNamespace>SingleAgent</RootNamespace>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" />
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
<PackageReference Include="Azure.AI.OpenAI" />
</ItemGroup>
<!-- Local projects that should be switched to package references when using the sample outside of this MAF repo -->
<!--
<ItemGroup>
<PackageReference Include="Microsoft.Agents.AI.DurableTask" />
<PackageReference Include="Microsoft.Agents.AI.Workflows" />
</ItemGroup>
-->
<ItemGroup>
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.DurableTask\Microsoft.Agents.AI.DurableTask.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
<ProjectReference Include="..\..\..\..\src\Microsoft.Agents.AI.OpenAI\Microsoft.Agents.AI.OpenAI.csproj" />
</ItemGroup>
</Project>
@@ -1,69 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace SingleAgent;
internal sealed class FeedbackExecutor : Executor<SloganResult>
{
private readonly AIAgent _agent;
private AgentThread? _thread;
public int MinimumRating { get; init; } = 9;
public int MaxAttempts { get; init; } = 3;
private int _attempts;
/// <summary>
/// Initializes a new instance of the <see cref="FeedbackExecutor"/> class.
/// </summary>
/// <param name="id">A unique identifier for the executor.</param>
/// <param name="chatClient">The chat client to use for the AI agent.</param>
public FeedbackExecutor(string id, IChatClient chatClient) : base(id)
{
ChatClientAgentOptions agentOptions = new()
{
ChatOptions = new()
{
Instructions = "You are a professional editor. You will be given a slogan and the task it is meant to accomplish.",
ResponseFormat = ChatResponseFormat.ForJsonSchema<FeedbackResult>()
}
};
this._agent = new ChatClientAgent(chatClient, agentOptions);
}
public override async ValueTask HandleAsync(SloganResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken);
var sloganMessage = $"""
Here is a slogan for the task '{message.Task}':
Slogan: {message.Slogan}
Please provide feedback on this slogan, including comments, a rating from 1 to 10, and suggested actions for improvement.
""";
var response = await this._agent.RunAsync(sloganMessage, this._thread, cancellationToken: cancellationToken);
var feedback = JsonSerializer.Deserialize<FeedbackResult>(response.Text) ?? throw new InvalidOperationException("Failed to deserialize feedback.");
if (feedback.Rating >= this.MinimumRating)
{
await context.YieldOutputAsync($"The following slogan was accepted:\n\n{message.Slogan}", cancellationToken);
return;
}
if (this._attempts >= this.MaxAttempts)
{
await context.YieldOutputAsync($"The slogan was rejected after {this.MaxAttempts} attempts. Final slogan:\n\n{message.Slogan}", cancellationToken);
return;
}
Console.WriteLine("Sending back for refining");
await context.SendMessageAsync(feedback, cancellationToken: cancellationToken);
this._attempts++;
}
}
@@ -1,113 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
// This sample demonstrates how to run a CYCLIC WORKFLOW as a durable orchestration.
// The workflow contains a loop: SloganWriter ⟷ FeedbackProvider
//
// WORKFLOW LOOP PATTERN:
// 1. SloganWriter generates a slogan based on user input
// 2. FeedbackProvider evaluates the slogan and provides feedback
// 3. If the rating is below threshold, FeedbackProvider sends feedback back to SloganWriter
// 4. SloganWriter improves the slogan based on feedback
// 5. Loop continues until FeedbackProvider accepts the slogan (rating >= threshold)
//
// This demonstrates:
// - Cyclic workflow support (back-edges in the graph)
// - Multi-type executor handlers (SloganWriter handles both string and FeedbackResult)
// - Message routing via SendMessageAsync for void-returning executors
// - YieldOutputAsync for final output when the loop completes
using Azure.Identity;
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client.AzureManaged;
using Microsoft.DurableTask.Worker.AzureManaged;
using Microsoft.Extensions.AI;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using SingleAgent;
using Azure.AI.OpenAI;
// Get DTS connection string from environment variable
string dtsConnectionString = Environment.GetEnvironmentVariable("DURABLE_TASK_SCHEDULER_CONNECTION_STRING")
?? "Endpoint=http://localhost:8080;TaskHub=default;Authentication=None";
var endpoint = Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT") ?? throw new InvalidOperationException("AZURE_OPENAI_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_OPENAI_DEPLOYMENT") ?? "gpt-4o-mini";
var chatClient = new AzureOpenAIClient(new Uri(endpoint), new AzureCliCredential()).GetChatClient(deploymentName).AsIChatClient();
// Define executors for the workflow
var sloganWriter = new SloganWriterExecutor("SloganWriter", chatClient);
var feedbackProvider = new FeedbackExecutor("FeedbackProvider", chatClient);
// Build the workflow by adding executors and connecting them
var workflow = new WorkflowBuilder(sloganWriter)
.WithName("SloganCreationWorkflow")
.AddEdge(sloganWriter, feedbackProvider)
.AddEdge(feedbackProvider, sloganWriter)
.WithOutputFrom(feedbackProvider)
.Build();
IHost host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging => logging.SetMinimumLevel(LogLevel.Warning))
.ConfigureServices(services =>
{
services.ConfigureDurableWorkflows(
options => options.Workflows.AddWorkflow(workflow),
workerBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString),
clientBuilder: builder => builder.UseDurableTaskScheduler(dtsConnectionString));
})
.Build();
await host.StartAsync();
// Get the IWorkflowClient from DI - no need to manually resolve DurableTaskClient
IWorkflowClient workflowClient = host.Services.GetRequiredService<IWorkflowClient>();
Console.WriteLine("Workflow Events Demo - Enter input for slogan generation (or 'exit'):");
while (true)
{
Console.Write("> ");
string? input = Console.ReadLine();
if (string.IsNullOrWhiteSpace(input) || input.Equals("exit", StringComparison.OrdinalIgnoreCase))
{
break;
}
try
{
await RunWorkflowWithStreamingAsync(input, workflow, workflowClient);
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
Console.WriteLine();
}
await host.StopAsync();
// Runs a workflow and streams events as they occur
async Task RunWorkflowWithStreamingAsync(string orderId, Workflow workflow, IWorkflowClient client)
{
// StreamAsync starts the workflow and returns a handle for observing events
// Cast to DurableStreamingRun for durable-specific features like InstanceId
await using DurableStreamingRun run = (DurableStreamingRun)await client.StreamAsync(workflow, orderId);
Console.WriteLine($"Started: {run.InstanceId}");
// WatchStreamAsync yields events as they're emitted by executors
await foreach (WorkflowEvent evt in run.WatchStreamAsync())
{
// Always print the event type name
WriteColored($" Event: {evt.GetType().Name}", ConsoleColor.Gray);
}
}
void WriteColored(string message, ConsoleColor color)
{
Console.ForegroundColor = color;
Console.WriteLine(message);
Console.ResetColor();
}
@@ -1,23 +0,0 @@
# Workflow Loop Sample
This sample demonstrates how to run a cyclic workflow (containing loops) as a durable orchestration.
## Overview
The workflow iteratively improves a slogan based on AI feedback until it meets quality criteria.
### Executors
- **SloganWriter** - Generates slogans using AI (handles string and FeedbackResult)
- **FeedbackProvider** - Evaluates slogans (calls YieldOutput to accept, SendMessage to loop)
## Key Concepts
- Cyclic Workflow Support (back-edges)
- Multi-Type Executor Handlers
- Message Routing via SendMessageAsync
- Workflow Termination via YieldOutputAsync
## Running
Set AZURE_OPENAI_ENDPOINT and run: dotnet run
@@ -1,26 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace SingleAgent;
public sealed class SloganResult
{
[JsonPropertyName("task")]
public required string Task { get; set; }
[JsonPropertyName("slogan")]
public required string Slogan { get; set; }
}
public sealed class FeedbackResult
{
[JsonPropertyName("comments")]
public string Comments { get; set; } = string.Empty;
[JsonPropertyName("rating")]
public int Rating { get; set; }
[JsonPropertyName("actions")]
public string Actions { get; set; } = string.Empty;
}
@@ -1,61 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json;
using Microsoft.Agents.AI;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Extensions.AI;
namespace SingleAgent;
internal sealed class SloganWriterExecutor : Executor
{
private readonly AIAgent _agent;
private AgentThread? _thread;
/// <summary>
/// Initializes a new instance of the <see cref="SloganWriterExecutor"/> class.
/// </summary>
/// <param name="id">A unique identifier for the executor.</param>
/// <param name="chatClient">The chat client to use for the AI agent.</param>
public SloganWriterExecutor(string id, IChatClient chatClient) : base(id)
{
ChatClientAgentOptions agentOptions = new()
{
ChatOptions = new()
{
Instructions = "You are a professional slogan writer. You will be given a task to create a slogan.",
ResponseFormat = ChatResponseFormat.ForJsonSchema<SloganResult>()
}
};
this._agent = new ChatClientAgent(chatClient, agentOptions);
}
protected override RouteBuilder ConfigureRoutes(RouteBuilder routeBuilder) =>
routeBuilder.AddHandler<string, SloganResult>(this.HandleAsync)
.AddHandler<FeedbackResult, SloganResult>(this.HandleAsync);
public async ValueTask<SloganResult> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
this._thread ??= await this._agent.GetNewThreadAsync(cancellationToken);
var result = await this._agent.RunAsync(message, this._thread, cancellationToken: cancellationToken);
return JsonSerializer.Deserialize<SloganResult>(result.Text) ?? throw new InvalidOperationException("Failed to deserialize slogan result.");
}
public async ValueTask<SloganResult> HandleAsync(FeedbackResult message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
var feedbackMessage = $"""
Here is the feedback on your previous slogan:
Comments: {message.Comments}
Rating: {message.Rating}
Suggested Actions: {message.Actions}
Please use this feedback to improve your slogan.
""";
var result = await this._agent.RunAsync(feedbackMessage, this._thread, cancellationToken: cancellationToken);
return JsonSerializer.Deserialize<SloganResult>(result.Text) ?? throw new InvalidOperationException("Failed to deserialize slogan result.");
}
}
@@ -9,8 +9,6 @@ This directory contains samples for console app hosting of durable agents. These
- **[05_AgentOrchestration_HITL](05_AgentOrchestration_HITL)**: A sample that demonstrates how to implement a human-in-the-loop workflow using durable orchestration, including interactive approval prompts.
- **[06_LongRunningTools](06_LongRunningTools)**: A sample that demonstrates how agents can start and interact with durable orchestrations from tool calls to enable long-running tool scenarios.
- **[07_ReliableStreaming](07_ReliableStreaming)**: A sample that demonstrates how to implement reliable streaming for durable agents using Redis Streams, enabling clients to disconnect and reconnect without losing messages.
- **[08_SingleWorkflow](08_SingleWorkflow)**: A sample that demonstrates how to run a simple workflow as a durable orchestration, showcasing activity durability and automatic resume on restart.
- **[09_SubWorkflows](09_SubWorkflows)**: A sample that demonstrates how to compose workflows hierarchically using sub-workflows, which run as separate orchestration instances visible in the DTS dashboard.
## Running the Samples
@@ -1,22 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.DurableTask.Client.AzureManaged" />
<PackageReference Include="Microsoft.DurableTask.Worker.AzureManaged" />
<PackageReference Include="Microsoft.Extensions.Hosting" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Hosting.AzureFunctions\Microsoft.Agents.AI.Hosting.AzureFunctions.csproj" />
<ProjectReference Include="..\..\..\src\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
</ItemGroup>
</Project>
@@ -1,63 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace WorkflowExecutorsAndEdgesSample;
/// <summary>
/// This sample introduces the concepts of executors and edges in a workflow.
///
/// Workflows are built from executors (processing units) connected by edges (data flow paths).
/// In this example, we create a simple text processing pipeline that:
/// 1. Takes input text and converts it to uppercase using an UppercaseExecutor
/// 2. Takes the uppercase text and reverses it using a ReverseTextExecutor
///
/// The executors are connected sequentially, so data flows from one to the next in order.
/// For input "Hello, World!", the workflow produces "!DLROW ,OLLEH".
/// </summary>
public static class Program
{
private static async Task Main()
{
// Create the executors
Func<string, string> uppercaseFunc = s => s.ToUpperInvariant();
var uppercase = uppercaseFunc.BindAsExecutor("UppercaseExecutor");
ReverseTextExecutor reverse = new();
// Build the workflow by connecting executors sequentially
WorkflowBuilder builder = new(uppercase);
builder.AddEdge(uppercase, reverse).WithOutputFrom(reverse);
var workflow = builder.Build();
// Execute the workflow with input data
await using Run run = await InProcessExecution.RunAsync(workflow, "Hello, World!");
foreach (WorkflowEvent evt in run.NewEvents)
{
if (evt is ExecutorCompletedEvent executorComplete)
{
Console.WriteLine($"{executorComplete.ExecutorId}: {executorComplete.Data}");
}
}
}
}
/// <summary>
/// Second executor: reverses the input text and completes the workflow.
/// </summary>
internal sealed class ReverseTextExecutor() : Executor<string, string>("ReverseTextExecutor")
{
/// <summary>
/// Processes the input message by reversing the text.
/// </summary>
/// <param name="message">The input text to reverse</param>
/// <param name="context">Workflow context for accessing workflow services and adding events</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> to monitor for cancellation requests.
/// The default is <see cref="CancellationToken.None"/>.</param>
/// <returns>The input text reversed</returns>
public override ValueTask<string> HandleAsync(string message, IWorkflowContext context, CancellationToken cancellationToken = default)
{
// Because we do not suppress it, the returned result will be yielded as an output from this executor.
return ValueTask.FromResult(string.Concat(message.Reverse()));
}
}
-4
View File
@@ -1,4 +0,0 @@
/* Visual Studio Settings File */
{
"environment.visualExperience.colorTheme": "dark-plus"
}
@@ -216,7 +216,8 @@ internal class AgentEntity(IServiceProvider services, CancellationToken cancella
private AIAgent GetAgent(AgentSessionId sessionId)
{
IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>> agents = this._options.GetAgentFactories();
IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>> agents =
this._services.GetRequiredService<IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>>>();
if (!agents.TryGetValue(sessionId.Name, out Func<IServiceProvider, AIAgent>? agentFactory))
{
throw new InvalidOperationException($"Agent '{sessionId.Name}' not found");
@@ -10,12 +10,8 @@ public sealed class DurableAgentsOptions
// Agent names are case-insensitive
private readonly Dictionary<string, Func<IServiceProvider, AIAgent>> _agentFactories = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, TimeSpan?> _agentTimeToLive = new(StringComparer.OrdinalIgnoreCase);
private readonly HashSet<string> _workflowOnlyAgents = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Initializes a new instance of the <see cref="DurableAgentsOptions"/> class.
/// </summary>
public DurableAgentsOptions()
internal DurableAgentsOptions()
{
}
@@ -105,22 +101,6 @@ public sealed class DurableAgentsOptions
/// Thrown when <paramref name="agent.Name"/> is null or whitespace or when an agent with the same name has already been registered.
/// </exception>
public DurableAgentsOptions AddAIAgent(AIAgent agent, TimeSpan? timeToLive = null)
{
return this.AddAIAgent(agent, workflowOnly: false, timeToLive);
}
/// <summary>
/// Adds an AI agent to the options with workflow-only configuration.
/// </summary>
/// <param name="agent">The agent to add.</param>
/// <param name="workflowOnly">If true, the agent is only accessible within workflows and won't have HTTP triggers.</param>
/// <param name="timeToLive">Optional time-to-live for this agent's entities. If not specified, uses <see cref="DefaultTimeToLive"/>.</param>
/// <returns>The options instance.</returns>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="agent"/> is null.</exception>
/// <exception cref="ArgumentException">
/// Thrown when <paramref name="agent.Name"/> is null or whitespace or when an agent with the same name has already been registered.
/// </exception>
public DurableAgentsOptions AddAIAgent(AIAgent agent, bool workflowOnly, TimeSpan? timeToLive = null)
{
ArgumentNullException.ThrowIfNull(agent);
@@ -140,11 +120,6 @@ public sealed class DurableAgentsOptions
this._agentTimeToLive[agent.Name] = timeToLive;
}
if (workflowOnly)
{
this._workflowOnlyAgents.Add(agent.Name);
}
return this;
}
@@ -166,24 +141,4 @@ public sealed class DurableAgentsOptions
{
return this._agentTimeToLive.TryGetValue(agentName, out TimeSpan? ttl) ? ttl : this.DefaultTimeToLive;
}
/// <summary>
/// Determines whether an agent is configured as workflow-only (no HTTP triggers).
/// </summary>
/// <param name="agentName">The name of the agent.</param>
/// <returns><see langword="true"/> if the agent is workflow-only; otherwise, <see langword="false"/>.</returns>
internal bool IsWorkflowOnly(string agentName)
{
return this._workflowOnlyAgents.Contains(agentName);
}
/// <summary>
/// Determines whether an agent with the specified name is already registered.
/// </summary>
/// <param name="agentName">The name of the agent.</param>
/// <returns><see langword="true"/> if an agent with the name is registered; otherwise, <see langword="false"/>.</returns>
internal bool ContainsAgent(string agentName)
{
return this._agentFactories.ContainsKey(agentName);
}
}
@@ -1 +0,0 @@

@@ -1,29 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Event raised when an executor requests the workflow to halt via <see cref="IWorkflowContext.RequestHaltAsync"/>.
/// </summary>
/// <remarks>
/// This is the durable equivalent of the internal RequestHaltEvent since that class is not accessible
/// from outside the Workflows assembly.
/// </remarks>
public sealed class DurableHaltRequestedEvent : WorkflowEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="DurableHaltRequestedEvent"/> class.
/// </summary>
/// <param name="executorId">The ID of the executor that requested the halt.</param>
public DurableHaltRequestedEvent(string executorId) : base($"Halt requested by {executorId}")
{
this.ExecutorId = executorId;
}
/// <summary>
/// Gets the ID of the executor that requested the halt.
/// </summary>
public string ExecutorId { get; }
}
@@ -1,30 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides configuration options for durable agents and workflows.
/// </summary>
[DebuggerDisplay("Workflows = {Workflows.Workflows.Count}, Agents = {Agents.AgentCount}")]
public sealed class DurableOptions
{
/// <summary>
/// Gets the configuration options for durable agents.
/// </summary>
public DurableAgentsOptions Agents { get; } = new();
/// <summary>
/// Gets the configuration options for durable workflows.
/// </summary>
public DurableWorkflowOptions Workflows { get; }
/// <summary>
/// Initializes a new instance of the <see cref="DurableOptions"/> class.
/// </summary>
internal DurableOptions()
{
this.Workflows = new DurableWorkflowOptions(this);
}
}
@@ -1,69 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Event raised when the durable workflow is waiting for external input at a <see cref="RequestPort"/>.
/// </summary>
/// <param name="RequestPortId">The ID of the request port waiting for input.</param>
/// <param name="Input">The serialized input data that was passed to the RequestPort.</param>
/// <param name="RequestType">The full type name of the request type.</param>
/// <param name="ResponseType">The full type name of the expected response type.</param>
/// <param name="RequestPort">The request port definition, if available.</param>
[DebuggerDisplay("RequestPort = {RequestPortId}")]
public sealed class DurableRequestInfoEvent(
string RequestPortId,
string Input,
string RequestType,
string ResponseType,
RequestPort? RequestPort) : WorkflowEvent(Input)
{
/// <summary>
/// Gets the ID of the request port waiting for input.
/// </summary>
public string RequestPortId { get; } = RequestPortId;
/// <summary>
/// Gets the serialized input data that was passed to the RequestPort.
/// </summary>
public string Input { get; } = Input;
/// <summary>
/// Gets the full type name of the request type.
/// </summary>
public string RequestType { get; } = RequestType;
/// <summary>
/// Gets the full type name of the expected response type.
/// </summary>
public string ResponseType { get; } = ResponseType;
/// <summary>
/// Gets the request port definition, if available.
/// </summary>
public RequestPort? RequestPort { get; } = RequestPort;
/// <summary>
/// Attempts to deserialize the input data to the specified type.
/// </summary>
/// <typeparam name="T">The type to deserialize to.</typeparam>
/// <returns>The deserialized input, or default if deserialization fails.</returns>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow types provided by the caller.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow types provided by the caller.")]
public T? GetInputAs<T>()
{
try
{
return JsonSerializer.Deserialize<T>(this.Input);
}
catch (JsonException)
{
return default;
}
}
}
@@ -1,242 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents a durable workflow run that tracks execution status and provides access to workflow events.
/// </summary>
/// <remarks>
/// This class provides a similar API to <see cref="Run"/> but for workflows executed as durable orchestrations.
/// Events are received by raising external events to the orchestration and can be streamed to the caller.
/// </remarks>
[DebuggerDisplay("{WorkflowName} ({InstanceId})")]
public sealed class DurableRun : IRun
{
private readonly DurableTaskClient _client;
private readonly List<WorkflowEvent> _eventSink = [];
private int _lastBookmark;
internal DurableRun(DurableTaskClient client, string instanceId, string workflowName)
{
this._client = client;
this.InstanceId = instanceId;
this.WorkflowName = workflowName;
}
/// <summary>
/// Gets the unique instance ID for this orchestration run.
/// </summary>
public string InstanceId { get; }
/// <inheritdoc/>
public string RunId => this.InstanceId;
/// <summary>
/// Gets the name of the workflow being executed.
/// </summary>
public string WorkflowName { get; }
/// <summary>
/// Gets the current execution status of the workflow run.
/// </summary>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>The current status of the durable run.</returns>
public async ValueTask<DurableRunStatus> GetStatusAsync(CancellationToken cancellationToken = default)
{
OrchestrationMetadata? metadata = await this._client.GetInstanceAsync(
this.InstanceId,
getInputsAndOutputs: false,
cancellation: cancellationToken).ConfigureAwait(false);
if (metadata is null)
{
return DurableRunStatus.NotFound;
}
return metadata.RuntimeStatus switch
{
OrchestrationRuntimeStatus.Pending => DurableRunStatus.Pending,
OrchestrationRuntimeStatus.Running => DurableRunStatus.Running,
OrchestrationRuntimeStatus.Completed => DurableRunStatus.Completed,
OrchestrationRuntimeStatus.Failed => DurableRunStatus.Failed,
OrchestrationRuntimeStatus.Terminated => DurableRunStatus.Terminated,
OrchestrationRuntimeStatus.Suspended => DurableRunStatus.Suspended,
_ => DurableRunStatus.Unknown
};
}
/// <summary>
/// Waits for the workflow to complete and returns the result.
/// </summary>
/// <typeparam name="TResult">The expected result type.</typeparam>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>The result of the workflow execution.</returns>
/// <exception cref="InvalidOperationException">Thrown when the workflow failed or was terminated.</exception>
public async ValueTask<TResult?> WaitForCompletionAsync<TResult>(CancellationToken cancellationToken = default)
{
OrchestrationMetadata metadata = await this._client.WaitForInstanceCompletionAsync(
this.InstanceId,
getInputsAndOutputs: true,
cancellation: cancellationToken).ConfigureAwait(false);
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
{
return metadata.ReadOutputAs<TResult>();
}
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Failed)
{
string errorMessage = metadata.FailureDetails?.ErrorMessage ?? "Workflow execution failed.";
throw new InvalidOperationException(errorMessage);
}
throw new InvalidOperationException($"Workflow ended with unexpected status: {metadata.RuntimeStatus}");
}
/// <summary>
/// Waits for the workflow to complete and returns the string result.
/// </summary>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>The string result of the workflow execution.</returns>
public ValueTask<string?> WaitForCompletionAsync(CancellationToken cancellationToken = default)
=> this.WaitForCompletionAsync<string>(cancellationToken);
/// <summary>
/// Sends an external event to the workflow orchestration.
/// </summary>
/// <remarks>
/// This can be used to send responses or messages to the workflow while it's running.
/// The orchestration must be waiting for the event using <c>WaitForExternalEvent</c>.
/// </remarks>
/// <param name="eventName">The name of the event to raise.</param>
/// <param name="eventData">The data to send with the event.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
#pragma warning disable CA1030 // Use events where appropriate - This is intentionally a method that sends events to an orchestration
internal async ValueTask SendExternalEventAsync(string eventName, object? eventData = null, CancellationToken cancellationToken = default)
#pragma warning restore CA1030
{
await this._client.RaiseEventAsync(
this.InstanceId,
eventName,
eventData,
cancellation: cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Sends a workflow event to the orchestration.
/// </summary>
/// <param name="workflowEvent">The workflow event to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
internal ValueTask SendEventAsync(WorkflowEvent workflowEvent, CancellationToken cancellationToken = default)
=> this.SendExternalEventAsync("WorkflowEvent", workflowEvent, cancellationToken);
/// <summary>
/// Sends an external response to the workflow.
/// </summary>
/// <param name="response">The external response to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
public ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default)
=> this.SendExternalEventAsync("ExternalResponse", response, cancellationToken);
/// <summary>
/// Sends a response to a pending request port in the workflow (human-in-the-loop).
/// </summary>
/// <remarks>
/// The response is serialized to JSON before being sent to match what the orchestration expects.
/// Use this method when responding to a <see cref="RequestPort"/> that is waiting for external input.
/// </remarks>
/// <typeparam name="TResponse">The type of the response data.</typeparam>
/// <param name="requestPortId">The ID of the request port to respond to.</param>
/// <param name="response">The response data to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow types provided by the caller.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow types provided by the caller.")]
public ValueTask SendResponseAsync<TResponse>(string requestPortId, TResponse response, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(requestPortId);
// Serialize the response to JSON string - the orchestration expects a string via WaitForExternalEvent<string>
string serializedResponse = JsonSerializer.Serialize(response);
return this.SendExternalEventAsync(requestPortId, serializedResponse, cancellationToken);
}
/// <summary>
/// Gets all events that have been collected from the workflow.
/// </summary>
public IEnumerable<WorkflowEvent> OutgoingEvents => this._eventSink;
/// <summary>
/// Gets the number of events collected since the last access to <see cref="NewEvents"/>.
/// </summary>
public int NewEventCount => this._eventSink.Count - this._lastBookmark;
/// <summary>
/// Gets all events collected since the last access to <see cref="NewEvents"/>.
/// </summary>
public IEnumerable<WorkflowEvent> NewEvents
{
get
{
if (this._lastBookmark >= this._eventSink.Count)
{
return [];
}
int currentBookmark = this._lastBookmark;
this._lastBookmark = this._eventSink.Count;
return this._eventSink.Skip(currentBookmark);
}
}
/// <summary>
/// Adds an event to the local event sink.
/// </summary>
/// <remarks>
/// This is used internally to collect events raised by the orchestration.
/// In the durable scenario, events are typically returned as part of the orchestration output
/// or raised via external events.
/// </remarks>
/// <param name="workflowEvent">The event to add.</param>
internal void AddEvent(WorkflowEvent workflowEvent)
{
this._eventSink.Add(workflowEvent);
}
/// <summary>
/// Terminates the workflow orchestration.
/// </summary>
/// <param name="reason">An optional reason for the termination.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
public async ValueTask TerminateAsync(string? reason = null, CancellationToken cancellationToken = default)
{
await this._client.TerminateInstanceAsync(
this.InstanceId,
reason,
cancellation: cancellationToken).ConfigureAwait(false);
}
/// <summary>
/// Purges the orchestration instance history.
/// </summary>
/// <param name="cancellationToken">A cancellation token to observe.</param>
public async ValueTask PurgeAsync(CancellationToken cancellationToken = default)
{
await this._client.PurgeInstanceAsync(
this.InstanceId,
cancellation: cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public ValueTask DisposeAsync()
{
// Nothing to dispose for durable runs - the orchestration continues independently
return default;
}
}
@@ -1,49 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents the execution status of a durable workflow run.
/// </summary>
public enum DurableRunStatus
{
/// <summary>
/// The orchestration instance was not found.
/// </summary>
NotFound,
/// <summary>
/// The orchestration is pending and has not started.
/// </summary>
Pending,
/// <summary>
/// The orchestration is currently running.
/// </summary>
Running,
/// <summary>
/// The orchestration completed successfully.
/// </summary>
Completed,
/// <summary>
/// The orchestration failed with an error.
/// </summary>
Failed,
/// <summary>
/// The orchestration was terminated.
/// </summary>
Terminated,
/// <summary>
/// The orchestration is suspended.
/// </summary>
Suspended,
/// <summary>
/// The orchestration status is unknown.
/// </summary>
Unknown
}
@@ -1,477 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Text.Json;
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents a durable workflow run that supports streaming workflow events as they occur.
/// </summary>
/// <remarks>
/// This class provides a similar API to <see cref="StreamingRun"/> but for workflows executed as durable orchestrations.
/// Events are detected by monitoring the orchestration status for <see cref="RequestPort"/> executors that are waiting
/// for external input (human-in-the-loop scenarios).
/// </remarks>
[DebuggerDisplay("{WorkflowName} ({InstanceId})")]
public sealed class DurableStreamingRun : IStreamingRun
{
private readonly DurableTaskClient _client;
private readonly Workflow _workflow;
private readonly List<RequestPort> _requestPorts;
internal DurableStreamingRun(DurableTaskClient client, string instanceId, Workflow workflow)
{
this._client = client;
this.InstanceId = instanceId;
this._workflow = workflow;
// Extract RequestPorts from the workflow for event detection
this._requestPorts = ExtractRequestPorts(workflow);
}
/// <summary>
/// Gets the unique instance ID for this orchestration run.
/// </summary>
public string InstanceId { get; }
/// <inheritdoc/>
public string RunId => this.InstanceId;
/// <summary>
/// Gets the name of the workflow being executed.
/// </summary>
public string WorkflowName => this._workflow.Name ?? string.Empty;
/// <summary>
/// Gets the request ports defined in the workflow.
/// </summary>
public IReadOnlyList<RequestPort> RequestPorts => this._requestPorts;
/// <summary>
/// Gets the current execution status of the workflow run.
/// </summary>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>The current status of the durable run.</returns>
public async ValueTask<DurableRunStatus> GetStatusAsync(CancellationToken cancellationToken = default)
{
OrchestrationMetadata? metadata = await this._client.GetInstanceAsync(
this.InstanceId,
getInputsAndOutputs: false,
cancellation: cancellationToken).ConfigureAwait(false);
if (metadata is null)
{
return DurableRunStatus.NotFound;
}
return metadata.RuntimeStatus switch
{
OrchestrationRuntimeStatus.Pending => DurableRunStatus.Pending,
OrchestrationRuntimeStatus.Running => DurableRunStatus.Running,
OrchestrationRuntimeStatus.Completed => DurableRunStatus.Completed,
OrchestrationRuntimeStatus.Failed => DurableRunStatus.Failed,
OrchestrationRuntimeStatus.Terminated => DurableRunStatus.Terminated,
OrchestrationRuntimeStatus.Suspended => DurableRunStatus.Suspended,
_ => DurableRunStatus.Unknown
};
}
/// <inheritdoc/>
public IAsyncEnumerable<WorkflowEvent> WatchStreamAsync(CancellationToken cancellationToken = default)
=> this.WatchStreamAsync(pollingInterval: null, cancellationToken);
/// <summary>
/// Asynchronously streams workflow events as they occur during workflow execution.
/// </summary>
/// <remarks>
/// <para>
/// This method monitors the durable orchestration and yields <see cref="WorkflowEvent"/> instances
/// when the workflow reaches points that require external input (human-in-the-loop scenarios).
/// </para>
/// <para>
/// When the orchestration reaches a <see cref="RequestPort"/> executor, a <see cref="DurableRequestInfoEvent"/>
/// is yielded containing the request data. The caller should then call <see cref="SendResponseAsync{TResponse}(DurableRequestInfoEvent, TResponse, CancellationToken)"/>
/// to provide the response and continue the workflow.
/// </para>
/// </remarks>
/// <param name="pollingInterval">The interval between status checks. Defaults to 500ms.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An asynchronous stream of <see cref="WorkflowEvent"/> objects.</returns>
public async IAsyncEnumerable<WorkflowEvent> WatchStreamAsync(
TimeSpan? pollingInterval,
[EnumeratorCancellation] CancellationToken cancellationToken = default)
{
TimeSpan interval = pollingInterval ?? TimeSpan.FromMilliseconds(500);
// Track which request ports we've already yielded events for and are waiting for response
// Key: EventName (RequestPort ID), Value: Input data (to detect if we're at a different invocation)
Dictionary<string, string> pendingRequests = [];
// Track how many events we've already read from custom status
int lastReadEventIndex = 0;
while (!cancellationToken.IsCancellationRequested)
{
OrchestrationMetadata? metadata = await this._client.GetInstanceAsync(
this.InstanceId,
getInputsAndOutputs: true,
cancellation: cancellationToken).ConfigureAwait(false);
if (metadata is null)
{
yield break;
}
// Check if the orchestration has completed
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
{
yield return new DurableWorkflowCompletedEvent(metadata.SerializedOutput);
yield break;
}
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Failed)
{
string errorMessage = metadata.FailureDetails?.ErrorMessage ?? "Workflow execution failed.";
yield return new DurableWorkflowFailedEvent(errorMessage);
yield break;
}
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Terminated)
{
yield return new DurableWorkflowFailedEvent("Workflow was terminated.");
yield break;
}
// Check custom status for events and pending external events
if (metadata.SerializedCustomStatus is not null)
{
DurableWorkflowCustomStatus? customStatus = TryParseCustomStatus(metadata.SerializedCustomStatus);
if (customStatus is not null)
{
// Yield any new events from executors
while (lastReadEventIndex < customStatus.Events.Count)
{
string serializedEvent = customStatus.Events[lastReadEventIndex];
lastReadEventIndex++;
WorkflowEvent? workflowEvent = TryDeserializeEvent(serializedEvent);
if (workflowEvent is not null)
{
yield return workflowEvent;
}
}
// Check for pending external event (HITL)
if (customStatus.PendingEvent is not null)
{
PendingExternalEventStatus pendingStatus = customStatus.PendingEvent;
string eventName = pendingStatus.EventName;
string inputData = pendingStatus.Input;
// Only yield a new event if:
// 1. We haven't seen this event name before, OR
// 2. The input data is different (meaning this is a new invocation of the same RequestPort)
bool shouldYield = !pendingRequests.TryGetValue(eventName, out string? previousInput)
|| previousInput != inputData;
if (shouldYield)
{
pendingRequests[eventName] = inputData;
// Find the matching RequestPort
RequestPort? requestPort = this._requestPorts.Find(p => p.Id == eventName);
yield return new DurableRequestInfoEvent(
RequestPortId: eventName,
Input: inputData,
RequestType: pendingStatus.RequestType,
ResponseType: pendingStatus.ResponseType,
RequestPort: requestPort);
}
}
}
else
{
// Try parsing as legacy PendingExternalEventStatus for backward compatibility
PendingExternalEventStatus? pendingStatus = TryParsePendingStatus(metadata.SerializedCustomStatus);
if (pendingStatus is not null)
{
string eventName = pendingStatus.EventName;
string inputData = pendingStatus.Input;
bool shouldYield = !pendingRequests.TryGetValue(eventName, out string? previousInput)
|| previousInput != inputData;
if (shouldYield)
{
pendingRequests[eventName] = inputData;
RequestPort? requestPort = this._requestPorts.Find(p => p.Id == eventName);
yield return new DurableRequestInfoEvent(
RequestPortId: eventName,
Input: inputData,
RequestType: pendingStatus.RequestType,
ResponseType: pendingStatus.ResponseType,
RequestPort: requestPort);
}
}
}
}
else
{
// Custom status is null - the orchestration is not waiting for external input
// Clear any pending requests that were waiting (they've been processed)
pendingRequests.Clear();
}
await Task.Delay(interval, cancellationToken).ConfigureAwait(false);
}
}
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow custom status.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow custom status.")]
private static DurableWorkflowCustomStatus? TryParseCustomStatus(string serializedStatus)
{
try
{
return JsonSerializer.Deserialize<DurableWorkflowCustomStatus>(serializedStatus);
}
catch (JsonException)
{
return null;
}
}
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow event types.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow event types.")]
[UnconditionalSuppressMessage("Trimming", "IL2057", Justification = "Event types are registered at startup and available at runtime.")]
private static WorkflowEvent? TryDeserializeEvent(string serializedEvent)
{
try
{
// First try to deserialize as SerializedWorkflowEvent (new format with type info)
SerializedWorkflowEvent? wrapper =
JsonSerializer.Deserialize<SerializedWorkflowEvent>(serializedEvent);
if (wrapper?.TypeName is not null && wrapper.Data is not null)
{
Type? eventType = Type.GetType(wrapper.TypeName);
if (eventType is not null)
{
// Use custom deserialization for event types with constructor parameter mismatches
return DeserializeEventByType(eventType, wrapper.Data);
}
}
// Fall back to deserializing as base WorkflowEvent (legacy format)
return JsonSerializer.Deserialize<WorkflowEvent>(serializedEvent);
}
catch (JsonException)
{
return null;
}
}
/// <summary>
/// Deserializes an event by type, handling constructor parameter name mismatches.
/// </summary>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow event types.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow event types.")]
private static WorkflowEvent? DeserializeEventByType(Type eventType, string json)
{
using JsonDocument doc = JsonDocument.Parse(json);
JsonElement root = doc.RootElement;
// Handle ExecutorInvokedEvent: constructor expects (executorId, message) but JSON has (ExecutorId, Data)
if (eventType == typeof(ExecutorInvokedEvent))
{
string executorId = root.GetProperty("ExecutorId").GetString() ?? string.Empty;
JsonElement? data = GetDataProperty(root);
return new ExecutorInvokedEvent(executorId, data!);
}
// Handle ExecutorCompletedEvent: constructor expects (executorId, result) but JSON has (ExecutorId, Data)
if (eventType == typeof(ExecutorCompletedEvent))
{
string executorId = root.GetProperty("ExecutorId").GetString() ?? string.Empty;
JsonElement? data = GetDataProperty(root);
return new ExecutorCompletedEvent(executorId, data);
}
// For other event types, try standard deserialization with case-insensitive options
return JsonSerializer.Deserialize(json, eventType, s_caseInsensitiveOptions) as WorkflowEvent;
}
// Cached JsonSerializerOptions for case-insensitive deserialization
private static readonly JsonSerializerOptions s_caseInsensitiveOptions = new() { PropertyNameCaseInsensitive = true };
/// <summary>
/// Gets the Data property from a JSON element.
/// </summary>
private static JsonElement? GetDataProperty(JsonElement root)
{
if (!root.TryGetProperty("Data", out JsonElement dataElement))
{
return null;
}
if (dataElement.ValueKind == JsonValueKind.Null)
{
return null;
}
return dataElement.Clone();
}
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing known type PendingExternalEventStatus.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing known type PendingExternalEventStatus.")]
private static PendingExternalEventStatus? TryParsePendingStatus(string serializedStatus)
{
try
{
return JsonSerializer.Deserialize<PendingExternalEventStatus>(serializedStatus);
}
catch (JsonException)
{
return null;
}
}
/// <summary>
/// Sends an external event to the workflow orchestration.
/// </summary>
/// <param name="eventName">The name of the event to raise (typically the <see cref="RequestPort.Id"/>).</param>
/// <param name="eventData">The data to send with the event.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
#pragma warning disable CA1030 // Use events where appropriate
public async ValueTask SendExternalEventAsync(string eventName, object? eventData = null, CancellationToken cancellationToken = default)
#pragma warning restore CA1030
{
await this._client.RaiseEventAsync(
this.InstanceId,
eventName,
eventData,
cancellation: cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default)
=> this.SendExternalEventAsync("ExternalResponse", response, cancellationToken);
/// <summary>
/// Sends a response to a pending request in the workflow.
/// </summary>
/// <remarks>
/// The response is serialized to JSON before being sent to match what the orchestration expects.
/// </remarks>
/// <typeparam name="TResponse">The type of the response data.</typeparam>
/// <param name="requestPortId">The ID of the request port to respond to.</param>
/// <param name="response">The response data to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow types provided by the caller.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow types provided by the caller.")]
public ValueTask SendResponseAsync<TResponse>(string requestPortId, TResponse response, CancellationToken cancellationToken = default)
{
ArgumentException.ThrowIfNullOrEmpty(requestPortId);
// Serialize the response to JSON string - the orchestration expects a string via WaitForExternalEvent<string>
string serializedResponse = JsonSerializer.Serialize(response);
return this.SendExternalEventAsync(requestPortId, serializedResponse, cancellationToken);
}
/// <summary>
/// Sends a response to a <see cref="DurableRequestInfoEvent"/>.
/// </summary>
/// <remarks>
/// The response is serialized to JSON before being sent to match what the orchestration expects.
/// </remarks>
/// <typeparam name="TResponse">The type of the response data.</typeparam>
/// <param name="requestEvent">The request event to respond to.</param>
/// <param name="response">The response data to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow types provided by the caller.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow types provided by the caller.")]
public ValueTask SendResponseAsync<TResponse>(DurableRequestInfoEvent requestEvent, TResponse response, CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(requestEvent);
// Serialize the response to JSON string - the orchestration expects a string via WaitForExternalEvent<string>
string serializedResponse = JsonSerializer.Serialize(response);
return this.SendExternalEventAsync(requestEvent.RequestPortId, serializedResponse, cancellationToken);
}
/// <summary>
/// Waits for the workflow to complete and returns the result.
/// </summary>
/// <typeparam name="TResult">The expected result type.</typeparam>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>The result of the workflow execution.</returns>
public async ValueTask<TResult?> WaitForCompletionAsync<TResult>(CancellationToken cancellationToken = default)
{
OrchestrationMetadata metadata = await this._client.WaitForInstanceCompletionAsync(
this.InstanceId,
getInputsAndOutputs: true,
cancellation: cancellationToken).ConfigureAwait(false);
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Completed)
{
return metadata.ReadOutputAs<TResult>();
}
if (metadata.RuntimeStatus == OrchestrationRuntimeStatus.Failed)
{
string errorMessage = metadata.FailureDetails?.ErrorMessage ?? "Workflow execution failed.";
throw new InvalidOperationException(errorMessage);
}
throw new InvalidOperationException($"Workflow ended with unexpected status: {metadata.RuntimeStatus}");
}
/// <summary>
/// Waits for the workflow to complete and returns the string result.
/// </summary>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>The string result of the workflow execution.</returns>
public ValueTask<string?> WaitForCompletionAsync(CancellationToken cancellationToken = default)
=> this.WaitForCompletionAsync<string>(cancellationToken);
/// <summary>
/// Terminates the workflow orchestration.
/// </summary>
/// <param name="reason">An optional reason for the termination.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
public async ValueTask TerminateAsync(string? reason = null, CancellationToken cancellationToken = default)
{
await this._client.TerminateInstanceAsync(
this.InstanceId,
reason,
cancellation: cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc/>
public ValueTask DisposeAsync()
{
// Nothing to dispose for durable runs - the orchestration continues independently
return default;
}
private static List<RequestPort> ExtractRequestPorts(Workflow workflow)
{
List<RequestPort> requestPorts = [];
foreach (WorkflowExecutorInfo executorInfo in WorkflowHelper.GetExecutorsFromWorkflowInOrder(workflow))
{
if (executorInfo.RequestPort is not null)
{
requestPorts.Add(executorInfo.RequestPort);
}
}
return requestPorts;
}
}
@@ -1,158 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides methods to run workflows as durable orchestrations.
/// </summary>
internal static class DurableWorkflow
{
/// <summary>
/// Runs a workflow as a durable orchestration and returns a handle to monitor its execution.
/// </summary>
/// <typeparam name="TInput">The type of the input to the workflow.</typeparam>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The input to pass to the workflow's starting executor.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
/// <exception cref="ArgumentNullException">Thrown when workflow or client is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public static async ValueTask<IRun> RunAsync<TInput>(
Workflow workflow,
TInput input,
DurableTaskClient client,
string? instanceId = null,
CancellationToken cancellationToken = default)
where TInput : notnull
{
ArgumentNullException.ThrowIfNull(workflow);
ArgumentNullException.ThrowIfNull(client);
if (string.IsNullOrEmpty(workflow.Name))
{
throw new ArgumentException("Workflow must have a valid Name property.", nameof(workflow));
}
string orchestrationName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflow.Name);
string actualInstanceId = await client.ScheduleNewOrchestrationInstanceAsync(
orchestratorName: orchestrationName,
input: input,
cancellation: cancellationToken).ConfigureAwait(false);
return new DurableRun(client, actualInstanceId, workflow.Name);
}
/// <summary>
/// Runs a workflow as a durable orchestration with string input.
/// </summary>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The string input to pass to the workflow.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public static ValueTask<IRun> RunAsync(
Workflow workflow,
string input,
DurableTaskClient client,
string? instanceId = null,
CancellationToken cancellationToken = default)
=> RunAsync<string>(workflow, input, client, instanceId, cancellationToken);
/// <summary>
/// Starts a workflow as a durable orchestration and returns a streaming handle to watch events.
/// </summary>
/// <typeparam name="TInput">The type of the input to the workflow.</typeparam>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The input to pass to the workflow's starting executor.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
/// <exception cref="ArgumentNullException">Thrown when workflow or client is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public static async ValueTask<IStreamingRun> StreamAsync<TInput>(
Workflow workflow,
TInput input,
DurableTaskClient client,
string? instanceId = null,
CancellationToken cancellationToken = default)
where TInput : notnull
{
ArgumentNullException.ThrowIfNull(workflow);
ArgumentNullException.ThrowIfNull(client);
if (string.IsNullOrEmpty(workflow.Name))
{
throw new ArgumentException("Workflow must have a valid Name property.", nameof(workflow));
}
string orchestrationName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflow.Name);
string actualInstanceId = await client.ScheduleNewOrchestrationInstanceAsync(
orchestratorName: orchestrationName,
input: input,
cancellation: cancellationToken).ConfigureAwait(false);
return new DurableStreamingRun(client, actualInstanceId, workflow);
}
/// <summary>
/// Starts a workflow as a durable orchestration with string input and returns a streaming handle.
/// </summary>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The string input to pass to the workflow.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <param name="instanceId">Optional instance ID for the orchestration.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public static ValueTask<IStreamingRun> StreamAsync(
Workflow workflow,
string input,
DurableTaskClient client,
string? instanceId = null,
CancellationToken cancellationToken = default)
=> StreamAsync<string>(workflow, input, client, instanceId, cancellationToken);
/// <summary>
/// Attaches to an existing workflow orchestration instance.
/// </summary>
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflowName">The name of the workflow being executed.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public static IRun Attach(
string instanceId,
string workflowName,
DurableTaskClient client)
{
ArgumentException.ThrowIfNullOrEmpty(instanceId);
ArgumentException.ThrowIfNullOrEmpty(workflowName);
ArgumentNullException.ThrowIfNull(client);
return new DurableRun(client, instanceId, workflowName);
}
/// <summary>
/// Attaches to an existing workflow orchestration instance for streaming.
/// </summary>
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflow">The workflow being executed.</param>
/// <param name="client">The durable task client for orchestration operations.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public static IStreamingRun AttachStream(
string instanceId,
Workflow workflow,
DurableTaskClient client)
{
ArgumentException.ThrowIfNullOrEmpty(instanceId);
ArgumentNullException.ThrowIfNull(workflow);
ArgumentNullException.ThrowIfNull(client);
return new DurableStreamingRun(client, instanceId, workflow);
}
}
@@ -1,87 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
using Microsoft.DurableTask.Client;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides a durable task-based implementation of <see cref="IWorkflowClient"/> for running
/// workflows as durable orchestrations.
/// </summary>
/// <remarks>
/// This class wraps the <see cref="DurableTaskClient"/> and provides methods to run workflows
/// without requiring the client to be passed explicitly. Register this class in DI using
/// <see cref="DurableWorkflowServiceCollectionExtensions.ConfigureDurableWorkflows"/>.
/// </remarks>
public sealed class DurableWorkflowClient : IWorkflowClient
{
private readonly DurableTaskClient _client;
/// <summary>
/// Initializes a new instance of the <see cref="DurableWorkflowClient"/> class.
/// </summary>
/// <param name="client">The durable task client for orchestration operations.</param>
public DurableWorkflowClient(DurableTaskClient client)
{
this._client = client ?? throw new ArgumentNullException(nameof(client));
}
/// <inheritdoc/>
public ValueTask<IRun> RunAsync<TInput>(
Workflow workflow,
TInput input,
string? runId = null,
CancellationToken cancellationToken = default)
where TInput : notnull
=> DurableWorkflow.RunAsync(workflow, input, this._client, runId, cancellationToken);
/// <inheritdoc/>
public ValueTask<IRun> RunAsync(
Workflow workflow,
string input,
string? runId = null,
CancellationToken cancellationToken = default)
=> DurableWorkflow.RunAsync(workflow, input, this._client, runId, cancellationToken);
/// <inheritdoc/>
public ValueTask<IStreamingRun> StreamAsync<TInput>(
Workflow workflow,
TInput input,
string? runId = null,
CancellationToken cancellationToken = default)
where TInput : notnull
=> DurableWorkflow.StreamAsync(workflow, input, this._client, runId, cancellationToken);
/// <inheritdoc/>
public ValueTask<IStreamingRun> StreamAsync(
Workflow workflow,
string input,
string? runId = null,
CancellationToken cancellationToken = default)
=> DurableWorkflow.StreamAsync(workflow, input, this._client, runId, cancellationToken);
/// <summary>
/// Attaches to an existing workflow orchestration instance.
/// </summary>
/// <remarks>
/// This is a durable-specific method not available on <see cref="IWorkflowClient"/>.
/// </remarks>
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflowName">The name of the workflow being executed.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
public IRun Attach(string instanceId, string workflowName)
=> DurableWorkflow.Attach(instanceId, workflowName, this._client);
/// <summary>
/// Attaches to an existing workflow orchestration instance for streaming.
/// </summary>
/// <remarks>
/// This is a durable-specific method not available on <see cref="IWorkflowClient"/>.
/// </remarks>
/// <param name="instanceId">The instance ID of the orchestration to attach to.</param>
/// <param name="workflow">The workflow being executed.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
public IStreamingRun AttachStream(string instanceId, Workflow workflow)
=> DurableWorkflow.AttachStream(instanceId, workflow, this._client);
}
@@ -1,27 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Event raised when a durable workflow completes successfully.
/// </summary>
[DebuggerDisplay("Completed: {Result}")]
public sealed class DurableWorkflowCompletedEvent : WorkflowEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="DurableWorkflowCompletedEvent"/> class.
/// </summary>
/// <param name="result">The serialized result of the workflow.</param>
public DurableWorkflowCompletedEvent(string? result) : base(result)
{
this.Result = result;
}
/// <summary>
/// Gets the serialized result of the workflow.
/// </summary>
public string? Result { get; }
}
@@ -1,27 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Event raised when a durable workflow fails.
/// </summary>
[DebuggerDisplay("Failed: {ErrorMessage}")]
public sealed class DurableWorkflowFailedEvent : WorkflowEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="DurableWorkflowFailedEvent"/> class.
/// </summary>
/// <param name="errorMessage">The error message describing the failure.</param>
public DurableWorkflowFailedEvent(string errorMessage) : base(errorMessage)
{
this.ErrorMessage = errorMessage;
}
/// <summary>
/// Gets the error message describing the failure.
/// </summary>
public string ErrorMessage { get; }
}
@@ -1,42 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Text.Json.Serialization;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Source-generated JSON serialization context for durable workflow types.
/// </summary>
/// <remarks>
/// <para>
/// This context provides AOT-compatible and trimmer-safe JSON serialization for the
/// internal data transfer types used by the durable workflow infrastructure:
/// </para>
/// <list type="bullet">
/// <item><description><see cref="DurableActivityInput"/>: Activity input wrapper with state</description></item>
/// <item><description><see cref="DurableActivityOutput"/>: Activity output wrapper with results and events</description></item>
/// <item><description><see cref="SentMessageInfo"/>: Messages sent via SendMessageAsync</description></item>
/// <item><description><see cref="SerializedWorkflowEvent"/>: Workflow event wrapper</description></item>
/// <item><description><see cref="ActivityInputWithState"/>: Orchestrator-to-activity input wrapper</description></item>
/// </list>
/// <para>
/// Note: User-defined executor input/output types still use reflection-based serialization
/// since their types are not known at compile time.
/// </para>
/// </remarks>
[JsonSourceGenerationOptions(
WriteIndented = false,
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull,
PropertyNamingPolicy = JsonKnownNamingPolicy.CamelCase)]
[JsonSerializable(typeof(DurableActivityInput))]
[JsonSerializable(typeof(DurableActivityOutput))]
[JsonSerializable(typeof(SentMessageInfo))]
[JsonSerializable(typeof(List<SentMessageInfo>))]
[JsonSerializable(typeof(SerializedWorkflowEvent))]
[JsonSerializable(typeof(ActivityInputWithState))]
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(Dictionary<string, string>))]
[JsonSerializable(typeof(Dictionary<string, string?>))]
internal partial class DurableWorkflowJsonContext : JsonSerializerContext
{
}
@@ -1,101 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides configuration options for managing durable workflows within an application.
/// </summary>
[DebuggerDisplay("Workflows = {Workflows.Count}")]
public sealed class DurableWorkflowOptions
{
private readonly Dictionary<string, Workflow> _workflows = new(StringComparer.OrdinalIgnoreCase);
private readonly DurableOptions? _parentOptions;
/// <summary>
/// Initializes a new instance of the <see cref="DurableWorkflowOptions"/> class.
/// </summary>
/// <param name="parentOptions">Optional parent options container for accessing related configuration.</param>
internal DurableWorkflowOptions(DurableOptions? parentOptions = null)
{
this._parentOptions = parentOptions;
this.Executors = new ExecutorRegistry();
}
/// <summary>
/// Gets the collection of workflows available in the current context, keyed by their unique names.
/// </summary>
public IReadOnlyDictionary<string, Workflow> Workflows => this._workflows;
/// <summary>
/// Gets the executor registry for direct executor lookup.
/// </summary>
internal ExecutorRegistry Executors { get; }
/// <summary>
/// Adds a workflow to the collection for processing or execution.
/// </summary>
/// <param name="workflow">The workflow instance to add. Cannot be null.</param>
/// <remarks>
/// When a workflow is added, any AI agent executors in the workflow will be automatically
/// registered with the <see cref="DurableAgentsOptions"/> if it was provided during construction.
/// </remarks>
/// <exception cref="ArgumentNullException">Thrown when <paramref name="workflow"/> is null.</exception>
/// <exception cref="ArgumentException">Thrown when the workflow does not have a valid name.</exception>
public void AddWorkflow(Workflow workflow)
{
ArgumentNullException.ThrowIfNull(workflow);
if (string.IsNullOrEmpty(workflow.Name))
{
throw new ArgumentException("Workflow must have a valid Name property.", nameof(workflow));
}
this._workflows[workflow.Name] = workflow;
RegisterExecutors(workflow, this.Executors);
DurableAgentsOptions? agentOptions = this._parentOptions?.Agents;
if (agentOptions is not null)
{
RegisterAgenticExecutors(workflow, agentOptions);
}
}
/// <summary>
/// Adds a collection of workflows to the current instance.
/// </summary>
/// <param name="workflows">The collection of <see cref="Workflow"/> objects to add. Cannot be <see langword="null"/>.</param>
public void AddWorkflows(IEnumerable<Workflow> workflows)
{
ArgumentNullException.ThrowIfNull(workflows);
foreach (var workflow in workflows)
{
this.AddWorkflow(workflow);
}
}
private static void RegisterExecutors(Workflow workflow, ExecutorRegistry registry)
{
foreach (KeyValuePair<string, ExecutorBinding> executor in workflow.ReflectExecutors())
{
int underscoreIndex = executor.Key.IndexOf('_');
string executorName = underscoreIndex > 0 ? executor.Key[..underscoreIndex] : executor.Key;
registry.Register(executorName, executor.Key, workflow);
}
}
private static void RegisterAgenticExecutors(Workflow workflow, DurableAgentsOptions agentOptions)
{
foreach (KeyValuePair<string, ExecutorBinding> executor in workflow.ReflectExecutors())
{
if (executor.Value.RawValue is AIAgent agent && agent.Name is not null && !agentOptions.ContainsAgent(agent.Name))
{
agentOptions.AddAIAgent(agent, workflowOnly: true);
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,513 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using System.Text.Json.Serialization.Metadata;
using Microsoft.Agents.AI.DurableTask.State;
using Microsoft.Agents.AI.Workflows;
using Microsoft.Agents.AI.Workflows.Checkpointing;
using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Extension methods for configuring durable workflows with the service collection.
/// </summary>
public static class DurableWorkflowServiceCollectionExtensions
{
/// <summary>
/// Configures durable workflows with the service collection, automatically registering
/// orchestrations and activities for each workflow.
/// </summary>
/// <param name="services">The service collection to configure.</param>
/// <param name="configure">A delegate to configure the durable options.</param>
/// <param name="workerBuilder">An optional delegate to configure the durable task worker.</param>
/// <param name="clientBuilder">An optional delegate to configure the durable task client.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection ConfigureDurableWorkflows(
this IServiceCollection services,
Action<DurableOptions> configure,
Action<IDurableTaskWorkerBuilder>? workerBuilder = null,
Action<IDurableTaskClientBuilder>? clientBuilder = null)
{
ArgumentNullException.ThrowIfNull(services);
ArgumentNullException.ThrowIfNull(configure);
// Create and configure durable options
DurableOptions durableOptions = new();
configure(durableOptions);
// Register DurableOptions as a singleton
services.AddSingleton(durableOptions);
// Register the workflow runner
services.AddSingleton<DurableWorkflowRunner>();
// Build registration info for all workflows (including sub-workflows)
List<WorkflowRegistrationInfo> registrations = [];
HashSet<string> registeredActivities = [];
HashSet<string> registeredOrchestrations = [];
// Take a snapshot of the workflows to avoid collection modified during enumeration
// (sub-workflows are added to the collection during recursive registration)
foreach (Workflow workflow in durableOptions.Workflows.Workflows.Values.ToList())
{
BuildWorkflowRegistrationRecursive(
workflow,
durableOptions.Workflows,
registrations,
registeredActivities,
registeredOrchestrations);
}
// Get any AI agents that were auto-registered from workflows
IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>> agentFactories = durableOptions.Agents.GetAgentFactories();
// Configure Durable Task Worker with orchestrations and activities
services.AddDurableTaskWorker(builder =>
{
workerBuilder?.Invoke(builder);
builder.AddTasks(registry =>
{
// Register all workflow tasks
foreach (WorkflowRegistrationInfo registration in registrations)
{
// Register orchestration
registry.AddOrchestratorFunc<string, string>(
registration.OrchestrationName,
(context, input) => RunWorkflowOrchestrationAsync(context, input, durableOptions));
// Register activities
foreach (ActivityRegistrationInfo activity in registration.Activities)
{
ExecutorBinding binding = activity.Binding;
registry.AddActivityFunc<string, string>(
activity.ActivityName,
(context, input) => ExecuteActivityAsync(binding, input));
}
}
// Register agent entities for any AI agents used in workflows
foreach (string agentName in agentFactories.Keys)
{
registry.AddEntity<AgentEntity>(AgentSessionId.ToEntityName(agentName));
}
});
});
// Register DurableAgentsOptions and agent factories for entity resolution
if (agentFactories.Count > 0)
{
services.AddSingleton(durableOptions.Agents);
// Register the agent factories dictionary for backward compatibility
services.TryAddSingleton(
sp => sp.GetRequiredService<DurableAgentsOptions>().GetAgentFactories());
// A custom data converter is needed for proper JSON serialization
services.TryAddSingleton<DataConverter, WorkflowDataConverter>();
}
// Configure Durable Task Client if a builder is provided
if (clientBuilder is not null)
{
services.AddDurableTaskClient(clientBuilder);
}
// Register the DurableWorkflowClient for DI-friendly workflow execution
// Register both the concrete type and the interface
services.TryAddSingleton<DurableWorkflowClient>();
services.TryAddSingleton<IWorkflowClient>(sp => sp.GetRequiredService<DurableWorkflowClient>());
return services;
}
/// <summary>
/// Recursively builds workflow registrations, including any sub-workflows.
/// Also adds sub-workflows to the workflow options so they can be looked up at runtime.
/// </summary>
/// <param name="workflow">The workflow to register.</param>
/// <param name="workflowOptions">The workflow options to add sub-workflows to.</param>
/// <param name="registrations">The list to add registrations to.</param>
/// <param name="registeredActivities">Set of already registered activity names to avoid duplicates.</param>
/// <param name="registeredOrchestrations">Set of already registered orchestration names to avoid duplicates.</param>
private static void BuildWorkflowRegistrationRecursive(
Workflow workflow,
DurableWorkflowOptions workflowOptions,
List<WorkflowRegistrationInfo> registrations,
HashSet<string> registeredActivities,
HashSet<string> registeredOrchestrations)
{
string orchestrationName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflow.Name!);
// Skip if this workflow is already registered (handles circular references)
if (!registeredOrchestrations.Add(orchestrationName))
{
return;
}
// Build registration for this workflow
registrations.Add(BuildWorkflowRegistration(workflow, registeredActivities));
// Recursively register any sub-workflows
foreach (KeyValuePair<string, ExecutorBinding> entry in workflow.ReflectExecutors())
{
if (entry.Value is SubworkflowBinding subworkflowBinding)
{
Workflow subWorkflow = subworkflowBinding.WorkflowInstance;
// Add sub-workflow to options so it can be looked up by the runner at runtime
workflowOptions.AddWorkflow(subWorkflow);
BuildWorkflowRegistrationRecursive(
subWorkflow,
workflowOptions,
registrations,
registeredActivities,
registeredOrchestrations);
}
}
}
private static WorkflowRegistrationInfo BuildWorkflowRegistration(
Workflow workflow,
HashSet<string> registeredActivities)
{
string workflowName = workflow.Name!;
string orchestrationName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
Dictionary<string, ExecutorBinding> executorBindings = workflow.ReflectExecutors();
List<ActivityRegistrationInfo> activities = [];
foreach (KeyValuePair<string, ExecutorBinding> entry in executorBindings)
{
// Skip agent executors - they're handled differently
if (entry.Value is AIAgentBinding)
{
continue;
}
// Skip sub-workflow executors - they're handled as sub-orchestrations
if (entry.Value is SubworkflowBinding)
{
continue;
}
string executorName = WorkflowNamingHelper.GetExecutorName(entry.Key);
string activityName = WorkflowNamingHelper.ToOrchestrationFunctionName(executorName);
// Skip if already registered (same executor used in multiple workflows)
if (!registeredActivities.Add(activityName))
{
continue;
}
activities.Add(new ActivityRegistrationInfo(activityName, entry.Value));
}
return new WorkflowRegistrationInfo(orchestrationName, activities);
}
private static async Task<string> RunWorkflowOrchestrationAsync(
TaskOrchestrationContext context,
string input,
DurableOptions durableOptions)
{
ILogger logger = context.CreateReplaySafeLogger("WorkflowOrchestration");
DurableWorkflowRunner runner = new(
NullLoggerFactory.Instance.CreateLogger<DurableWorkflowRunner>(),
durableOptions);
return await runner.RunWorkflowOrchestrationAsync(context, input, logger).ConfigureAwait(true);
}
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", Justification = "Executor types are registered at startup.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "Executor types are registered at startup.")]
private static async Task<string> ExecuteActivityAsync(ExecutorBinding binding, string input)
{
DurableActivityInput? inputWithState = TryDeserializeActivityInput(input);
string executorInput = inputWithState?.Input ?? input;
Dictionary<string, string> sharedState = inputWithState?.State ?? [];
Executor executor = await binding.FactoryAsync!("activity-run").ConfigureAwait(false);
Type inputType = ResolveInputType(inputWithState?.InputTypeName, executor.InputTypes);
object typedInput = DeserializeInput(executorInput, inputType);
PipelineActivityContext workflowContext = new(sharedState, executor);
object? result = await executor.ExecuteAsync(
typedInput,
new TypeId(inputType),
workflowContext,
CancellationToken.None).ConfigureAwait(false);
return SerializeActivityOutput(result, workflowContext);
}
/// <summary>
/// Serializes the activity output using source-generated serialization.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", Justification = "SerializeResult uses reflection for user types.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "SerializeResult uses reflection for user types.")]
private static string SerializeActivityOutput(object? result, PipelineActivityContext context)
{
DurableActivityOutput output = new()
{
Result = SerializeResult(result),
StateUpdates = context.StateUpdates,
ClearedScopes = [.. context.ClearedScopes],
Events = context.Events.ConvertAll(SerializeEvent),
SentMessages = context.SentMessages.ConvertAll(m => new SentMessageInfo
{
Message = m.Message,
TypeName = m.TypeName
})
};
return JsonSerializer.Serialize(output, DurableWorkflowJsonContext.Default.DurableActivityOutput);
}
/// <summary>
/// Resolves the input type from the provided type name, or falls back to the first supported type.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", Justification = "Type resolution for registered executor types.")]
[UnconditionalSuppressMessage("Trimming", "IL2057:TypeGetType", Justification = "Type resolution for registered executor types.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "Type resolution for registered executor types.")]
private static Type ResolveInputType(string? inputTypeName, ISet<Type> supportedTypes)
{
if (string.IsNullOrEmpty(inputTypeName))
{
return supportedTypes.FirstOrDefault() ?? typeof(string);
}
// Try to find a matching type in the supported types
Type? matchedType = supportedTypes.FirstOrDefault(t =>
t.AssemblyQualifiedName == inputTypeName ||
t.FullName == inputTypeName ||
t.Name == inputTypeName);
if (matchedType is not null)
{
return matchedType;
}
// Try to load the type directly (for types not in supported types)
Type? loadedType = Type.GetType(inputTypeName);
// If the loaded type is string but the executor doesn't support string,
// fall back to the first supported type. This handles the case where
// serialized JSON objects are passed with type "System.String" but need
// to be deserialized to the actual expected type (e.g., OrderInfo).
if (loadedType == typeof(string) && !supportedTypes.Contains(typeof(string)))
{
return supportedTypes.FirstOrDefault() ?? typeof(string);
}
return loadedType ?? supportedTypes.FirstOrDefault() ?? typeof(string);
}
/// <summary>
/// Attempts to deserialize activity input using source-generated serialization.
/// </summary>
private static DurableActivityInput? TryDeserializeActivityInput(string input)
{
try
{
return JsonSerializer.Deserialize(input, DurableWorkflowJsonContext.Default.DurableActivityInput);
}
catch (JsonException)
{
return null;
}
}
/// <summary>
/// Serializes a workflow event with type information.
/// </summary>
/// <remarks>
/// The event data itself uses reflection-based serialization since event types
/// are user-defined, but the wrapper uses source generation.
/// </remarks>
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", Justification = "Event data uses reflection for user types.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "Event data uses reflection for user types.")]
private static string SerializeEvent(WorkflowEvent evt)
{
// Serialize with type information so we can deserialize to the correct type later
SerializedWorkflowEvent wrapper = new()
{
TypeName = evt.GetType().AssemblyQualifiedName,
Data = JsonSerializer.Serialize(evt, evt.GetType())
};
return JsonSerializer.Serialize(wrapper, DurableWorkflowJsonContext.Default.SerializedWorkflowEvent);
}
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", Justification = "Deserializing workflow types registered at startup.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "Deserializing workflow types registered at startup.")]
private static object DeserializeInput(string input, Type targetType)
{
if (targetType == typeof(string))
{
return input;
}
return JsonSerializer.Deserialize(input, targetType)
?? throw new InvalidOperationException($"Failed to deserialize input to type '{targetType.Name}'.");
}
[UnconditionalSuppressMessage("Trimming", "IL2026:RequiresUnreferencedCode", Justification = "Serializing workflow types registered at startup.")]
[UnconditionalSuppressMessage("AOT", "IL3050:RequiresDynamicCode", Justification = "Serializing workflow types registered at startup.")]
private static string SerializeResult(object? result)
{
if (result is null)
{
return string.Empty;
}
if (result is string str)
{
return str;
}
return JsonSerializer.Serialize(result, result.GetType());
}
private sealed record WorkflowRegistrationInfo(string OrchestrationName, List<ActivityRegistrationInfo> Activities);
private sealed record ActivityRegistrationInfo(string ActivityName, ExecutorBinding Binding);
/// <summary>
/// Custom data converter for workflow execution with AI agents.
/// </summary>
private sealed class WorkflowDataConverter : DataConverter
{
private static readonly JsonSerializerOptions s_options = new(DurableAgentJsonUtilities.DefaultOptions)
{
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
PropertyNameCaseInsensitive = true,
};
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Fallback path uses reflection when metadata unavailable.")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "Fallback path uses reflection when metadata unavailable.")]
public override object? Deserialize(string? data, Type targetType)
{
if (data is null)
{
return null;
}
if (targetType == typeof(DurableAgentState))
{
return JsonSerializer.Deserialize(data, DurableAgentStateJsonContext.Default.DurableAgentState);
}
return TryDeserializeWithTypeInfo(data, targetType)
?? JsonSerializer.Deserialize(data, targetType, s_options);
}
[return: NotNullIfNotNull(nameof(value))]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Fallback path uses reflection when metadata unavailable.")]
[UnconditionalSuppressMessage("ReflectionAnalysis", "IL3050", Justification = "Fallback path uses reflection when metadata unavailable.")]
public override string? Serialize(object? value)
{
if (value is null)
{
return null;
}
if (value is DurableAgentState durableAgentState)
{
return JsonSerializer.Serialize(durableAgentState, DurableAgentStateJsonContext.Default.DurableAgentState);
}
return TrySerializeWithTypeInfo(value)
?? JsonSerializer.Serialize(value, s_options);
}
private static object? TryDeserializeWithTypeInfo(string data, Type targetType)
{
JsonTypeInfo? typeInfo = s_options.GetTypeInfo(targetType);
return typeInfo is not null ? JsonSerializer.Deserialize(data, typeInfo) : null;
}
private static string? TrySerializeWithTypeInfo(object value)
{
JsonTypeInfo? typeInfo = s_options.GetTypeInfo(value.GetType());
return typeInfo is not null ? JsonSerializer.Serialize(value, typeInfo) : null;
}
}
}
/// <summary>
/// Input payload for activity execution, containing the executor input and shared workflow state.
/// </summary>
internal sealed class DurableActivityInput
{
/// <summary>
/// Gets or sets the serialized executor input.
/// </summary>
public string? Input { get; set; }
/// <summary>
/// Gets or sets the assembly-qualified type name of the input, used for proper deserialization.
/// </summary>
public string? InputTypeName { get; set; }
/// <summary>
/// Gets or sets the shared state dictionary (scope-prefixed key -> serialized value).
/// </summary>
public Dictionary<string, string> State { get; set; } = [];
}
/// <summary>
/// Output payload from activity execution, containing the result, state updates, and emitted events.
/// </summary>
internal sealed class DurableActivityOutput
{
/// <summary>
/// Gets or sets the serialized result of the activity.
/// </summary>
public string? Result { get; set; }
/// <summary>
/// Gets or sets state updates made during activity execution (scope-prefixed key -> serialized value, null = delete).
/// </summary>
public Dictionary<string, string?> StateUpdates { get; set; } = [];
/// <summary>
/// Gets or sets scopes that were cleared during activity execution.
/// </summary>
public List<string> ClearedScopes { get; set; } = [];
/// <summary>
/// Gets or sets the serialized workflow events emitted during activity execution.
/// </summary>
public List<string> Events { get; set; } = [];
/// <summary>
/// Gets or sets messages sent via SendMessageAsync during activity execution.
/// Each entry is a tuple of (serializedMessage, typeName).
/// </summary>
public List<SentMessageInfo> SentMessages { get; set; } = [];
}
/// <summary>
/// Information about a message sent via SendMessageAsync.
/// </summary>
internal sealed class SentMessageInfo
{
/// <summary>
/// Gets or sets the serialized message content.
/// </summary>
public string? Message { get; set; }
/// <summary>
/// Gets or sets the full type name of the message.
/// </summary>
public string? TypeName { get; set; }
}
@@ -1,38 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Event raised when an executor yields intermediate output via <see cref="IWorkflowContext.YieldOutputAsync"/>.
/// </summary>
/// <remarks>
/// This is the durable equivalent of <see cref="WorkflowOutputEvent"/> since that class has an internal
/// constructor not accessible from outside the Workflows assembly.
/// </remarks>
[DebuggerDisplay("Yielded by {ExecutorId}: {Output}")]
public sealed class DurableYieldedOutputEvent : WorkflowEvent
{
/// <summary>
/// Initializes a new instance of the <see cref="DurableYieldedOutputEvent"/> class.
/// </summary>
/// <param name="executorId">The ID of the executor that yielded the output.</param>
/// <param name="output">The yielded output value.</param>
public DurableYieldedOutputEvent(string executorId, object output) : base(output)
{
this.ExecutorId = executorId;
this.Output = output;
}
/// <summary>
/// Gets the ID of the executor that yielded the output.
/// </summary>
public string ExecutorId { get; }
/// <summary>
/// Gets the yielded output value.
/// </summary>
public object Output { get; }
}
@@ -1,74 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides a registry for storing and retrieving executor bindings independently from workflows.
/// </summary>
internal sealed class ExecutorRegistry
{
private readonly Dictionary<string, ExecutorRegistration> _executors = new(StringComparer.OrdinalIgnoreCase);
/// <summary>
/// Gets the number of registered executors.
/// </summary>
public int Count => this._executors.Count;
/// <summary>
/// Attempts to get an executor registration by name.
/// </summary>
/// <param name="executorName">The executor name to look up.</param>
/// <param name="registration">When this method returns, contains the registration if found; otherwise, null.</param>
/// <returns><see langword="true"/> if the executor was found; otherwise, <see langword="false"/>.</returns>
public bool TryGetExecutor(string executorName, out ExecutorRegistration? registration)
{
return this._executors.TryGetValue(executorName, out registration);
}
/// <summary>
/// Registers an executor binding from a workflow.
/// </summary>
/// <param name="executorName">The executor name (without GUID suffix).</param>
/// <param name="executorId">The full executor ID (may include GUID suffix).</param>
/// <param name="workflow">The workflow containing the executor.</param>
internal void Register(string executorName, string executorId, Workflow workflow)
{
ArgumentException.ThrowIfNullOrEmpty(executorName);
ArgumentException.ThrowIfNullOrEmpty(executorId);
ArgumentNullException.ThrowIfNull(workflow);
Dictionary<string, ExecutorBinding> bindings = workflow.ReflectExecutors();
if (!bindings.TryGetValue(executorId, out ExecutorBinding? binding))
{
throw new InvalidOperationException($"Executor '{executorId}' not found in workflow.");
}
this._executors.TryAdd(executorName, new ExecutorRegistration(executorId, binding));
}
}
/// <summary>
/// Represents a registered executor with its associated workflow.
/// </summary>
/// <param name="ExecutorId">The full executor ID (may include GUID suffix).</param>
/// <param name="Binding">The executor binding from the workflow.</param>
internal sealed record ExecutorRegistration(string ExecutorId, ExecutorBinding Binding)
{
/// <summary>
/// Creates an instance of the executor.
/// </summary>
/// <param name="runId">A unique identifier for the run context.</param>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>The created executor instance.</returns>
public async ValueTask<Executor> CreateExecutorInstanceAsync(string runId, CancellationToken cancellationToken = default)
{
if (this.Binding.FactoryAsync is null)
{
throw new InvalidOperationException($"Cannot create executor '{this.ExecutorId}': Binding is a placeholder.");
}
return await this.Binding.FactoryAsync(runId).ConfigureAwait(false);
}
}
@@ -1,53 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents a workflow run that tracks execution status and emitted workflow events,
/// supporting resumption with responses to external requests.
/// </summary>
/// <remarks>
/// This interface defines the common contract for workflow runs across different execution
/// environments (in-process, durable, etc.). Implementations provide the mechanism to
/// interact with running workflows, send responses, and access emitted events.
/// </remarks>
public interface IRun : IAsyncDisposable
{
/// <summary>
/// Gets the unique identifier for the run.
/// </summary>
/// <remarks>
/// This identifier can be provided at the start of the run, or auto-generated.
/// For durable runs, this corresponds to the orchestration instance ID.
/// </remarks>
string RunId { get; }
/// <summary>
/// Gets all events that have been emitted by the workflow.
/// </summary>
IEnumerable<WorkflowEvent> OutgoingEvents { get; }
/// <summary>
/// Gets the number of events emitted since the last access to <see cref="NewEvents"/>.
/// </summary>
int NewEventCount { get; }
/// <summary>
/// Gets all events emitted by the workflow since the last access to this property.
/// </summary>
/// <remarks>
/// Each access to this property advances the bookmark, so subsequent accesses
/// will only return events emitted after the previous access.
/// </remarks>
IEnumerable<WorkflowEvent> NewEvents { get; }
/// <summary>
/// Sends an external response to the workflow.
/// </summary>
/// <param name="response">The external response to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default);
}
@@ -1,52 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents a workflow run that supports streaming workflow events as they occur,
/// providing a mechanism to send responses back to the workflow.
/// </summary>
/// <remarks>
/// This interface defines the common contract for streaming workflow runs across different
/// execution environments (in-process, durable, etc.). Implementations provide real-time
/// access to workflow events and the ability to respond to external requests.
/// </remarks>
public interface IStreamingRun : IAsyncDisposable
{
/// <summary>
/// Gets the unique identifier for the run.
/// </summary>
/// <remarks>
/// This identifier can be provided at the start of the run, or auto-generated.
/// For durable runs, this corresponds to the orchestration instance ID.
/// </remarks>
string RunId { get; }
/// <summary>
/// Asynchronously streams workflow events as they occur during workflow execution.
/// </summary>
/// <remarks>
/// This method yields <see cref="WorkflowEvent"/> instances in real time as the workflow
/// progresses. The stream completes when the workflow completes, fails, or is terminated.
/// Events are delivered in the order they are raised.
/// </remarks>
/// <param name="cancellationToken">
/// A <see cref="CancellationToken"/> that can be used to cancel the streaming operation.
/// If cancellation is requested, the stream will end and no further events will be yielded.
/// </param>
/// <returns>
/// An asynchronous stream of <see cref="WorkflowEvent"/> objects representing significant
/// workflow state changes.
/// </returns>
IAsyncEnumerable<WorkflowEvent> WatchStreamAsync(CancellationToken cancellationToken = default);
/// <summary>
/// Sends an external response to the workflow.
/// </summary>
/// <param name="response">The external response to send.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>A <see cref="ValueTask"/> representing the asynchronous operation.</returns>
ValueTask SendResponseAsync(ExternalResponse response, CancellationToken cancellationToken = default);
}
@@ -1,76 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Defines a client for running and managing workflow executions.
/// </summary>
/// <remarks>
/// This interface provides a unified API for running workflows across different execution
/// environments. Implementations handle the underlying infrastructure details, allowing
/// callers to work with workflows without knowledge of the specific execution backend.
/// </remarks>
public interface IWorkflowClient
{
/// <summary>
/// Runs a workflow and returns a handle to monitor its execution.
/// </summary>
/// <typeparam name="TInput">The type of the input to the workflow.</typeparam>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The input to pass to the workflow's starting executor.</param>
/// <param name="runId">Optional identifier for the run. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
ValueTask<IRun> RunAsync<TInput>(
Workflow workflow,
TInput input,
string? runId = null,
CancellationToken cancellationToken = default)
where TInput : notnull;
/// <summary>
/// Runs a workflow with string input and returns a handle to monitor its execution.
/// </summary>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The string input to pass to the workflow.</param>
/// <param name="runId">Optional identifier for the run. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IRun"/> that can be used to monitor the workflow execution.</returns>
ValueTask<IRun> RunAsync(
Workflow workflow,
string input,
string? runId = null,
CancellationToken cancellationToken = default);
/// <summary>
/// Starts a workflow and returns a streaming handle to watch events.
/// </summary>
/// <typeparam name="TInput">The type of the input to the workflow.</typeparam>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The input to pass to the workflow's starting executor.</param>
/// <param name="runId">Optional identifier for the run. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
ValueTask<IStreamingRun> StreamAsync<TInput>(
Workflow workflow,
TInput input,
string? runId = null,
CancellationToken cancellationToken = default)
where TInput : notnull;
/// <summary>
/// Starts a workflow with string input and returns a streaming handle to watch events.
/// </summary>
/// <param name="workflow">The workflow to execute.</param>
/// <param name="input">The string input to pass to the workflow.</param>
/// <param name="runId">Optional identifier for the run. If not provided, a new ID will be generated.</param>
/// <param name="cancellationToken">A cancellation token to observe.</param>
/// <returns>An <see cref="IStreamingRun"/> that can be used to stream workflow events.</returns>
ValueTask<IStreamingRun> StreamAsync(
Workflow workflow,
string input,
string? runId = null,
CancellationToken cancellationToken = default);
}
@@ -100,52 +100,4 @@ internal static partial class Logs
public static partial void LogTTLExpirationTimeCleared(
this ILogger logger,
AgentSessionId sessionId);
[LoggerMessage(
EventId = 12,
Level = LogLevel.Information,
Message = "Attempting to run workflow: {WorkflowName}")]
public static partial void LogAttemptingToRunWorkflow(this ILogger logger, string workflowName);
[LoggerMessage(
EventId = 13,
Level = LogLevel.Warning,
Message = "Running workflow: {WorkflowName}")]
public static partial void LogRunningWorkflow(this ILogger logger, string? workflowName);
[LoggerMessage(
EventId = 14,
Level = LogLevel.Warning,
Message = "Attempting to execute activity in workflow '{WorkflowName}' for executor '{ExecutorName}'")]
public static partial void LogAttemptingToExecuteActivity(this ILogger logger, string workflowName, string executorName);
[LoggerMessage(
EventId = 15,
Level = LogLevel.Warning,
Message = "Executing activity for executor '{ExecutorId}' of type '{ExecutorType}'")]
public static partial void LogExecutingActivity(this ILogger logger, string executorId, string executorType);
[LoggerMessage(
EventId = 16,
Level = LogLevel.Warning,
Message = "Activity executed for executor '{ExecutorId}' with result: {Result}")]
public static partial void LogActivityExecuted(this ILogger logger, string executorId, string result);
[LoggerMessage(
EventId = 17,
Level = LogLevel.Debug,
Message = "Executor '{ExecutorId}' skipped due to edge condition evaluation")]
public static partial void LogExecutorSkipped(this ILogger logger, string executorId);
[LoggerMessage(
EventId = 18,
Level = LogLevel.Debug,
Message = "Waiting for external event '{EventName}' with input: {Input}")]
public static partial void LogWaitingForExternalEvent(this ILogger logger, string eventName, string input);
[LoggerMessage(
EventId = 19,
Level = LogLevel.Debug,
Message = "Received external event '{EventName}' with response: {Response}")]
public static partial void LogReceivedExternalEvent(this ILogger logger, string eventName, string response);
}
@@ -21,11 +21,9 @@
<ItemGroup>
<PackageReference Include="Microsoft.DurableTask.Client" />
<PackageReference Include="Microsoft.DurableTask.Worker" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Microsoft.Agents.AI.Workflows\Microsoft.Agents.AI.Workflows.csproj" />
<ProjectReference Include="..\Microsoft.Agents.AI\Microsoft.Agents.AI.csproj" />
</ItemGroup>
@@ -1,265 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// A workflow context for activity execution that uses pipeline-based state management.
/// State is passed in from the orchestration and updates are collected for return.
/// </summary>
internal sealed class PipelineActivityContext : IWorkflowContext
{
private readonly Dictionary<string, string> _initialState;
private readonly Executor _executor;
/// <summary>
/// Initializes a new instance of the <see cref="PipelineActivityContext"/> class.
/// </summary>
/// <param name="initialState">The shared state passed from the orchestration.</param>
/// <param name="executor">The executor running in this context.</param>
public PipelineActivityContext(Dictionary<string, string>? initialState, Executor executor)
{
this._initialState = initialState ?? [];
this._executor = executor;
}
/// <summary>
/// Gets the events that were added during activity execution.
/// </summary>
public List<WorkflowEvent> Events { get; } = [];
/// <summary>
/// Gets the state updates made during activity execution.
/// </summary>
public Dictionary<string, string?> StateUpdates { get; } = [];
/// <summary>
/// Gets the scopes that were cleared during activity execution.
/// </summary>
public HashSet<string> ClearedScopes { get; } = [];
/// <summary>
/// Gets the messages sent during activity execution via SendMessageAsync.
/// Each entry is (serializedMessage, typeName).
/// </summary>
public List<(string Message, string TypeName)> SentMessages { get; } = [];
/// <inheritdoc/>
public ValueTask AddEventAsync(WorkflowEvent workflowEvent, CancellationToken cancellationToken = default)
{
if (workflowEvent is not null)
{
this.Events.Add(workflowEvent);
}
return default;
}
/// <inheritdoc/>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow message types.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow message types.")]
public ValueTask SendMessageAsync(object message, string? targetId = null, CancellationToken cancellationToken = default)
{
if (message is not null)
{
// Capture the message and its type for routing in the orchestrator
string serializedMessage = JsonSerializer.Serialize(message, message.GetType());
string typeName = message.GetType().FullName ?? message.GetType().Name;
this.SentMessages.Add((serializedMessage, typeName));
}
return default;
}
/// <inheritdoc/>
public ValueTask YieldOutputAsync(object output, CancellationToken cancellationToken = default)
{
if (output is not null)
{
// Validate output type matches executor's declared output types (same as in-process execution)
if (!CanOutput(this._executor.OutputTypes, output.GetType()))
{
throw new InvalidOperationException(
$"Cannot output object of type {output.GetType().Name}. " +
$"Expecting one of [{string.Join(", ", this._executor.OutputTypes)}].");
}
this.Events.Add(new DurableYieldedOutputEvent(this._executor.Id, output));
}
return default;
}
/// <inheritdoc/>
public ValueTask RequestHaltAsync()
{
this.Events.Add(new DurableHaltRequestedEvent(this._executor.Id));
return default;
}
/// <inheritdoc/>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow state types.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow state types.")]
public ValueTask<T?> ReadStateAsync<T>(string key, string? scopeName = null, CancellationToken cancellationToken = default)
{
string scopeKey = GetScopeKey(scopeName, key);
string normalizedScope = scopeName ?? "__default__";
// Check if scope was cleared
if (this.ClearedScopes.Contains(normalizedScope))
{
// Only return from updates made after clear
if (this.StateUpdates.TryGetValue(scopeKey, out string? updatedAfterClear) && updatedAfterClear is not null)
{
return ValueTask.FromResult(JsonSerializer.Deserialize<T>(updatedAfterClear));
}
return ValueTask.FromResult<T?>(default);
}
// Check local updates first (read-your-writes)
if (this.StateUpdates.TryGetValue(scopeKey, out string? updated))
{
if (updated is null)
{
return ValueTask.FromResult<T?>(default);
}
return ValueTask.FromResult(JsonSerializer.Deserialize<T>(updated));
}
// Fall back to initial state passed from orchestration
if (this._initialState.TryGetValue(scopeKey, out string? initial))
{
return ValueTask.FromResult(JsonSerializer.Deserialize<T>(initial));
}
return ValueTask.FromResult<T?>(default);
}
/// <inheritdoc/>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Deserializing workflow state types.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Deserializing workflow state types.")]
public async ValueTask<T> ReadOrInitStateAsync<T>(string key, Func<T> initialStateFactory, string? scopeName = null, CancellationToken cancellationToken = default)
{
T? value = await this.ReadStateAsync<T>(key, scopeName, cancellationToken).ConfigureAwait(false);
if (value is not null)
{
return value;
}
// Initialize with factory value
T initialValue = initialStateFactory();
await this.QueueStateUpdateAsync(key, initialValue, scopeName, cancellationToken).ConfigureAwait(false);
return initialValue;
}
/// <inheritdoc/>
public ValueTask<HashSet<string>> ReadStateKeysAsync(string? scopeName = null, CancellationToken cancellationToken = default)
{
string scopePrefix = GetScopePrefix(scopeName);
string normalizedScope = scopeName ?? "__default__";
HashSet<string> keys = [];
// If scope was cleared, only return keys from updates made after clear
if (this.ClearedScopes.Contains(normalizedScope))
{
foreach (KeyValuePair<string, string?> update in this.StateUpdates)
{
if (update.Key.StartsWith(scopePrefix, StringComparison.Ordinal) && update.Value is not null)
{
keys.Add(update.Key[scopePrefix.Length..]);
}
}
return ValueTask.FromResult(keys);
}
// Start with keys from initial state
foreach (string stateKey in this._initialState.Keys)
{
if (stateKey.StartsWith(scopePrefix, StringComparison.Ordinal))
{
keys.Add(stateKey[scopePrefix.Length..]);
}
}
// Merge with updates
foreach (KeyValuePair<string, string?> update in this.StateUpdates)
{
if (update.Key.StartsWith(scopePrefix, StringComparison.Ordinal))
{
string foundKey = update.Key[scopePrefix.Length..];
if (update.Value is not null)
{
keys.Add(foundKey);
}
else
{
keys.Remove(foundKey);
}
}
}
return ValueTask.FromResult(keys);
}
/// <inheritdoc/>
[UnconditionalSuppressMessage("AOT", "IL3050", Justification = "Serializing workflow state types.")]
[UnconditionalSuppressMessage("Trimming", "IL2026", Justification = "Serializing workflow state types.")]
public ValueTask QueueStateUpdateAsync<T>(string key, T? value, string? scopeName = null, CancellationToken cancellationToken = default)
{
string scopeKey = GetScopeKey(scopeName, key);
this.StateUpdates[scopeKey] = value is null ? null : JsonSerializer.Serialize(value);
return default;
}
/// <inheritdoc/>
public ValueTask QueueClearScopeAsync(string? scopeName = null, CancellationToken cancellationToken = default)
{
string normalizedScope = scopeName ?? "__default__";
this.ClearedScopes.Add(normalizedScope);
// Remove any pending updates in this scope
string scopePrefix = GetScopePrefix(scopeName);
List<string> keysToRemove = this.StateUpdates.Keys
.Where(k => k.StartsWith(scopePrefix, StringComparison.Ordinal))
.ToList();
foreach (string key in keysToRemove)
{
this.StateUpdates.Remove(key);
}
return default;
}
/// <inheritdoc/>
public IReadOnlyDictionary<string, string>? TraceContext => null;
/// <inheritdoc/>
public bool ConcurrentRunsEnabled => false;
private static bool CanOutput(ISet<Type> outputTypes, Type messageType)
{
foreach (Type type in outputTypes)
{
if (type.IsAssignableFrom(messageType))
{
return true;
}
}
return false;
}
private static string GetScopeKey(string? scopeName, string key)
=> $"{GetScopePrefix(scopeName)}{key}";
private static string GetScopePrefix(string? scopeName)
=> scopeName is null ? "__default__:" : $"{scopeName}:";
}
@@ -8,7 +8,6 @@ using Microsoft.DurableTask;
using Microsoft.DurableTask.Client;
using Microsoft.DurableTask.Worker;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
namespace Microsoft.Agents.AI.DurableTask;
@@ -81,45 +80,23 @@ public static class ServiceCollectionExtensions
DurableAgentsOptions options = new();
configure(options);
IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>> newAgents = options.GetAgentFactories();
IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>> agents = options.GetAgentFactories();
// Check if we already have DurableAgentsOptions registered and merge with it
ServiceDescriptor? existingOptionsDescriptor = services.FirstOrDefault(
d => d.ServiceType == typeof(DurableAgentsOptions));
// The agent dictionary contains the real agent factories, which is used by the agent entities.
services.AddSingleton(agents);
if (existingOptionsDescriptor?.ImplementationInstance is DurableAgentsOptions existingOptions)
{
// Merge new agents into the existing options
foreach (KeyValuePair<string, Func<IServiceProvider, AIAgent>> agent in newAgents)
{
if (!existingOptions.ContainsAgent(agent.Key))
{
existingOptions.AddAIAgentFactory(agent.Key, agent.Value, options.GetTimeToLive(agent.Key));
}
}
options = existingOptions;
}
else
{
// Register the options so AgentEntity can access configuration
services.AddSingleton(options);
}
// Register the options so AgentEntity can access TTL configuration
services.AddSingleton(options);
// The keyed services are used to resolve durable agent *proxy* instances for external clients.
foreach (var factory in newAgents)
foreach (var factory in agents)
{
services.AddKeyedSingleton(factory.Key, (sp, _) => factory.Value(sp).AsDurableAgentProxy(sp));
}
// Register the agent factories dictionary for backward compatibility.
// This allows consumers to retrieve agents via services.GetService<IReadOnlyDictionary<string, Func<IServiceProvider, AIAgent>>>().
services.TryAddSingleton(
sp => sp.GetRequiredService<DurableAgentsOptions>().GetAgentFactories());
// A custom data converter is needed because the default chat client uses camel case for JSON properties,
// which is not the default behavior for the Durable Task SDK.
services.TryAddSingleton<DataConverter, DefaultDataConverter>();
services.AddSingleton<DataConverter, DefaultDataConverter>();
return options;
}
@@ -1,43 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents the complete execution plan for a workflow, including parallel execution levels.
/// </summary>
[DebuggerDisplay("Start = {StartExecutorId}, Levels = {Levels.Count}")]
internal sealed class WorkflowExecutionPlan
{
/// <summary>
/// The execution levels in order. Each level contains executors that can run in parallel.
/// </summary>
public List<WorkflowExecutionLevel> Levels { get; } = [];
/// <summary>
/// Maps each executor ID to its predecessors (for Fan-In result aggregation).
/// </summary>
public Dictionary<string, List<string>> Predecessors { get; } = [];
/// <summary>
/// Maps each executor ID to its successors (for Fan-Out result distribution).
/// </summary>
public Dictionary<string, List<string>> Successors { get; } = [];
/// <summary>
/// Maps edge connections (sourceId, targetId) to their condition functions.
/// The condition function takes the predecessor's result and returns true if the edge should be followed.
/// </summary>
public Dictionary<(string SourceId, string TargetId), Func<object?, bool>?> EdgeConditions { get; } = [];
/// <summary>
/// Maps executor IDs to their output types (for proper deserialization during condition evaluation).
/// </summary>
public Dictionary<string, Type?> ExecutorOutputTypes { get; } = [];
/// <summary>
/// Gets or sets the starting executor ID for the workflow.
/// </summary>
public string StartExecutorId { get; set; } = string.Empty;
}
@@ -1,350 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using System.Diagnostics;
using Microsoft.Agents.AI.Workflows;
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Represents an executor in the workflow with its metadata.
/// </summary>
/// <param name="ExecutorId">The unique identifier of the executor.</param>
/// <param name="IsAgenticExecutor">Indicates whether this executor is an agentic executor.</param>
/// <param name="RequestPort">The request port if this executor is a request port executor; otherwise, null.</param>
/// <param name="SubWorkflow">The sub-workflow if this executor is a sub-workflow executor; otherwise, null.</param>
[DebuggerDisplay("{ExecutorId}, Agentic = {IsAgenticExecutor}, HITL = {IsRequestPortExecutor}, SubWorkflow = {IsSubworkflowExecutor}")]
internal sealed record WorkflowExecutorInfo(string ExecutorId, bool IsAgenticExecutor, RequestPort? RequestPort = null, Workflow? SubWorkflow = null)
{
/// <summary>
/// Gets a value indicating whether this executor is a request port executor (human-in-the-loop).
/// </summary>
public bool IsRequestPortExecutor => this.RequestPort is not null;
/// <summary>
/// Gets a value indicating whether this executor is a sub-workflow executor.
/// </summary>
public bool IsSubworkflowExecutor => this.SubWorkflow is not null;
}
/// <summary>
/// Represents a level of executors that can be executed in parallel (Fan-Out).
/// All executors in the same level have their dependencies satisfied by previous levels.
/// </summary>
/// <param name="Level">The level number (0-based, starting from the root executor).</param>
/// <param name="Executors">The executors that can run in parallel at this level.</param>
/// <param name="IsFanIn">Indicates if this level is a Fan-In point (has executors with multiple predecessors).</param>
[DebuggerDisplay("Level {Level}: {Executors.Count} executor(s), FanIn = {IsFanIn}")]
internal sealed record WorkflowExecutionLevel(int Level, List<WorkflowExecutorInfo> Executors, bool IsFanIn);
/// <summary>
/// Provides helper methods for analyzing and executing workflows.
/// </summary>
internal static class WorkflowHelper
{
/// <summary>
/// Accepts a workflow instance and returns a list of executors with metadata in the order they should be executed.
/// </summary>
/// <param name="workflow">The workflow instance to analyze.</param>
/// <returns>A list of executor information in topological order (execution order).</returns>
public static List<WorkflowExecutorInfo> GetExecutorsFromWorkflowInOrder(Workflow workflow)
{
WorkflowExecutionPlan plan = GetExecutionPlan(workflow);
// Flatten the levels into a single list for backward compatibility
List<WorkflowExecutorInfo> result = [];
foreach (WorkflowExecutionLevel level in plan.Levels)
{
result.AddRange(level.Executors);
}
return result;
}
/// <summary>
/// Analyzes the workflow and returns an execution plan that supports Fan-Out/Fan-In patterns.
/// Executors at the same level can be executed in parallel (Fan-Out).
/// Fan-In points are identified where multiple executors converge.
/// Cyclic workflows are detected and marked for message-driven execution.
/// </summary>
/// <param name="workflow">The workflow instance to analyze.</param>
/// <returns>An execution plan with parallel execution levels.</returns>
public static WorkflowExecutionPlan GetExecutionPlan(Workflow workflow)
{
ArgumentNullException.ThrowIfNull(workflow);
Dictionary<string, ExecutorBinding> executors = workflow.ReflectExecutors();
Dictionary<string, HashSet<Edge>> edges = workflow.Edges;
WorkflowExecutionPlan plan = new()
{
StartExecutorId = workflow.StartExecutorId
};
// Build adjacency lists (successors and predecessors)
Dictionary<string, List<string>> successors = new(executors.Count);
Dictionary<string, List<string>> predecessors = new(executors.Count);
Dictionary<string, int> executorIndex = new(executors.Count);
// Initialize all executors and extract their output types
int index = 0;
foreach (KeyValuePair<string, ExecutorBinding> executor in executors)
{
executorIndex[executor.Key] = index++;
successors[executor.Key] = [];
predecessors[executor.Key] = [];
// Extract output type from executor type (e.g., Executor<TInput, TOutput> -> TOutput)
plan.ExecutorOutputTypes[executor.Key] = GetExecutorOutputType(executor.Value.ExecutorType);
}
// Build the graph from edges and extract edge conditions
Dictionary<(string SourceId, string TargetId), Func<object?, bool>?> edgeConditions = [];
foreach (KeyValuePair<string, HashSet<Edge>> edgeGroup in edges)
{
string sourceId = edgeGroup.Key;
List<string> sourceSuccessors = successors[sourceId];
foreach (Edge edge in edgeGroup.Value)
{
foreach (string sinkId in edge.Data.Connection.SinkIds)
{
if (executorIndex.ContainsKey(sinkId))
{
sourceSuccessors.Add(sinkId);
predecessors[sinkId].Add(sourceId);
}
}
// Extract condition from DirectEdgeData if present
DirectEdgeData? directEdge = edge.DirectEdgeData;
if (directEdge is not null)
{
edgeConditions[(directEdge.SourceId, directEdge.SinkId)] = directEdge.Condition;
}
}
}
// Detect back-edges using DFS from start executor
HashSet<(string Source, string Target)> backEdges = DetectBackEdges(workflow.StartExecutorId, successors);
// Calculate in-degrees, EXCLUDING back-edges
int[] inDegree = new int[executors.Count];
foreach (string executorId in executors.Keys)
{
foreach (string pred in predecessors[executorId])
{
// Only count edge if it's NOT a back-edge
if (!backEdges.Contains((pred, executorId)))
{
inDegree[executorIndex[executorId]]++;
}
}
}
// Store edge conditions in the plan
foreach (KeyValuePair<(string SourceId, string TargetId), Func<object?, bool>?> condition in edgeConditions)
{
plan.EdgeConditions[condition.Key] = condition.Value;
}
// Store the graph structure in the plan
foreach (string executorId in executors.Keys)
{
plan.Predecessors[executorId] = predecessors[executorId];
plan.Successors[executorId] = successors[executorId];
}
// Build execution levels using queue-based Kahn's algorithm
// Process all nodes with in-degree 0 at once (same level) for parallel execution
Queue<string> currentLevel = new();
foreach (KeyValuePair<string, int> kvp in executorIndex)
{
if (inDegree[kvp.Value] == 0)
{
currentLevel.Enqueue(kvp.Key);
}
}
int levelNumber = 0;
int processedCount = 0;
while (currentLevel.Count > 0)
{
List<WorkflowExecutorInfo> levelExecutors = new(currentLevel.Count);
Queue<string> nextLevel = new();
bool isFanIn = false;
while (currentLevel.Count > 0)
{
string executorId = currentLevel.Dequeue();
processedCount++;
ExecutorBinding executorBinding = executors[executorId];
bool isAgentic = IsAgentExecutorType(executorBinding.ExecutorType);
RequestPort? requestPort = (executorBinding is RequestPortBinding rpb) ? rpb.Port : null;
Workflow? subWorkflow = (executorBinding is SubworkflowBinding swb) ? swb.WorkflowInstance : null;
levelExecutors.Add(new WorkflowExecutorInfo(executorId, isAgentic, requestPort, subWorkflow));
// Check Fan-In for this executor (excluding back-edges)
int nonBackEdgePredecessors = predecessors[executorId]
.Count(pred => !backEdges.Contains((pred, executorId)));
if (nonBackEdgePredecessors > 1)
{
isFanIn = true;
}
// Decrement in-degree of all successors (excluding back-edges) and enqueue those ready for next level
foreach (string successor in successors[executorId])
{
// Skip back-edges for topological ordering
if (backEdges.Contains((executorId, successor)))
{
continue;
}
int successorIdx = executorIndex[successor];
if (--inDegree[successorIdx] == 0)
{
nextLevel.Enqueue(successor);
}
}
}
plan.Levels.Add(new WorkflowExecutionLevel(levelNumber, levelExecutors, isFanIn));
levelNumber++;
currentLevel = nextLevel;
}
// Handle any remaining executors not processed (shouldn't happen if back-edge detection is correct)
if (processedCount < executors.Count)
{
List<WorkflowExecutorInfo> remainingExecutors = [];
foreach (KeyValuePair<string, ExecutorBinding> executor in executors)
{
if (inDegree[executorIndex[executor.Key]] > 0)
{
bool isAgentic = IsAgentExecutorType(executor.Value.ExecutorType);
RequestPort? requestPort = (executor.Value is RequestPortBinding rpb) ? rpb.Port : null;
Workflow? subWorkflow = (executor.Value is SubworkflowBinding swb) ? swb.WorkflowInstance : null;
remainingExecutors.Add(new WorkflowExecutorInfo(executor.Key, isAgentic, requestPort, subWorkflow));
}
}
if (remainingExecutors.Count > 0)
{
bool isFanIn = remainingExecutors.Exists(e => predecessors[e.ExecutorId].Count > 1);
plan.Levels.Add(new WorkflowExecutionLevel(levelNumber, remainingExecutors, isFanIn));
}
}
return plan;
}
/// <summary>
/// Detects back-edges in the graph using DFS.
/// A back-edge is an edge that points to an ancestor in the DFS tree (creates a cycle).
/// </summary>
/// <param name="startId">The starting executor ID for DFS traversal.</param>
/// <param name="successors">The adjacency list mapping each executor to its successors.</param>
/// <returns>A set of back-edges as (source, target) tuples.</returns>
private static HashSet<(string Source, string Target)> DetectBackEdges(
string startId,
Dictionary<string, List<string>> successors)
{
HashSet<(string, string)> backEdges = [];
HashSet<string> visited = [];
HashSet<string> inStack = []; // Nodes in current DFS path
void Dfs(string nodeId)
{
visited.Add(nodeId);
inStack.Add(nodeId);
if (successors.TryGetValue(nodeId, out List<string>? neighbors))
{
foreach (string neighbor in neighbors)
{
if (inStack.Contains(neighbor))
{
// Edge to ancestor in current path = back-edge (creates cycle)
backEdges.Add((nodeId, neighbor));
}
else if (!visited.Contains(neighbor))
{
Dfs(neighbor);
}
}
}
inStack.Remove(nodeId);
}
// Start DFS from the workflow's start executor
Dfs(startId);
// Handle any disconnected components (shouldn't happen in valid workflows, but for safety)
foreach (string nodeId in successors.Keys)
{
if (!visited.Contains(nodeId))
{
Dfs(nodeId);
}
}
return backEdges;
}
/// <summary>
/// Determines whether the specified executor type is an agentic executor.
/// </summary>
/// <param name="executorType">The executor type to check.</param>
/// <returns><c>true</c> if the executor is an agentic executor; otherwise, <c>false</c>.</returns>
internal static bool IsAgentExecutorType(Type executorType)
{
// hack for now. In the future, the MAF type could expose something which can help with this.
// Check if the type name or assembly indicates it's an agent executor
// This includes AgentRunStreamingExecutor, AgentExecutor, ChatClientAgent wrappers, etc.
string typeName = executorType.FullName ?? executorType.Name;
string assemblyName = executorType.Assembly.GetName().Name ?? string.Empty;
return typeName.Contains("AIAgentHostExecutor", StringComparison.OrdinalIgnoreCase) &&
assemblyName.Contains("Microsoft.Agents.AI", StringComparison.OrdinalIgnoreCase);
}
/// <summary>
/// Extracts the output type from an executor type.
/// For Executor&lt;TInput, TOutput&gt;, returns TOutput.
/// For Executor&lt;TInput&gt;, returns null (void output).
/// </summary>
/// <param name="executorType">The executor type to analyze.</param>
/// <returns>The output type, or null if the executor has no typed output.</returns>
private static Type? GetExecutorOutputType(Type executorType)
{
// Walk up the inheritance chain to find Executor<TInput, TOutput> or Executor<TInput>
Type? currentType = executorType;
while (currentType is not null)
{
if (currentType.IsGenericType)
{
Type genericDefinition = currentType.GetGenericTypeDefinition();
Type[] genericArgs = currentType.GetGenericArguments();
// Check for Executor<TInput, TOutput> (2 type parameters)
if (genericArgs.Length == 2 && genericDefinition.Name.StartsWith("Executor", StringComparison.Ordinal))
{
return genericArgs[1]; // TOutput
}
// Check for Executor<TInput> (1 type parameter) - void return
if (genericArgs.Length == 1 && genericDefinition.Name.StartsWith("Executor", StringComparison.Ordinal))
{
return null;
}
}
currentType = currentType.BaseType;
}
return null;
}
}
@@ -1,127 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
namespace Microsoft.Agents.AI.DurableTask;
/// <summary>
/// Provides helper methods for workflow naming conventions used in durable orchestrations.
/// </summary>
internal static class WorkflowNamingHelper
{
/// <summary>
/// The prefix used for durable workflow orchestration function names.
/// </summary>
public const string OrchestrationFunctionPrefix = "dafx-";
/// <summary>
/// Converts a workflow name to its corresponding orchestration function name.
/// </summary>
/// <param name="workflowName">The workflow name.</param>
/// <returns>The orchestration function name.</returns>
/// <exception cref="ArgumentException">Thrown when the workflow name is null or empty.</exception>
public static string ToOrchestrationFunctionName(string workflowName)
{
ArgumentException.ThrowIfNullOrEmpty(workflowName);
return $"{OrchestrationFunctionPrefix}{workflowName}";
}
/// <summary>
/// Converts an orchestration function name back to its workflow name.
/// </summary>
/// <param name="orchestrationFunctionName">The orchestration function name.</param>
/// <returns>The workflow name.</returns>
/// <exception cref="ArgumentException">Thrown when the orchestration function name is null, empty, or doesn't have the expected prefix.</exception>
public static string ToWorkflowName(string orchestrationFunctionName)
{
ArgumentException.ThrowIfNullOrEmpty(orchestrationFunctionName);
if (!orchestrationFunctionName.StartsWith(OrchestrationFunctionPrefix, StringComparison.Ordinal))
{
throw new ArgumentException(
$"Orchestration function name '{orchestrationFunctionName}' does not start with the expected '{OrchestrationFunctionPrefix}' prefix.",
nameof(orchestrationFunctionName));
}
string workflowName = orchestrationFunctionName[OrchestrationFunctionPrefix.Length..];
if (string.IsNullOrEmpty(workflowName))
{
throw new ArgumentException(
$"Orchestration function name '{orchestrationFunctionName}' does not contain a workflow name after the prefix.",
nameof(orchestrationFunctionName));
}
return workflowName;
}
/// <summary>
/// Tries to convert an orchestration function name back to its workflow name.
/// </summary>
/// <param name="orchestrationFunctionName">The orchestration function name.</param>
/// <param name="workflowName">When this method returns, contains the workflow name if the conversion succeeded, or null if it failed.</param>
/// <returns><c>true</c> if the conversion succeeded; otherwise, <c>false</c>.</returns>
public static bool TryGetWorkflowName(string? orchestrationFunctionName, out string? workflowName)
{
workflowName = null;
if (string.IsNullOrEmpty(orchestrationFunctionName))
{
return false;
}
if (!orchestrationFunctionName.StartsWith(OrchestrationFunctionPrefix, StringComparison.Ordinal))
{
return false;
}
workflowName = orchestrationFunctionName[OrchestrationFunctionPrefix.Length..];
return !string.IsNullOrEmpty(workflowName);
}
/// <summary>
/// The suffix separator used when the workflow builder appends a GUID to executor IDs.
/// </summary>
/// <remarks>
/// For agentic executors, the workflow builder appends a GUID suffix to ensure uniqueness.
/// For example: "Physicist_8884e71021334ce49517fa2b17b1695b".
/// </remarks>
private const char ExecutorIdSuffixSeparator = '_';
/// <summary>
/// Extracts the executor name from an executor ID.
/// </summary>
/// <remarks>
/// <para>
/// For non-agentic executors, the executor ID is the same as the executor name (e.g., "OrderParser").
/// </para>
/// <para>
/// For agentic executors, the workflow builder appends a GUID suffix separated by an underscore
/// (e.g., "Physicist_8884e71021334ce49517fa2b17b1695b"). This method extracts just the name portion.
/// </para>
/// </remarks>
/// <param name="executorId">The executor ID, which may contain a GUID suffix.</param>
/// <returns>The executor name without any GUID suffix.</returns>
/// <exception cref="ArgumentException">Thrown when the executor ID is null or empty.</exception>
public static string GetExecutorName(string executorId)
{
ArgumentException.ThrowIfNullOrEmpty(executorId);
int separatorIndex = executorId.IndexOf(ExecutorIdSuffixSeparator);
return separatorIndex > 0 ? executorId[..separatorIndex] : executorId;
}
/// <summary>
/// Determines whether the executor ID contains a GUID suffix.
/// </summary>
/// <param name="executorId">The executor ID to check.</param>
/// <returns><c>true</c> if the executor ID contains a suffix; otherwise, <c>false</c>.</returns>
public static bool HasExecutorIdSuffix(string? executorId)
{
if (string.IsNullOrEmpty(executorId))
{
return false;
}
int separatorIndex = executorId.IndexOf(ExecutorIdSuffixSeparator);
return separatorIndex > 0 && separatorIndex < executorId.Length - 1;
}
}
@@ -25,44 +25,6 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
IFunctionInputBindingFeature? functionInputBindingFeature = context.Features.Get<IFunctionInputBindingFeature>() ??
throw new InvalidOperationException("Function input binding feature is not available on the current context.");
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint)
{
// Bind all inputs to get the input string and DurableTaskClient
FunctionInputBindingResult? bindingResults = await functionInputBindingFeature.BindFunctionInputAsync(context);
if (bindingResults is not { Values: { } activityBindings })
{
throw new InvalidOperationException($"Function input binding failed for the invocation {context.InvocationId}");
}
DurableTaskClient? activityDurableTaskClient = null;
string? activityInput = null;
foreach (object? binding in activityBindings)
{
if (binding is string stringInput)
{
activityInput = stringInput;
}
if (binding is DurableTaskClient client)
{
activityDurableTaskClient = client;
}
}
if (activityInput is null)
{
throw new InvalidOperationException($"Activity input binding is missing for the invocation {context.InvocationId}.");
}
if (activityDurableTaskClient is null)
{
throw new InvalidOperationException($"DurableTaskClient binding is missing for the invocation {context.InvocationId}.");
}
context.GetInvocationResult().Value = await BuiltInFunctions.InvokeWorkflowActivityAsync(activityInput, activityDurableTaskClient, context);
return;
}
FunctionInputBindingResult? inputBindingResults = await functionInputBindingFeature.BindFunctionInputAsync(context);
if (inputBindingResults is not { Values: { } values })
{
@@ -140,32 +102,6 @@ internal sealed class BuiltInFunctionExecutor : IFunctionExecutor
return;
}
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint)
{
if (httpRequestData == null)
{
throw new InvalidOperationException($"HTTP request data binding is missing for the invocation {context.InvocationId}.");
}
context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowOrechstrtationHttpTriggerAsync(
httpRequestData,
durableTaskClient,
context);
return;
}
// Handle workflow MCP tool trigger
if (context.FunctionDefinition.EntryPoint == BuiltInFunctions.RunWorkflowMcpToolFunctionEntryPoint)
{
if (mcpToolInvocationContext is null)
{
throw new InvalidOperationException($"MCP tool invocation context binding is missing for the invocation {context.InvocationId}.");
}
context.GetInvocationResult().Value = await BuiltInFunctions.RunWorkflowMcpToolAsync(mcpToolInvocationContext, durableTaskClient, context);
return;
}
throw new InvalidOperationException($"Unsupported function entry point '{context.FunctionDefinition.EntryPoint}' for invocation {context.InvocationId}.");
}
}
@@ -20,30 +20,7 @@ internal static class BuiltInFunctions
internal static readonly string RunAgentHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunAgentHttpAsync)}";
internal static readonly string RunAgentEntityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeAgentAsync)}";
internal static readonly string RunWorkflowOrechstrtationHttpFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowOrechstrtationHttpTriggerAsync)}";
internal static readonly string InvokeWorkflowActivityFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(InvokeWorkflowActivityAsync)}";
internal static readonly string RunAgentMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunMcpToolAsync)}";
internal static readonly string RunWorkflowMcpToolFunctionEntryPoint = $"{typeof(BuiltInFunctions).FullName!}.{nameof(RunWorkflowMcpToolAsync)}";
#pragma warning disable IL3000 // Avoid accessing Assembly file path when publishing as a single file - Azure Functions does not use single-file publishing
internal static readonly string ScriptFile = Path.GetFileName(typeof(BuiltInFunctions).Assembly.Location);
#pragma warning restore IL3000
// Exposed as an activity trigger for workflow executors
public static Task<string> InvokeWorkflowActivityAsync(
[ActivityTrigger] string input,
[DurableClient] DurableTaskClient durableTaskClient,
FunctionContext functionContext)
{
ArgumentNullException.ThrowIfNull(input);
ArgumentNullException.ThrowIfNull(durableTaskClient);
ArgumentNullException.ThrowIfNull(functionContext);
string activityFunctionName = functionContext.FunctionDefinition.Name;
FunctionsWorkflowRunner runner = functionContext.InstanceServices.GetRequiredService<FunctionsWorkflowRunner>();
return runner.ExecuteActivityAsync(activityFunctionName, input, durableTaskClient, functionContext);
}
// Exposed as an entity trigger via AgentFunctionsProvider
public static Task<string> InvokeAgentAsync(
@@ -66,24 +43,6 @@ internal static class BuiltInFunctions
return GrpcEntityRunner.LoadAndRunAsync(encodedEntityRequest, entity, combinedServiceProvider);
}
/// <summary>
/// Invokes a workflow orchestration in response to an HTTP request.
/// </summary>
public static async Task<HttpResponseData> RunWorkflowOrechstrtationHttpTriggerAsync(
[HttpTrigger] HttpRequestData req,
[DurableClient] DurableTaskClient client,
FunctionContext context)
{
var workflowName = context.FunctionDefinition.Name.Replace(HttpPrefix, string.Empty);
var orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
var inputMessage = await req.ReadAsStringAsync();
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, inputMessage);
HttpResponseData response = req.CreateResponse(HttpStatusCode.Accepted);
await response.WriteStringAsync($"InvokeWorkflowOrechstrtationAsync is invoked for {workflowName}. Orchestration instanceId: {instanceId}");
return response;
}
public static async Task<HttpResponseData> RunAgentHttpAsync(
[HttpTrigger] HttpRequestData req,
[DurableClient] DurableTaskClient client,
@@ -219,39 +178,6 @@ internal static class BuiltInFunctions
return agentResponse.Text;
}
/// <summary>
/// Runs a workflow via MCP tool trigger.
/// </summary>
public static async Task<string?> RunWorkflowMcpToolAsync(
[McpToolTrigger("BuiltInWorkflowMcpTool")] ToolInvocationContext context,
[DurableClient] DurableTaskClient client,
FunctionContext functionContext)
{
if (context.Arguments is null)
{
throw new ArgumentException("MCP Tool invocation is missing required arguments.");
}
if (!context.Arguments.TryGetValue("input", out object? inputObj) || inputObj is not string input)
{
throw new ArgumentException("MCP Tool invocation is missing required 'input' argument of type string.");
}
// Extract workflow name from the MCP tool name (format: mcptool-workflow-{workflowName})
string workflowName = context.Name;
string orchestrationFunctionName = WorkflowNamingHelper.ToOrchestrationFunctionName(workflowName);
string instanceId = await client.ScheduleNewOrchestrationInstanceAsync(orchestrationFunctionName, input);
// Wait for the orchestration to complete and return the result
OrchestrationMetadata? metadata = await client.WaitForInstanceCompletionAsync(
instanceId,
getInputsAndOutputs: true,
cancellation: functionContext.CancellationToken);
return metadata?.ReadOutputAs<string>();
}
/// <summary>
/// Creates an error response with the specified status code and error message.
/// </summary>
@@ -1,80 +0,0 @@
// Copyright (c) Microsoft. All rights reserved.
using Microsoft.Agents.AI.DurableTask;
using Microsoft.Azure.Functions.Worker.Builder;
using Microsoft.Azure.Functions.Worker.Core.FunctionMetadata;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Hosting;
namespace Microsoft.Agents.AI.Hosting.AzureFunctions;
/// <summary>
/// Shared configuration logic for durable agents and workflows.
/// This class consolidates common service registrations used by both
/// <see cref="FunctionsApplicationBuilderExtensions.ConfigureDurableAgents"/> and
/// <see cref="DurableOptionsExtensions.ConfigureDurableOptions"/>.
/// </summary>
internal static class CoreAgentConfigurationExtensions
{
/// <summary>
/// Registers the core agent services required for durable agents.
/// </summary>
/// <param name="builder">The functions application builder.</param>
/// <returns>The functions application builder for method chaining.</returns>
internal static FunctionsApplicationBuilder RegisterCoreAgentServices(this FunctionsApplicationBuilder builder)
{
builder.Services.TryAddSingleton<IFunctionsAgentOptionsProvider>(_ =>
new DefaultFunctionsAgentOptionsProvider(DurableAgentsOptionsExtensions.GetAgentOptionsSnapshot()));
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IFunctionMetadataTransformer, DurableAgentFunctionMetadataTransformer>());
return builder;
}
/// <summary>
/// Registers the workflow-specific services required for durable workflows.
/// This should only be called when workflows are configured in the application.
/// </summary>
/// <param name="builder">The functions application builder.</param>
/// <returns>The functions application builder for method chaining.</returns>
internal static FunctionsApplicationBuilder RegisterWorkflowServices(this FunctionsApplicationBuilder builder)
{
// Register FunctionsWorkflowRunner as a singleton
builder.Services.TryAddSingleton<FunctionsWorkflowRunner>();
// Also register it as DurableWorkflowRunner so orchestrations can resolve it by base type
builder.Services.TryAddSingleton<DurableWorkflowRunner>(sp => sp.GetRequiredService<FunctionsWorkflowRunner>());
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IFunctionMetadataTransformer, DurableWorkflowFunctionMetadataTransformer>());
return builder;
}
/// <summary>
/// Configures the middleware and executor for handling built-in function execution.
/// This is shared by both agents and workflows, handling Agent HTTP, MCP tool,
/// workflow orchestration, and Entity invocations.
/// </summary>
/// <param name="builder">The functions application builder.</param>
/// <returns>The functions application builder for method chaining.</returns>
internal static FunctionsApplicationBuilder ConfigureBuiltInFunctionMiddleware(this FunctionsApplicationBuilder builder)
{
builder.Services.TryAddSingleton<BuiltInFunctionExecutor>();
builder.UseWhen<BuiltInFunctionExecutionMiddleware>(static context =>
IsBuiltInFunction(context.FunctionDefinition.EntryPoint));
return builder;
}
private static bool IsBuiltInFunction(string? entryPoint)
{
return string.Equals(entryPoint, BuiltInFunctions.RunAgentHttpFunctionEntryPoint, StringComparison.Ordinal)
|| string.Equals(entryPoint, BuiltInFunctions.RunAgentMcpToolFunctionEntryPoint, StringComparison.Ordinal)
|| string.Equals(entryPoint, BuiltInFunctions.RunWorkflowMcpToolFunctionEntryPoint, StringComparison.Ordinal)
|| string.Equals(entryPoint, BuiltInFunctions.RunWorkflowOrechstrtationHttpFunctionEntryPoint, StringComparison.Ordinal)
|| string.Equals(entryPoint, BuiltInFunctions.InvokeWorkflowActivityFunctionEntryPoint, StringComparison.Ordinal)
|| string.Equals(entryPoint, BuiltInFunctions.RunAgentEntityFunctionEntryPoint, StringComparison.Ordinal);
}
}

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