- Ollama CI: fail the step immediately if model pull fails after 3
retries instead of silently proceeding to tests
- Azure OpenAI file search: add the same vector-store readiness polling
that was applied to the non-Azure OpenAI tests, preventing eventual
consistency race conditions
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Rename scripts/flaky_report/ to scripts/integration_test_report/ to
reflect expanded scope beyond flaky-test detection
- Update workflow references in both CI files
- Wrap file search integration tests in try/finally to ensure vector
store cleanup runs even on test failure or timeout
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* dotnet: Add hosted-agent User-Agent supplement to outgoing requests
When an agent runs inside a Foundry Hosted Agent, the outgoing
User-Agent header now includes 'agent-framework-hosted/{version}'
alongside the existing 'MEAI/{version}' segment.
- Add HostedAgentContext with AsyncLocal<string?> property
- MeaiUserAgentPolicy reads the supplement per-call
- AgentFrameworkResponseHandler sets/restores the context
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* chore: update hosted UA format to foundry-hosting/agent-framework-dotnet/{version}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Trying to get UA flowing, no luck yet.
* .NET: Polyfill MEAI OpenAIResponsesChatClient to add hosted-agent User-Agent supplement
When AgentFrameworkResponseHandler resolves an agent (i.e. we are running in a
hosted context), TryApplyUserAgent walks the agent's IChatClient decorator chain
to find MEAI's internal OpenAIResponsesChatClient and reflectively swaps its
inner _responseClient field with a DelegatingResponsesClient wrapper. The
wrapper overrides the public-virtual protocol methods to add a per-call
HostedAgentUserAgentPolicy to the RequestOptions and delegate to the inner
ResponsesClient. The OpenAI SDK's internal streaming overloads bottom out in
calls to the public-virtual non-streaming overloads via virtual dispatch on
this, so streaming is covered without overriding any non-virtual member.
The wrapper accepts any ResponsesClient-derived inner — both the Foundry
ProjectResponsesClient and the native OpenAI ResponsesClient — and preserves
the inner client's full pipeline (Transport, RetryPolicy, NetworkTimeout,
OrganizationId / ProjectId / UserAgentApplicationId, custom policies).
- Add DelegatingResponsesClient + HostedAgentUserAgentPolicy in Microsoft.Agents.AI.Foundry.Hosting.
- Add TryApplyUserAgent next to ApplyOpenTelemetry in FoundryHostingExtensions; wire it into AgentFrameworkResponseHandler.GetAgent for both keyed and default-agent paths.
- Drop earlier-iteration dead code: AddHostedAgentTelemetry extension, HostedUserAgentPolicy class, HostedAgentContext.cs, and the never-called ToRequestOptions helper.
- Revert RequestOptionsExtensions.MeaiUserAgentPolicy to MEAI-only (the supplement is now injected by the polyfill).
- Revert unrelated whitespace change in Agent_Step25_ToolboxServerSideTools sample.
- Tests cover streaming AND non-streaming, retry policy preservation, OrganizationId/ProjectId/UserAgentApplicationId pass-through, idempotency, native OpenAI ResponsesClient, and reflection guards for MEAI/OpenAI shape drift.
* .NET: Address review feedback on hosted-agent User-Agent polyfill
- TryApplyUserAgent: replace silent null-return with ArgumentNullException to match the codebase's convention.
- Add idempotency test (TryApplyUserAgent_CalledTwiceOnSameAgent_DoesNotDoubleWrap) — runs the polyfill twice on the same agent and asserts the wire UA contains exactly one foundry-hosting segment, proving the 'current is DelegatingResponsesClient' guard prevents nested wrapping.
- Add retry-double-append test (Polyfill_RetryWithinCall_DoesNotDuplicateSupplementInUserAgent) — exercises the HostedAgentUserAgentPolicy Contains-guard via a custom retry policy that re-runs the inner pipeline on the same message.
- Replace TryApplyUserAgent_NullAgent_ReturnsNullWithoutThrowing with TryApplyUserAgent_NullAgent_ThrowsArgumentNullException to match the new contract.
* .NET: Drop null check from TryApplyUserAgent and its now-redundant test
The two call sites in AgentFrameworkResponseHandler.GetAgent already null-check the agent before invoking TryApplyUserAgent, so the defensive ArgumentNullException is unreachable. Remove it and the corresponding test.
* .NET: Remove unused Microsoft.Shared.Diagnostics import in ServiceCollectionExtensions
The Throw.IfNull helper from this namespace was used by the now-removed null check in TryApplyUserAgent. Drop the unused import to satisfy IDE0005 in CI's full-project dotnet format run.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
The tests were skipped because xdist distributes module tests across
workers, each spawning their own func process (port conflicts). Adding
xdist_group forces all tests in this module onto a single worker so
the module-scoped function_app_for_test fixture works correctly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The LLM call through Azure OpenAI + Redis streaming pipeline can exceed
30s in CI due to cold starts or throttling. Raise to 60s to reduce
flaky timeouts while still bounded by pytest's 120s per-test limit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use a no-argument greet() function instead of hello_world(arg1) for
integration tests. The 1.5B model in CI is unreliable at generating
correct tool call arguments, causing 'Argument parsing failed' errors.
A no-arg function eliminates this flakiness entirely.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add declarative HttpRequestAction support to workflows
* Clean up response body for diagnostics and fix tests.
* Fix merge with main.
* Remove redundant fallback for request content headers.
* Add declarative InvokeHttpRequest sample
* Fix solution file and update sample yaml comments
* Add final newline to sample class to fix formatting failure
* Support OpenAI allowed_tools in ToolMode (#5309)
Add allowed_tools field to ToolMode TypedDict, enabling users to restrict
which tools the model may call via the OpenAI allowed_tools tool_choice
type. This preserves prompt caching by keeping all tools in the tools list
while limiting which ones the model can invoke.
- Add allowed_tools: list[str] to ToolMode TypedDict
- Add validation in validate_tool_mode() (only valid when mode == "auto")
- Convert to OpenAI API format in _prepare_options()
- Add tests for validation and API payload generation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Support OpenAI `allowed_tools` tool choice in Python SDK
Fixes#5309
* Fix#5309: Validate allowed_tools shape and add Chat Completions client support
- validate_tool_mode now checks allowed_tools is a non-string sequence of
strings and normalizes to list[str], raising ContentError for invalid types
- Add missing allowed_tools branch in _chat_completion_client._prepare_options
so allowed_tools is emitted as the OpenAI allowed_tools wire format instead
of being silently dropped
- Add tests for invalid allowed_tools types (string, int, mixed), empty list,
tuple normalization, and Chat Completions client payload generation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: support allowed_tools with mode 'required' in addition to 'auto'
OpenAI's allowed_tools tool_choice type supports both mode 'auto' and
'required'. Update validation, client conversion, and tests to allow
both modes instead of restricting to 'auto' only.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: use Gemini VALIDATED mode for allowed_tools, warn in unsupported providers
- Use FunctionCallingConfigMode.VALIDATED instead of ANY when allowed_tools
is set with auto mode in Gemini, preserving optional tool-call semantics.
- Handle allowed_tools in required mode with required_function_name precedence.
- Fix allowed_names guard to use identity check (is not None) so empty lists
are preserved.
- Bump google-genai minimum to >=1.32.0 (VALIDATED added in that version).
- Add warnings in Anthropic and Bedrock when allowed_tools is set but not
supported.
- Add Gemini unit tests for allowed_tools with auto, required, empty list,
and required_function_name precedence scenarios.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Chat Completions API does not support allowed_tools, add integration tests
- Chat Completions API (_chat_completion_client.py) now warns and falls
back to plain mode when allowed_tools is set, since the /chat/completions
endpoint does not support the allowed_tools type.
- Add allowed_tools integration test param to both OpenAIChatClient
(Responses API) and OpenAIChatCompletionClient parametrized option tests.
- Update Chat Completions unit tests to reflect the warn-and-fallback
behavior.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: remove unused walrus operator variable in chat completion client
Remove assigned-but-never-used variable 'allowed' flagged by ruff F841.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: bump package versions for 1.2.2 release
PATCH bump (1.2.1 -> 1.2.2) for the released cohort. Five PRs land in this
window:
- agent-framework-openai: fix file_search citations breaking the assistant-
message history roundtrip (#5557) — drives the released-tier PATCH
- agent-framework-orchestrations: [BREAKING] standardize orchestration
terminal outputs as AgentResponse (#5301)
- agent-framework-core, agent-framework-declarative: preserve Workflow.run()
shared state across calls, accept list[Message] in declarative start
executor, and coerce Enum values when serializing PowerFx symbols (#5531)
- agent-framework-foundry-hosting: add hosted Durable Workflow support
(#5531)
- agent-framework-azure-contentunderstanding: new alpha package — Azure AI
Content Understanding context provider (#4829)
- dependencies: workspace package dependency refresh (#5555)
Per lockstep convention, all 21 beta packages stamp 1.0.0b260429 and all 4
alpha packages (now including the new contentunderstanding) stamp
1.0.0a260429. Date stamp reflects 2026-04-29 Pacific. Every non-core package
floor on agent-framework-core is raised to >=1.2.2; the new
contentunderstanding package's stale >=1.0.0 floor is brought into line.
Two follow-on fixes bundled to keep validate-dependency-bounds-test green
at lowest-direct resolution:
- Bump agent-framework-azure-contentunderstanding's azure-ai-content
understanding lower bound from >=1.0.0 to >=1.0.1 (1.0.0 ships without
proper typing — pyright reports 65 unknown-type errors)
- Add pyright ignore comments to core/foundry/__init__.pyi for the new
alpha package's type-stub imports, since alpha packages are not in
core's [all] extra and therefore aren't installed at lowest-direct
* Python: add #5552 to 1.2.2 CHANGELOG
Add the streaming-span observability fix to the Fixed section. PR is on
upstream/main but not yet pulled into origin/main; the code itself will
land via the PR merge.
* Python: address PR #5561 review feedback on dependency bounds
Two packaging fixes flagged in review:
1. agent-framework-azure-contentunderstanding: add agent-framework-foundry
as a runtime dependency. The package's README directs users to
`pip install agent-framework-azure-contentunderstanding --pre` and the
basic example imports `FoundryChatClient` from `agent_framework.foundry`,
so the documented install path was failing with ImportError. Pulling
agent-framework-foundry into deps makes the advertised entry path
self-contained.
2. agent-framework-foundry: bump agent-framework-openai lower bound from
>=1.1.0 to >=1.2.2,<2. Foundry imports private modules from
agent_framework_openai (`_chat_client.py:22`, `_agent.py:34`), so
resolvers were free to pair foundry==1.2.2 with older OpenAI versions
that lack this release's coordinated Responses/history fix. Lockstep the
floor with the released cohort to prevent mismatched installs.
Both changes pass `validate-dependency-bounds-test` lower + upper at
their respective packages.
* Python: Fix file_search citations breaking assistant history roundtrip
The Responses API rejects 'input_file' inside an assistant message, but the
SDK was emitting it whenever an assistant Message contained a hosted_file
content (which is what file_search citations become). Three coordinated fixes:
1. _prepare_content_for_openai now skips hosted_file for the assistant role
instead of mapping to input_file (which the API rejects there).
2. The streaming response.output_text.annotation.added handler attaches
file_citation, container_file_citation, and file_path as annotations on
text content, matching the non-streaming path. Previously streaming
produced standalone HostedFileContent items that always tripped (1).
3. output_text serialization preserves Annotation objects on roundtrip via a
new _annotations_to_output_text helper instead of hardcoding 'annotations'
to []. file_search citations now survive multi-agent forwarding.
Closes#5556.
* Address PR review
- _annotations_to_output_text: fan out one entry per annotated_region for
url_citation/container_file_citation (Annotation.annotated_regions is a
Sequence; the API form carries one start/end per entry).
- Validate region span bounds are ints before emitting; skip otherwise.
- Add test for the file_path branch (annotation with file_id only).
- Add test verifying streamed citation events coalesce onto surrounding
text via _finalize_response so span indices reference the merged text,
not the empty-text streaming carrier.
* Update dependencies
* Preserve mcp[ws] and uvicorn[standard] extras in override-dependencies
Bare-package overrides on mcp and uvicorn dropped the [ws] and [standard]
extras (and their transitive deps like httptools, watchfiles) from the
generated lock. Re-add the extras to the overrides so the lock matches
what workspace packages actually request.
* Fix declarative Workflow.as_agent() by accepting list[Message] in start executor
The declarative start executor (JoinExecutor) only advertised dict and str
in its input_types, so WorkflowAgent.__init__ rejected it with
'Workflow's start executor cannot handle list[Message]'.
Add list[Message] to the JoinExecutor handler annotation and add a
matching branch in DeclarativeActionExecutor._ensure_state_initialized
that extracts the last user-message text and falls through to the
string-input initialization path, so =System.LastMessageText works
end-to-end via as_agent().
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Populate Conversation.messages from list[Message] trigger
When Workflow.as_agent() is invoked with a list[Message], the start executor now populates Conversation.messages / Conversation.history / System.conversations.{id}.messages with prior turns only (excluding the latest user message), and surfaces the latest user message via Inputs.input and System.LastMessage*. This matches InvokeAzureAgent's contract that the messages binding holds prior turns and the executor itself appends the new user input before invoking, avoiding double-append of the trailing user turn while preserving full history (incl. assistant/system/tool roles and multi-modal content) for downstream actions.
* Coerce Enum values when serializing PowerFx symbols
MessageRole and other str-subclass Enums passed isinstance(v, str) and were forwarded to pythonnet unchanged. pythonnet then raised 'MessageRole value cannot be converted to System.String' for every PowerFx primitive when ConditionGroup/Expr eval walked the symbol table containing Conversation.messages. Reduce Enum members to their underlying value before the primitive check so eval sees plain strings/ints.
* Foundry hosting: pass full conversation history to workflow agents
_handle_inner_workflow only forwarded the latest user turn to WorkflowAgent.run, even though _handle_inner_agent already prepends history fetched from Foundry storage to the messages it sends a regular agent. Declarative workflows reset Conversation.messages on every run (state.initialize), so checkpoint replay alone does not give them prior turns - the host has to pass them in, the same way it does for non-workflow agents. Mirror that contract: fetch context.get_history() and pass [*history, *input_messages] to the workflow agent.
* feat(workflows): support combined message + checkpoint_id for multi-turn continuation
Allow Workflow.run(message=..., checkpoint_id=...) so callers can restore
prior workflow state from a checkpoint AND deliver a new message to the
start executor in a single call. The existing reset_context logic
already preserves shared state when checkpoint_id is set, so this gives
us 'fresh start executor invocation with prior state intact' - exactly
what hosted multi-turn declarative workflows need.
- _workflow.py: drop the message+checkpoint_id mutual exclusion and
update _execute_with_message_or_checkpoint to do both (restore then
execute) when both are provided.
- _agent.py: in _run_core's checkpoint branch, also forward
input_messages so WorkflowAgent.run(messages, checkpoint_id=...) works
end-to-end. Falls back to the legacy 'restore only' behavior when
messages are absent.
- _declarative_base.py: detect continuation in _ensure_state_initialized
by checking whether DECLARATIVE_STATE_KEY already exists in shared
state; if so, refresh inputs/LastMessage* and append non-user trigger
messages instead of calling state.initialize() (which would wipe
Conversation/Local/System).
- foundry_hosting/_responses.py: collapse the host's two-call pattern
(restore-only, then fresh run) into a single combined call now that
the underlying APIs support it.
- tests: drop the assertion that combined message+checkpoint_id raises.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Pivot: preserve workflow state across run() calls
Replace the prior 'combined message + checkpoint_id in one run()' approach
with a cleaner default: Workflow.run no longer wipes shared state or runner-
context messages between calls. Iteration counting and per-run kwargs still
reset on a fresh-message run; checkpoint and responses runs are continuations
that preserve everything.
This lets a WorkflowAgent be invoked repeatedly on the same instance and
maintain multi-turn context (e.g. accumulated Conversation.messages) without
asking developers to opt in. Hosted-agent multi-turn pattern becomes two
explicit calls: restore-from-checkpoint (drive to idle), then run-with-message.
Key changes:
- _workflow.py: drop _state.clear() and reset_for_new_run() from run().
Reset iteration count and run kwargs on fresh-message runs only.
Restore 'Cannot provide both message and checkpoint_id' validation.
Add async guard: fresh-message run with un-drained pending executor
messages from a prior run is invalid.
- _runner.py: clear _state before import_state in restore_from_checkpoint
so restore is authoritative (import_state merges, not replaces).
- _agent.py: revert checkpoint branch to restore-only (no message forward).
- _responses.py (foundry_hosting): two-call host pattern - restore checkpoint
silently, then run with new user input.
- tests: state-preservation is the new default; rebuild Workflow for clean slate.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CI lint and mypy issues from prior pivot commit
- _workflow.py: collapse nested if (SIM102), drop redundant assignment (RET504)
- _declarative_base.py: remove unused last_user_msg = tail assignment
whose Message | None type clashed with the prior Message-typed branch
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: fix Inputs.input update and checkpoint storage path
- _declarative_base.py: continuation branch was writing 'Inputs.input' via
state.set, which routes to the Custom namespace and never updates the
PowerFx-visible Workflow.Inputs.input. Update state_data['Inputs'] in
place via get_state_data / set_state_data so =Workflow.Inputs.input and
=inputs.input see the new turn's user text on continuation.
- _declarative_base.py: refresh docstring to clarify that on a list[Message]
trigger, Conversation.messages excludes the current user message at the
start of the turn (agent executors append it before invoking the inner
agent).
- _responses.py: when previous_response_id is supplied (no conversation_id),
the prior checkpoint lives under <storage>/<previous_response_id> but new
checkpoints must land under <storage>/<current_response_id> for the next
turn to find them. Hold onto restore_storage from the get_latest lookup
and pass it to the restore-only run; pass write_storage (current id) to
the message-delivery run and to checkpoint cleanup.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pyright errors in _declarative_base.py for CI
- Replace state._state.get(...) protected access with new public
is_initialized() method on DeclarativeWorkflowState (also clearer intent
for the continuation detection use case).
- Add narrow pyright ignores for the Any-typed trigger paths that pyright
cannot fully narrow (the list[Message] isinstance loop and the
fallback-DefaultTransform branch).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Copilot review batch: tests + Workflow.reset escape hatch
* Add Workflow.reset() public method as recovery escape hatch when an
in-flight run aborted (e.g. WorkflowConvergenceException) and the
workflow is not checkpointed. Update the in-flight messages guard's
error message to point callers at it.
* Add test_workflow_run_inflight_messages_guard exercising both the
guard (sync + streaming) and the reset() recovery path.
* Add test_workflow_reset_rejects_concurrent_runs to lock down the
in-progress guard on reset.
* Add test_as_agent_continuation_preserves_prior_state covering the
is_continuation branch in _ensure_state_initialized: stamps a marker
between calls and asserts it survives, while Inputs.input and
System.LastMessageText refresh to the new turn.
* Add test_powerfx_safe.py regression tests for the Enum branch in
_make_powerfx_safe (str-subclass, int-subclass, plain Enum, and
Enums nested in dict/list).
* Drop redundant @pytest.mark.asyncio on
test_as_agent_round_trip_with_last_message_text (asyncio_mode='auto').
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Skip restore-only pre-pass when checkpoint has pending request_info
Address Copilot review on _responses.py: the restore-only checkpoint
replay populates self._agent.pending_requests for any request_info
events captured in the checkpoint. The follow-up run(input_messages)
call would then route through WorkflowAgent._process_pending_requests,
which expects function-response content and rejects plain text input
as 'unexpected content while awaiting request info responses'.
Workflows resumed from a checkpoint that was idle-with-pending-requests
would therefore fail every subsequent plain-text user turn. Inspect the
loaded checkpoint and skip the pre-pass when its
pending_request_info_events dict is non-empty. Workflows that don't use
request_info (the current sample set) are unaffected; workflows that do
will fall through to a fresh-message run rather than silently corrupting
the routing state.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Loosen azure-ai-agentserver-* pins to major version
The exact-version pins on azure-ai-agentserver-{core,responses,invocations}
forced foundry-hosting consumers to upgrade in lockstep with every beta
bump from upstream. Switch to '>=current,<next-major' so we pick up patch
and feature updates within the same major series without a coordinated
release.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Drop Workflow.reset(); checkpointing is the recovery path
The in-flight-messages guard prevented silent misbehavior, but the
companion Workflow.reset() escape hatch only cleared _messages while
leaving iteration count, executor-local state, and shared State
mutations in an indeterminate condition after a mid-run failure. That
gave a false sense of recovery.
Recovery from a mid-run failure is supported only via checkpoint
restoration. Keep the guard and reframe its error message accordingly;
remove reset() and its tests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Tao's review on PR 5531
- Rename Workflow._run_workflow_with_tracing parameter
is_fresh_message_run -> is_continuation (default False, inverted).
Fresh-message turns reset per-run accounting; continuations
(checkpoint restores, responses replays) preserve it.
- Simplify the in-flight-messages guard: _validate_run_params already
enforces that 'message' is mutually exclusive with 'checkpoint_id'
and 'responses', so the additional checks were dead code.
- foundry_hosting _responses: move the restore-only pre-pass above
emit_created/emit_in_progress; restore is preparation, not run
progress. Drop the skip-restore gate (state preservation requires
unconditional restore) and instead clear agent.pending_requests
after the restore-only call. Collapse over-conditioned check.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Don't clear pending_requests after restore-only pre-pass
Pending requests in the restored checkpoint represent genuinely
outstanding HITL requests. The next user input may carry function
responses (Responses API `function_call_output` items become
FunctionResultContent / FunctionApprovalResponseContent), which
`WorkflowAgent._process_pending_requests` correctly extracts and
matches against the populated `pending_requests`. Clearing them
after restore would silently drop that state and force the next turn
to be treated as a fresh input even when the caller is responding to
the outstanding requests.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* Fix orchestration outputs so as_agent() returns the final answer only. Align other orchestration outputs
* Fix orchestration output issues from review comments
1. Sample cleanup: Remove commented-out FoundryChatClient block and update
prerequisites to reference OPENAI_CHAT_MODEL_ID instead of FOUNDRY_* vars.
2. Sequential approval output: Change _EndWithConversation.end_with_agent_executor_response
from a no-op sink to yield response.agent_response. When the last participant is
AgentApprovalExecutor (via with_request_info), _EndWithConversation is the output
executor so the yield produces the terminal answer. When the last participant is a
regular AgentExecutor, _EndWithConversation is not in output_executors so the yield
is silently filtered out.
3. Forward data events through WorkflowExecutor: _process_workflow_result now also
forwards 'data' events from sub-workflows so that emit_intermediate_data=True on
AgentExecutor works correctly when wrapped in AgentApprovalExecutor.
4. Concurrent docstring: Update _AggregateAgentConversations docstring to say
'deterministic participant order' instead of 'completion order'.
5. Add test_concurrent_intermediate_outputs_emits_data_events verifying that
ConcurrentBuilder(intermediate_outputs=True) emits per-participant data events
alongside the single aggregated output event.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add tests for sequential workflow with_request_info and intermediate_outputs (#5301)
Address PR review comments 2, 3, and 5:
- Add test_sequential_request_info_last_participant_emits_output:
Verifies that when the last participant is wrapped via with_request_info()
(AgentApprovalExecutor), the workflow still emits a terminal output after
approval, exercising the _EndWithConversation.end_with_agent_executor_response
fallback path.
- Add test_sequential_request_info_with_intermediate_outputs_emits_data_events:
Verifies that emit_intermediate_data=True works correctly through
AgentApprovalExecutor wrapping—WorkflowExecutor._process_result already
forwards data events from sub-workflows, so intermediate agent responses
surface as data events in the parent workflow.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pyright type errors from AgentResponse output refactor (#5301)
Update cast() calls in _group_chat.py and _magentic.py to use
WorkflowContext[Never, AgentResponse] instead of the old
WorkflowContext[Never, list[Message]], matching the updated method
signatures in _base_group_chat_orchestrator.py.
Fix _sequential.py _EndWithConversation.end_with_agent_executor_response
to declare WorkflowContext[Any, AgentResponse] so yield_output accepts
AgentResponse[None].
Fix _workflow_executor.py data event forwarding to handle nullable
executor_id.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pyright reportUnknownVariableType in _agent.py (#5301)
Extract event.data into a typed local variable before the isinstance
check to avoid pyright narrowing it to AgentResponse[Unknown].
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pyright reportMissingImports for orjson in file history samples (#5301)
Add pyright: ignore[reportMissingImports] to orjson imports that are
already guarded by try/except ImportError, matching the existing pattern
used elsewhere in the samples.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5301: review comment fixes
* Address review feedback for #5301: review comment fixes
* Revert sequential_workflow_as_agent sample to FoundryChatClient
Reverts the mistaken switch from FoundryChatClient to OpenAIChatClient
in the sequential workflow as agent sample.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address ultrareview feedback: emit_data_events rename + WorkflowAgent reasoning conversion
Layered on top of the prior review-feedback work in this branch.
Renames:
- AgentExecutor.emit_intermediate_data -> emit_data_events (mechanical
rename; orchestration semantics live at the orchestration layer, not
the general-purpose executor). Forwarded through MagenticAgentExecutor,
AgentApprovalExecutor, and all orchestration call sites.
- HandoffAgentExecutor._check_terminate_and_yield -> _should_terminate
(pure predicate; no longer yields anything). HandoffBuilder docstring
rewritten to describe the new per-agent AgentResponse output contract.
WorkflowAgent reasoning-content conversion:
- Add _rewrite_text_to_reasoning(contents) and _msg_as_reasoning(msg)
helpers; the as_agent() path now reframes text content from data events
as text_reasoning Content blocks before merging into the AgentResponse.
- Consumers iterate msg.contents and branch on content.type — same path
they already use for Claude thinking and OpenAI reasoning. No new
field on Message/AgentResponse/WorkflowEvent.
- Streaming branch constructs fresh AgentResponseUpdate instances instead
of mutating shared payloads (regression test added).
- Helper _msg_maybe_reasoning consolidates the conditional rewrite at
three call sites in the non-streaming conversion.
Tests:
- TestWorkflowAgentReasoningHelpers + TestWorkflowAgentDataEventReasoningConversion
add 9 new tests covering helpers, non-streaming, streaming, mixed content,
already-reasoning passthrough, and mutation-safety regression.
- Updated test_sequential_as_agent_with_intermediate_outputs_includes_chain
to assert text_reasoning content for intermediate agents.
* Fix pyright: widen event.data to Any to avoid partial-unknown narrowing
The streaming conversion path narrowed event.data via isinstance against
generic AgentResponse, producing AgentResponse[Unknown] and tripping
reportUnknownVariableType/reportUnknownMemberType. Binding data: Any
before the check keeps runtime behavior identical while restoring a fully
known type for downstream access.
* Clean up design
* Scope to agent output semantics only
* yield AgentResponseUpdate streaming, AgentResponse non-streaming
* Fix mypy/pyright: widen cast types at GroupChat callsites
Eight callsites in _group_chat.py still cast to WorkflowContext[Never,
AgentResponse] but the base orchestrator methods now accept the wider
WorkflowContext[Never, AgentResponse | AgentResponseUpdate] (mode-aware
yields). W_OutT is invariant, so the narrower cast is not assignable.
Magentic was widened in the same commit; this catches the GroupChat
callsites that were missed.
* Python: skip flaky Foundry / Foundry Hosting integration tests (#5553)
These two integration tests have been failing in the merge queue across
multiple unrelated PRs (5301, 5531). Both are marked `@pytest.mark.flaky`
with 3 retries, but all attempts fail back-to-back. Skipping both with a
reason pointing to #5553 so they can be fixed properly without continuing
to block unrelated merges.
- packages/foundry_hosting/tests/test_responses_int.py::TestOptions::test_temperature_and_max_tokens
- packages/foundry/tests/foundry/test_foundry_embedding_client.py::TestFoundryEmbeddingIntegration::test_text_embedding_live
Also includes a one-line uv.lock specifier-ordering normalization
auto-applied by the poe-check pre-commit hook.
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Kill any auto-started Ollama before launching serve (fixes port
conflict: 'address already in use')
- Retry ollama pull up to 3 times with 15s backoff (fixes 429 rate
limit failures)
- Applied to both python-merge-tests.yml and python-integration-tests.yml
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add declarative HttpRequestAction support to workflows
* Clean up response body for diagnostics and fix tests.
* Fix merge with main.
* Remove redundant fallback for request content headers.
* feat: add agent-framework-azure-contentunderstanding package
Add Azure Content Understanding integration as a context provider for the
Agent Framework. The package automatically analyzes file attachments
(documents, images, audio, video) using Azure CU and injects structured
results (markdown, fields) into the LLM context.
Key features:
- Multi-document session state with status tracking (pending/ready/failed)
- Configurable timeout with async background fallback for large files
- Output filtering via AnalysisSection enum
- Auto-registered list_documents() and get_analyzed_document() tools
- Supports all CU modalities: documents, images, audio, video
- Content limits enforcement (pages, file size, duration)
- Binary stripping of supported files from input messages
Public API:
- ContentUnderstandingContextProvider (main class)
- AnalysisSection (output section selector enum)
- ContentLimits (configurable limits dataclass)
Tests: 46 unit tests, 91% coverage, all linting and type checks pass.
* fix: update CU fixtures with real API data, fix test assertions
- Replace synthetic fixtures with real CU API responses (sanitized)
- Update test assertions to match real data (Contoso vs CONTOSO,
TotalAmount vs InvoiceTotal, field values from real analysis)
- Add --pre install note in README (preview package)
- Document unenforced ContentLimits fields (max_pages, duration)
* chore: add connector .gitignore, update uv.lock
* refactor: rename to azure-ai-contentunderstanding, fix CI issues
Align naming with Azure SDK convention and AF pattern:
- Directory: azure-contentunderstanding -> azure-ai-contentunderstanding
- PyPI: agent-framework-azure-contentunderstanding -> agent-framework-azure-ai-contentunderstanding
- Module: agent_framework_azure_contentunderstanding -> agent_framework_azure_ai_contentunderstanding
CI fixes:
- Inline conftest helpers to avoid cross-package import collision in xdist
- Remove PyPI badge and dead API reference link from README (package not published yet)
* feat: add samples (document_qa, invoice_processing, multimodal_chat)
- document_qa.py: Single PDF upload, CU context provider, follow-up Q&A
- invoice_processing.py: Structured field extraction with prebuilt-invoice
- multimodal_chat.py: Multi-file session with status tracking
- Add ruff per-file-ignores for samples/ directory
- Update README with samples section, env vars, and run instructions
* feat: add remaining samples (devui_multimodal_agent, large_doc_file_search)
- S3: devui_multimodal_agent/ — DevUI web UI with CU-powered file analysis
- S4: large_doc_file_search.py — CU extraction + OpenAI vector store RAG
- Update README and samples/README.md with all 5 samples
* feat: add file_search integration for large document RAG
Add FileSearchConfig — when provided, CU-extracted markdown is automatically
uploaded to an OpenAI vector store and a file_search tool is registered on
the context. This enables token-efficient RAG retrieval for large documents
without users needing to manage vector stores manually.
- FileSearchConfig dataclass (openai_client, vector_store_name)
- Auto-create vector store, upload markdown, register file_search tool
- Auto-cleanup on close()
- When file_search is enabled, skip full content injection (use RAG instead)
- Update large_doc_file_search sample to use the integration
- 4 new tests (50 total, 90% coverage)
* fix: add key-based auth support to all samples
Follow established AF pattern: check for API key env var first,
fall back to AzureCliCredential. Supports AZURE_OPENAI_API_KEY and
AZURE_CONTENTUNDERSTANDING_API_KEY environment variables.
* FEATURE(python): add analyzer auto-detection, file_search RAG, and lazy init
_context_provider.py:
- Make analyzer_id optional (default None) with auto-detection by media
type prefix: audio->audioSearch, video->videoSearch, else documentSearch
- Add _ensure_initialized() for lazy client creation in before_run()
- Add FileSearchConfig-based vector store upload
- Fix: background-completed docs in file_search mode now upload to vector
store instead of injecting full markdown into context messages
- Add _pending_uploads queue for deferred vector store uploads
devui_file_search_agent/ (new sample):
- DevUI agent combining CU extraction + OpenAI file_search RAG
azure_responses_agent (existing sample fix):
- Add AzureCliCredential support and AZURE_AI_PROJECT_ENDPOINT fallback
Tests (19 new), Docs updated (AGENTS.md, README.md)
* feat(cu): MIME sniffing, media-aware formatting, unified timeout, vector store expiration
- Add three-layer MIME detection (fast path → filetype binary sniff → filename
fallback) to handle unreliable upstream MIME types (e.g. mp4 sent as
application/octet-stream). Adds filetype>=1.2,<2 dependency.
- Media-aware output formatting: video shows duration/resolution + all fields
as JSON; audio promotes Summary as prose; document unchanged.
- Unified timeout for all media types (removed file_search special-case that
waited indefinitely for video/audio). All files use max_wait with background
polling fallback.
- Vector store created with expires_after=1 day as crash safety net.
- Add 8 MIME sniffing tests (TestMimeSniffing class).
* fix: merge all CU content segments for video/audio analysis
CU's prebuilt-videoSearch and prebuilt-audioSearch analyzers split long
media files into multiple `contents[]` segments. Previously,
`_extract_sections()` only read `contents[0]`, causing truncated
duration, missing transcript, and incomplete fields for any video/audio
longer than a single scene.
Now iterates all segments and merges:
- duration: global min(startTimeMs) → max(endTimeMs)
- markdown: concatenated with `---` separators
- fields: same-named fields collected into per-segment list
- metadata (kind, resolution): taken from first segment
Single-segment results (documents, short audio) are unaffected.
Update test fixture to realistic 3-segment video structure and expand
assertions to verify multi-segment merging. Add documentation for
multi-segment processing and speaker diarization limitation.
* refactor: improve CU context provider docs and remove ContentLimits
- Improve class docstring: clarify endpoint (Azure AI Foundry URL with
example), credential (AzureKeyCredential vs Entra ID), and analyzer_id
(prebuilt/custom with auto-selection behavior and reference links)
- Add SUPPORTED_MEDIA_TYPES comments explaining MIME-based matching
behavior and add missing file types per CU service docs
- Use namespaced logger to align with other packages
- Remove ContentLimits and related code/tests
- Rename DEFAULT_MAX_WAIT to DEFAULT_MAX_WAIT_SECONDS for clarity
* feat: support user-provided vector store in FileSearchConfig
- Add vector_store_id field to FileSearchConfig (None = auto-create)
- Track _owns_vector_store to only delete auto-created stores on close()
- Remove vector_store_name; use internal _DEFAULT_VECTOR_STORE_NAME
- Add inline comments for private state fields
- Document output_sections default in docstring
- Update AGENTS.md, samples, and tests
* fix: remove ContentLimits from README code block
* refactor: create CU client in __init__ instead of __aenter__
Follow Azure AI Search provider pattern: create the client eagerly in
__init__, make __aenter__ a no-op. This ensures __aexit__/close() is
always safe to call and eliminates the _ensure_initialized() workaround.
* docs: add file_search param to class docstring
* feat: introduce FileSearchBackend abstraction for cross-client support
Replace direct OpenAI client usage with FileSearchBackend ABC:
- OpenAIFileSearchBackend: for OpenAIChatClient (Responses API)
- FoundryFileSearchBackend: for FoundryChatClient (Azure Foundry)
- Shared base _OpenAICompatBackend for common vector store CRUD
FileSearchConfig now takes a backend instead of openai_client.
Factory methods from_openai() and from_foundry() for convenience.
BREAKING: FileSearchConfig(openai_client=...) -> FileSearchConfig.from_openai(...)
* refactor: FileSearchBackend abstraction + caller-owned vector store
* fix: file_search reliability and sample improvements
- Poll vector store indexing (create_and_poll) to ensure file_search
returns results immediately after upload
- Set status to failed when vector store upload fails
- Skip get_analyzed_document tool in file_search mode to prevent
LLM from bypassing RAG
- Simplify sample auth: single credential, direct parameters
- Use from_foundry backend for Foundry project endpoints
* perf: set max_num_results=10 for file_search to reduce token usage
* fix: move import to top of file (E402 lint)
* chore: remove unused imports
* fix: align azure-ai-contentunderstanding with MAF coding conventions
- Add module-level docstrings to __init__.py and _context_provider.py
- Use Self return type for __aenter__ (with typing_extensions fallback)
- Use explicit typed params for __aexit__ signature
- Add sync TokenCredential to AzureCredentialTypes union
- Pass AGENT_FRAMEWORK_USER_AGENT to ContentUnderstandingClient
- Remove unused ContentLimits from public API and tests
- Fix FileSearchConfig tests to match refactored backend API
- Fix lifecycle tests to match eager client initialization
* refactor: improve CU context provider API surface and fix CI
- Refactor _analyze_file to return DocumentEntry instead of mutating dict
- Remove TokenCredential from AzureCredentialTypes (fixes mypy/pyright CI)
- Remove OpenAIFileSearchBackend/FoundryFileSearchBackend from public API
(internal to FileSearchConfig factory methods)
- Remove DocumentStatus from public exports (implementation detail)
- Update file_search comments to reflect backend-agnostic design
- Add DocumentStatus enum, analysis/upload duration tracking
- Add combined timeout for CU analysis + vector store upload
* fix: improve file_search samples and move tool guidelines to context provider
- Delete redundant devui_file_search_agent sample (duplicate of azure_openai variant)
- Move tool usage guidelines from sample agent instructions into context provider
(extend_instructions in step 6, applied automatically for all file_search users)
- Fix file_search purpose: use from_foundry() for Azure OpenAI (purpose="assistants")
- Add filename hint in upload instructions for targeted file_search queries
- Reduce max_num_results from 10 to 3 in both devui samples
- Simplify agent instructions in both samples (remove tool-specific guidance)
* feat: improve source_id, integration tests, and content assertions
- Rename DEFAULT_SOURCE_ID to "azure_ai_contentunderstanding" (matches
azure_ai_search convention)
- Improve source_id docstring to describe default value
- Clarify _detect_and_strip_files docstring (CU-supported files)
- Add invoice.pdf test fixture from Azure CU samples repo
- Refactor integration tests to use invoice.pdf directly (assert instead
of skip when fixture missing)
- Add URI content test (Content.from_uri with external URL)
- Add "CONTOSO LTD." content assertion to all integration tests
- Use max_wait=None in integration tests (wait until complete)
* feat: reject duplicate filenames, add integration tests and sample comments
- Reject duplicate document keys in before_run (skip + warn LLM to rename)
- Update _derive_doc_key docstring to document uniqueness constraint
- Add unit tests for duplicate filename rejection (cross-turn and same-turn)
- Add integration test for data URI content (from_uri with base64)
- Add integration test for background analysis (max_wait timeout + resolve)
- Add filename recommendation comments to all samples' Content.from_data()
* chore: improve doc key derivation, comments, and README
- Replace hash-based doc key with uuid4 for anonymous uploads (O(1), no payload traversal)
- Remove hashlib import (no longer needed)
- Add File Naming section to README (filename importance, duplicate rejection)
- Improve inline comments (_derive_doc_key, _extract_binary, URL parsing)
* test: strengthen _format_result assertions with exact expected strings
- Replace loose 'in' checks with exact 'assert formatted == expected'
for both multi-segment and single-segment format tests
- Add object-type fields (ShippingAddress, Speakers) to test data
to cover nested dict/list serialization
- Add position-based ordering assertions to verify structural
correctness (header -> markdown -> fields across segments)
* refactor: move invoice.pdf to shared sample_assets directory
- Move invoice.pdf from tests/cu/test_data/ to
python/samples/shared/sample_assets/ as single source of truth
- Add INVOICE_PDF_PATH constant in test_integration.py pointing
to the shared location
- Update document_qa.py, invoice_processing.py, large_doc_file_search.py
to use invoice.pdf instead of sample.pdf
* refactor: reorganize samples into numbered dirs and simplify auth
- Move script samples into 01-get-started/ with numbered prefixes
(01_document_qa, 02_multimodal_chat, 03_invoice_processing,
04_large_doc_file_search)
- Move devui samples into 02-devui/ with 01-multimodal_agent and
02-file_search_agent/{azure_openai_backend,foundry_backend}
- Move invoice.pdf to CU package-local samples/shared/sample_assets/
- Replace kwargs dicts with direct constructor calls; support both
API key (AZURE_OPENAI_API_KEY) and AzureCliCredential
- Update README sample table with new paths
* fix: resolve CI lint errors (D205, RUF001, E501)
- Fix D205: single-line docstring summary for _detect_and_strip_files
- Fix RUF001: replace EN DASH with HYPHEN-MINUS in segment headers
- Fix E501: wrap long assertion lines in tests
- Also includes samples reorg and auth simplification
* refactor: overhaul samples — FoundryChatClient, sessions, remove get_analyzed_document
Samples:
- Switch all samples from deprecated AzureOpenAIResponsesClient to FoundryChatClient
- Add 02_multi_turn_session.py showing AgentSession persistence across turns
- Rewrite 03_multimodal_chat.py with real PDF + audio + video (parallel
analysis), per-modality follow-ups, cross-document question, elapsed
time, user prompts, and input token counts
- Renumber: 02->03 multimodal, 03->04 invoice, 04->05 file_search
Context provider:
- Remove get_analyzed_document tool -- full content is in conversation
history via InMemoryHistoryProvider, no retrieval tool needed
- Remove follow-up turn instructions about tools
- Only list_documents tool remains (for status queries)
- Update README to reflect tool removal
* feat: add 05_background_analysis sample and fix 04 session/max_wait
- Add 05_background_analysis.py demonstrating non-blocking CU analysis
with max_wait=1s, status tracking via list_documents(), and automatic
background task resolution on subsequent turns
- Fix 04_invoice_processing.py: add max_wait=None and AgentSession
- Rename 05→06 large_doc_file_search
- Update README sample table
* docs: update README and fix sample 06
README:
- Switch Quick Start from AzureOpenAIResponsesClient to FoundryChatClient
- Add AgentSession to Quick Start example
- Fix status values: pending -> analyzing/uploading/ready/failed
- Fix env var: AZURE_OPENAI_RESPONSES_DEPLOYMENT_NAME -> AZURE_OPENAI_DEPLOYMENT_NAME
- Update samples section with new paths, link to samples/README.md
- Update multi-segment description to reflect per-segment fields
Sample 06:
- Fix from_openai -> from_foundry for Azure endpoints
- Add AgentSession and max_wait=None
* docs: rewrite README — concise format, prerequisites, CU link
* fix: resolve pyright errors in _format_result segment cast
* docs: add numbered section comments and fresh sample output to all samples
- Add numbered section comments (# 1. ..., # 2. ...) per SAMPLE_GUIDELINES
- Re-run all 6 samples and update expected output with real results
- Fix duplicate sample output blocks in 04 and 05
- Update README code example to use public invoice URL
* feat: add load_settings support for env var configuration
- Make endpoint optional in constructor — auto-loads from
AZURE_CONTENTUNDERSTANDING_ENDPOINT env var via load_settings()
- Add ContentUnderstandingSettings TypedDict
- Add env_file_path/env_file_encoding params for .env file support
- Add 4 unit tests: env var loading, explicit override, missing
endpoint error, missing credential error
- Update README with env var auto-resolution docs
- Follows framework convention used by all other packages
* docs: polish README — fix duplicate env var, add Next steps, service limits link
* chore: trim invoice fixture from 199K to 33 lines
Keep only VendorName, InvoiceTotal, DueDate, InvoiceDate, InvoiceId
fields and first 500 chars of markdown. Strip spans/source/coordinates.
Reduces fixture from 6.6MB to 1.2KB.
* feat: per-file analyzer_id override via additional_properties
- Read analyzer_id from Content.additional_properties for per-file override
- Resolution order: per-file > provider-level > auto-detect by media type
- Update class docstring documenting filename and analyzer_id properties
- Update sample 04 to demonstrate per-file override (prebuilt-invoice)
- Add unit test for per-file analyzer override
* Trim PDF test fixture and clarify unique filename requirement
- Trim analyze_pdf_result.json from 4427 to 23 lines by removing
pages, words, lines, paragraphs, sections, spans, and source
fields that are not used by any unit test.
- Add docstring note that filename must be unique within a session;
duplicate filenames are rejected and the file will not be analyzed.
* Update python/packages/azure-ai-contentunderstanding/agent_framework_azure_ai_contentunderstanding/_context_provider.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update python/packages/azure-ai-contentunderstanding/agent_framework_azure_ai_contentunderstanding/_context_provider.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update python/packages/azure-ai-contentunderstanding/samples/02-devui/02-file_search_agent/azure_openai_backend/agent.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update python/packages/azure-ai-contentunderstanding/samples/02-devui/01-multimodal_agent/agent.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update python/packages/azure-ai-contentunderstanding/samples/01-get-started/06_large_doc_file_search.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Fix AGENTS.md to match implementation; remove unused variable in test helper
AGENTS.md:
- Remove _ensure_initialized() reference (client is created in __init__)
- Fix multi-segment docs: segments kept as list, not merged into fields
- Remove get_analyzed_document() reference (only list_documents registered)
- Update sample names to match current directory structure
test_context_provider.py:
- Simplify _make_data_uri() — remove unused 'encoded' variable
* Fix premature file_search instruction for background-completed docs
- Change _resolve_pending_tasks() instruction from 'Use file_search'
to 'being indexed' since the upload hasn't completed yet at that point.
- Add LLM instruction on upload failure in step 1b so the agent can
inform the user the document isn't searchable.
* fix: wrap long line in devui agent instructions (E501)
* Fix Copilot review: unused logger, stray code in README, await cancelled tasks
- _file_search.py: Remove unused logger and logging import
- 01-multimodal_agent/README.md: Remove accidentally pasted Python script
- _context_provider.py close(): Await cancelled tasks before closing
client to prevent 'Task destroyed but pending' warnings
* Sanitize doc keys and fix duplicate filename re-injection
- Add _sanitize_doc_key() to strip control characters, collapse
whitespace, and cap length at 255 chars — prevents prompt injection
via crafted filenames in extend_instructions() calls.
- Track accepted doc_keys in step 3 so step 5 only injects content
for files actually analyzed this turn, not pre-existing duplicates.
- Soften duplicate upload instruction wording (remove IMPORTANT/caps).
* fix: add type annotation to tasks_to_cancel for pyright
* Move per-session mutable state to state dict for session isolation
Previously _pending_tasks, _pending_uploads, and _uploaded_file_ids
were stored on self, shared across all sessions. This caused
cross-session leakage: Session A's background task results could be
injected into Session B's context.
Now these are stored in the per-session state dict. Global copies
(_all_pending_tasks, _all_uploaded_file_ids) are kept on self only
for best-effort cleanup in close().
Add 2 new TestSessionIsolation tests verifying that background tasks
and resolved content stay within their originating session.
* Remove unused AnalysisSection enum values
Only MARKDOWN and FIELDS are handled by _extract_sections().
Remove FIELD_GROUNDING, TABLES, PARAGRAPHS, SECTIONS to avoid
exposing dead options to users.
* Recursively flatten object/array field values for cleaner LLM output
- Use SDK .value property with recursive extraction for object/array fields
- Object: AmountDue -> {Amount: 610, CurrencyCode: USD} (was raw SDK dict)
- Array: LineItems -> list of flattened items (was raw SDK list)
- Update invoice fixture with object/array fields from prebuilt-invoice
- Add 3 unit tests for object, array, and nested object field extraction
* Preserve sub-field confidence; compare full expected JSON in tests
* Remove incorrect MIME aliases (audio/mp4, video/x-matroska)
* feat: add AnalysisInput, content_range, warnings, and category support
- Use SDK AnalysisInput model instead of raw body dict for begin_analyze
- Forward content_range from additional_properties to CU (page/time ranges)
- Extract CU warnings with code/message/target (ODataV4Format) into output
- Include content-level category from classifier analyzers
- Add 5 new tests: warnings, category, content_range forwarding
- Fix pyright with explicit casts; fix en-dash lint (RUF002)
* fix: falsy-0 bug in duration calc; improve test coverage
- Fix start_time_ms=0 treated as falsy by 'or' short-circuit, use
'is None' checks instead for duration and segment time extraction
- Update warnings test to use RAI ContentFiltered codes
- Enrich warnings extraction to include code/message/target (ODataV4Format)
- Add multi-segment video category test with per-segment assertions
* refactor: split _context_provider.py into focused modules
- Extract _constants.py: SUPPORTED_MEDIA_TYPES, MIME_ALIASES, analyzer maps
- Extract _detection.py: file detection, MIME sniffing, doc key derivation
- Extract _extraction.py: result extraction, field flattening, LLM formatting
- _context_provider.py delegates via thin wrappers (793 lines, was 1255)
- Update test imports to use _constants.py for SUPPORTED_MEDIA_TYPES
* docs: update AGENTS.md with DocumentStatus, FileSearchBackend, and _file_search.py
* refactor: replace AnalysisSection enum with Literal type for simpler DX
- Remove AnalysisSection(str, Enum) class, replace with Literal["markdown", "fields"] type alias
- Users can now pass plain strings: output_sections=["markdown"] — no extra import needed
- AnalysisSection type alias still exported for type annotation use
- Update all samples, tests, and internal code to use string literals
- Address PR review feedback (eavanvalkenburg)
* refactor: replace asyncio.Task with continuation tokens for serializable state
- Replace state["_pending_tasks"] (asyncio.Task — not serializable) with
state["_pending_tokens"] (dict of continuation token strings) so the
framework can persist session state to disk/storage
- Resume pending analyses via Azure SDK continuation_token mechanism
- Fix: resumed pollers have stale cached status (done() always False),
use asyncio.wait_for(poller.result()) with 10s min timeout instead
- Remove _background_poll(), _all_pending_tasks, and task cancellation
- Address PR review feedback (eavanvalkenburg): state must be serializable
* fix: resolve CI lint (RUF052) and mypy (call-overload) errors
* feat: add structured output (Pydantic model) to invoice processing sample
- Use response_format=InvoiceResult for schema-constrained LLM output
- Use output_sections=["fields"] only (no markdown needed for structured output)
- Add LowConfidenceField model with confidence values
- Add comments about prebuilt-invoice extensive schema vs simplified model
- Address PR review feedback (eavanvalkenburg): use structured response
* fix: use FOUNDRY_PROJECT_ENDPOINT and FOUNDRY_MODEL env vars in all samples
Replace AZURE_AI_PROJECT_ENDPOINT → FOUNDRY_PROJECT_ENDPOINT and
AZURE_OPENAI_DEPLOYMENT_NAME → FOUNDRY_MODEL across all sample .py and
README.md files. Address PR review feedback (eavanvalkenburg).
* refactor: remove background_analysis sample, use FoundryChatClient in DevUI
- Remove 05_background_analysis.py (per reviewer feedback — discuss max_wait
design separately from samples)
- Renumber 06_large_doc_file_search.py → 05_large_doc_file_search.py
- Replace AzureOpenAIResponsesClient with FoundryChatClient in all DevUI samples
- Replace client.as_agent() with Agent(client=client, ...) everywhere
- Add max_wait comments explaining interactive vs batch usage
- Update README.md and AGENTS.md
- Address PR review feedback (eavanvalkenburg)
* fix: vector_stores API moved from beta namespace in OpenAI SDK
* docs: add comments about multi-file support and CU service limits in file_search sample
* fix: broken markdown links after sample removal and renumbering
* fix: migrate BaseContextProvider to ContextProvider (non-deprecated)
* fix: Message(text=) -> Message(contents=[]) for API compatibility
* Inline _constants.py into consuming modules
Remove _constants.py and move constants to where they are used:
- SUPPORTED_MEDIA_TYPES, MIME_ALIASES → _detection.py
- MEDIA_TYPE_ANALYZER_MAP, DEFAULT_ANALYZER → _context_provider.py
Addresses review feedback to reduce file count.
* Mark package as alpha per package management skill
- Version: 1.0.0b260401 → 1.0.0a260401
- Classifier: Development Status 4 - Beta → 3 - Alpha
- Add to PACKAGE_STATUS.md as alpha
Follows the alpha package checklist from python-package-management skill.
* Replace extend_instructions with extend_messages for status notifications
Status/error/result notifications now use extend_messages (conversation
context) instead of extend_instructions (system prompt). This avoids
system prompt bloat and keeps behavioral directives separate from
event notifications.
- 11 extend_instructions calls → extend_messages (role='user')
- 1 extend_instructions retained: tool usage guidelines (behavioral)
- 6 test assertions updated to check context_messages
All 84 unit tests + 5 live integration tests pass.
* Fix lint: E402 import order, ISC004 implicit string concatenation
- Move constants after all imports to fix E402
- Wrap multi-line strings in parentheses inside contents=[] to fix ISC004
* Fix lint: remove unused json import in invoice sample
* Fix CI: apply ruff format + fix E501 line length after reformatting
ruff format expands Message() calls to multi-line, pushing string
indentation deeper. Break long strings to fit within 120 char limit
after formatting. Also removes unused json import in sample.
* Address review feedback: keyword-only args, accept pre-built client, remove wrappers
- All __init__ args now keyword-only (matches FoundryChatClient pattern)
- New 'client' param accepts pre-built ContentUnderstandingClient
- core dep bound: >=1.0.0rc5 → >=1.0.0,<2
- Self import moved after local imports
- Removed 9 static method wrappers; callsites use module functions directly
- Tests updated to import derive_doc_key and format_result directly
* fix: remove duplicate ContentUnderstandingClient instantiation
The client was being created twice — once inside the if/else block and
again unconditionally after it. The second instantiation overwrote the
pre-built client path and failed type checking when credential was None.
* rename: azure-ai-contentunderstanding → azure-contentunderstanding
Package: agent-framework-azure-ai-contentunderstanding → agent-framework-azure-contentunderstanding
Module: agent_framework_azure_ai_contentunderstanding → agent_framework_azure_contentunderstanding
Directory: packages/azure-ai-contentunderstanding → packages/azure-contentunderstanding
Per agreement with PM and MAF team to drop 'AI' from the package name.
* feat: add ContentUnderstanding re-export to agent_framework.foundry namespace
Enables: from agent_framework.foundry import ContentUnderstandingContextProvider
Exports: ContentUnderstandingContextProvider, FileSearchConfig,
FileSearchBackend, AnalysisSection, DocumentStatus
Updates all samples and README to use the foundry namespace import.
* fix: add missing copyright headers to standalone sample scripts
* chore: remove .vscode/settings.json and add to .gitignore
* refactor: reuse FoundryChatClient.client for vector store ops in file_search sample
Address review feedback from TaoChenOSU:
- 05_large_doc_file_search.py: use client.client instead of manually
constructing AsyncAzureOpenAI; remove openai dependency
- azure_openai_backend/agent.py: import reorder only (AIProjectClient
kept — required for sync vector store creation in DevUI)
* fix: skip closing client when caller passes pre-built client
When a ContentUnderstandingClient is passed via client=, the caller
owns its lifecycle. Added _owns_client flag so close() only closes
the client when we created it internally.
---------
Co-authored-by: yungshinlin <yungshin@msn.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Python: bump package versions for 1.2.1 release
PATCH bump (1.2.0 -> 1.2.1) for the released cohort. The release window
covers two PRs, no new public APIs:
- agent-framework-core: prevent inner_exception from being lost in
AgentFrameworkException (#5167)
- samples: add requirements.txt and .env.example to the a2a/ hosting
sample for pip-based setup (#5510)
Per lockstep convention, all 21 beta packages stamp 1.0.0b260428 and all
3 alpha packages stamp 1.0.0a260428, regardless of per-package code
churn. Every non-core package floor on agent-framework-core is raised to
>=1.2.1 to keep cohort signaling consistent. Date stamp reflects the
local (Asia) cut date 2026-04-28.
* Python: silence pyright unknown-type warnings in hosted-env detection
`azure.ai.agentserver.core` is probed at runtime via `importlib.util.find_spec`
and is not a declared dependency. The existing `# pyright: ignore[reportMissingImports]`
suppresses the missing-import warning, but at `lowest-direct` resolution pyright
still reports the imported symbol (`AgentConfig`) and its members (`from_env`,
`is_hosted`) as unknown, breaking `validate-dependency-bounds-test` for
`packages/core`.
Extend the existing ignore to cover `reportUnknownVariableType` on the import
and `reportUnknownMemberType` on the call site so the bounds check returns to
green. Behavior is unchanged.
Latent since #5455 (shipped in 1.2.0).
* Python: raise agent-framework-gemini lower bound to google-genai>=1.65.0
The Gemini chat client references several `google.genai.types` symbols
(`FileSearch`, `ThinkingLevel`, `SearchTypes`, `McpServer`,
`StreamableHttpTransport`, plus call-site keyword args `mcp_servers` and
`search_types`) that are not present at the lower bound of `google-genai>=1.0.0`.
At `lowest-direct` resolution this caused `validate-dependency-bounds-test` to
fail for `packages/gemini` with eleven `reportAttributeAccessIssue` /
`reportUnknownVariableType` errors.
Walking the upstream `google.genai.types` API:
- `GoogleMaps`, `AuthConfig`: present from 1.40.0
- `FileSearch`: introduced in 1.49.0
- `ThinkingLevel`: introduced in 1.55.0
- `SearchTypes`, `McpServer`, `StreamableHttpTransport`: introduced in 1.65.0
Bump the lower bound to 1.65.0 — the minimum version that exposes every symbol
the package actually uses. Keep the `<2.0.0` upper cap unchanged. With this
bump `validate-dependency-bounds-test` passes for both lower and upper
resolution scenarios across all 27 workspace packages.
Latent since #4847 (Gemini package introduction in 1.1.0); aggravated by
subsequent feature additions that pulled in newer `types.*` symbols.
* Python: add dependabot bumps to 1.2.1 CHANGELOG
Catalog the 15 dependabot dependency updates that merged on `upstream/main`
between python-1.2.0 and the 1.2.1 cut window under a new Changed section:
- Workspace dev/runtime deps: `rich`, `prek`, `python-multipart`, `pyasn1`,
`pytest` (ag-ui, devui, lab), `uv` (lab)
- Frontend deps: `vite` (devui, chatkit), `postcss` (devui, chatkit, handoff),
`picomatch` (devui, handoff)
CHANGELOG-only — no source or pyproject.toml changes. PRs themselves merged
upstream independently of this release branch and will be brought in via the
PR merge.
* Add requirements.txt and .env.example to a2a sample
Beginners following the a2a/ sample had no pip-based install path:
the directory lacked requirements.txt and .env.example, unlike every
other 04-hosting/ sample.
- Add requirements.txt with editable local package paths matching the
pattern used in azure_functions/ and similar hosting samples
- Add .env.example documenting FOUNDRY_PROJECT_ENDPOINT, FOUNDRY_MODEL,
and A2A_AGENT_HOST
- Update README Quick Start to cover both pip (.venv) and uv workflows
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add `requirements.txt` and `.env.example` to the `a2a/` sample for pip-based setup
Fixes#5395
* fix(a2a-sample): address PR review feedback for issue #5395
- Remove 'from repo root' wording from Option B uv heading in README
to avoid contradicting the 'run from this directory' instruction
- Fix A2A_AGENT_HOST default in .env.example from 5001 to 5000 to match
function-tools flow; add clarifying comments about port usage
- Add note for pip users explaining they can replace 'uv run python'
with 'python' once the virtual environment is activated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5395: Python: [Samples][Python] a2a/ sample missing requirements.txt — beginners cannot install dependencies
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The 4 parallel workflow tests crash because xdist worksteal distributes
them across separate workers, each spawning its own func process against
shared emulators. Auth fix (api_key->credential) was valid and stays.
test_conditional_branching now passes with the auth fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Both samples passed a bearer token provider via api_key= which caused the
client to route to api.openai.com instead of Azure OpenAI, resulting in
401 Unauthorized. Changed to credential= which correctly triggers Azure
routing and picks up AZURE_OPENAI_ENDPOINT from the environment.
- samples/azure_functions/11_workflow_parallel/function_app.py: 1 fix
- samples/durabletask/06_multi_agent_orchestration_conditionals/worker.py: 2 fixes
- Re-enable 4 parallel workflow tests and 1 conditional branching test
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: prevent inner_exception from being lost in AgentFrameworkException
The __init__ method unconditionally called super().__init__() after
the conditional call with inner_exception, effectively overwriting the
exception args and losing the inner_exception reference.
Add else branch so super().__init__() is only called once with the
correct arguments.
Fixes#5155
Signed-off-by: bahtya <bahtyar153@qq.com>
* test: add explicit tests for AgentFrameworkException inner_exception handling
- test_exception_with_inner_exception: verifies args include inner exception
- test_exception_without_inner_exception: verifies args only contain message
- test_exception_inner_exception_none_explicit: verifies explicit None
Covers both branches of the if/else in __init__.
* fix: export AgentFrameworkException from package
Bahtya
---------
Signed-off-by: bahtya <bahtyar153@qq.com>
* Adding support for "wait for response" when invoking workflow http endpoint.
* update changelog.
* PR comment fixes.
* Address PR review feedback.
- Return 404 Not Found when no orchestration with the given ID exists
- Return 200 OK for failed workflows (the HTTP operation succeeded;
the workflow outcome is conveyed via the response body)
- Rename 'status' to 'workflowStatus' in WorkflowRunResponse to avoid
inconsistency with AgentRunSuccessResponse which uses integer status
- Add optional 'error' field (omitted from JSON when null) to
WorkflowRunResponse for failed workflow details
* Bump OpenTelemetry packages to 1.15.3 to fix known vulnerabilities
Update OpenTelemetry packages from 1.15.0 to 1.15.3 in Directory.Packages.props
to resolve NU1902 warnings-as-errors for CVEs GHSA-g94r-2vxg-569j,
GHSA-mr8r-92fq-pj8p, and GHSA-q834-8qmm-v933.
Add explicit PackageReference for OpenTelemetry.Exporter.OpenTelemetryProtocol
in Foundry.Hosting and OpenTelemetry.Api + OpenTelemetry.Exporter.OpenTelemetryProtocol
in Hosted-Invocations-EchoAgent to override transitive 1.15.0 resolution in
projects with CentralPackageTransitivePinningEnabled=false.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump OpenTelemetry Extensions and Instrumentation packages to 1.15.x
Align the full OpenTelemetry package set to the 1.15.x family:
- OpenTelemetry.Extensions.Hosting: 1.14.0 -> 1.15.3
- OpenTelemetry.Instrumentation.AspNetCore: 1.14.0 -> 1.15.2
- OpenTelemetry.Instrumentation.Http: 1.14.0 -> 1.15.1
- OpenTelemetry.Instrumentation.Runtime: 1.14.0 -> 1.15.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Remove hard skips from 4 tests in test_11_workflow_parallel.py
- Remove hard skip from test_conditional_branching in test_06_dt_multi_agent_orchestration_conditionals.py
- Increase pytest --timeout from 360 to 480 for Functions+DurableTask CI job
- Updated in both python-merge-tests.yml and python-integration-tests.yml
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Remove the hard skip on test_03_reliable_streaming tests that was
temporarily disabled for instability investigation. CI infrastructure
(Azurite, DTS emulator, Redis, func CLI) is already in place.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump Python package versions for 1.2.0 release
Released tier bumps 1.1.1 -> 1.2.0 (core, openai, foundry, root) to
reflect additive public APIs landed since 1.1.0: functional workflow API
(#4238) and FunctionTool SKIP_PARSING sentinel (#5424). All beta packages
stamped 1.0.0b260424, alpha packages 1.0.0a260424. All 26 non-core
agent-framework-core floors raised to >=1.2.0,<2. CHANGELOG consolidates
the never-tagged 1.1.1 entries with the post-merge additions into [1.2.0].
* Update CHANGELOG footer links for 1.2.0
Advance [Unreleased] comparison base from python-1.1.0 to python-1.2.0
and add a [1.2.0] reference link comparing python-1.1.0...python-1.2.0
so the heading links resolve correctly.
* Fix CHANGELOG: restore [1.1.1] section and add proper [1.2.0]
Previous commit incorrectly renamed the [1.1.1] header to [1.2.0], which
wiped the historical 1.1.1 entries and wrongly attributed them to 1.2.0.
This restores [1.1.1] to its origin/main content and adds a new [1.2.0]
section above containing only the commits in python-1.1.1..HEAD:
- #4238 functional workflow API
- #5142 GitHub Copilot OpenTelemetry
- #2403 A2A bridge support
- #5070 oauth_consent_request events in Foundry clients
- #5447 FoundryAgent hosted agent sessions
- #5459 hosting server dependency upgrade + types
- #5389 AG-UI reasoning/multimodal parsing fix
- #5440 stop [TOOLBOXES] warning spam
- #5455 user agent prefix fix
Also corrects the [1.2.0] compare base to python-1.1.1 (not 1.1.0) and
adds the missing [1.1.1] reference link.
* Fix Foundry clients not surfacing oauth_consent_request events (#5054)
Override _parse_chunk_from_openai in both RawFoundryChatClient and
RawFoundryAgentChatClient to intercept response.output_item.added
events with item.type == 'oauth_consent_request'. The consent link
is validated (HTTPS required) and converted to
Content.from_oauth_consent_request, which the AG-UI layer already
knows how to emit as a CUSTOM event.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review feedback for #5054 OAuth consent parsing
- Extract shared helper (try_parse_oauth_consent_event) to avoid
duplicated logic between RawFoundryChatClient and
RawFoundryAgentChatClient
- Use urllib.parse.urlparse() for HTTPS validation instead of
case-sensitive startswith check
- Sanitize log messages to avoid leaking consent_link tokens;
log only item id
- Add model=self.model to ChatResponseUpdate to match parent behavior
- Add assertions on role, raw_representation, and model in happy-path
tests
- Add test for empty-string consent_link
- Add test verifying non-oauth events delegate to super()
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Handle response.oauth_consent_requested top-level event (#5054)
Add support for the top-level response.oauth_consent_requested stream
event in addition to the response.output_item.added variant. The
service may emit either form; handle both so the consent link is
reliably surfaced.
Extract _validate_consent_link helper within _oauth_helpers.py to
reduce nesting.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5054: Python: [Bug]: `FoundryAgent` (Responses API) Does Not Surface `oauth_consent_request` as a CUSTOM AG-UI Event
* Address review feedback: defensive getattr and dedicated helper tests (#5054)
- Use getattr(event, 'type', None) in try_parse_oauth_consent_event
for defensive access against malformed events without a type attribute
- Add test_oauth_helpers.py with unit tests for _validate_consent_link
and try_parse_oauth_consent_event covering edge cases:
- HTTPS URL with empty netloc (https:///path)
- Warning log messages for rejected consent links
- Event objects missing 'type' attribute
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5054: Python: [Bug]: `FoundryAgent` (Responses API) Does Not Surface `oauth_consent_request` as a CUSTOM AG-UI Event
* Fix mypy: match _parse_chunk_from_openai signature with superclass
Add seen_reasoning_delta_item_ids parameter to _parse_chunk_from_openai
overrides in both RawFoundryChatClient and RawFoundryAgentChatClient to
match the updated superclass signature on main. Update super() calls and
test assertions accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
* Add functional workflow api
* cleanup
* More cleanup
* address copilot feedback
* Address PR feedbacK
* updates
* PR feedback
* Address review comments on functional workflow samples
- Swap 05/06 get-started samples: agent workflow first (motivates
why workflows exist), simple text workflow second
- Rename text_pipeline → text_workflow, poem_pipeline → poem_workflow
- Add @step to agent workflow sample (05) to demonstrate caching
- Switch agent samples to AzureOpenAIResponsesClient with Foundry
- Remove .as_agent() from agent_integration.py to focus on the key
difference between inline agent calls vs @step-cached calls
- Add commented-out Agent.run example in hitl_review.py
- Add clarifying comment in _functional.py that event streaming is
buffered (not true per-token streaming)
- Add naive_group_chat.py functional sample: round-robin group chat
as a plain Python loop
- Update READMEs to reflect new file names and group chat sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pyright type errors
* Address PR review comments on functional workflow API
1. Allow request_info inside @step: Auto-inject RunContext into step
functions that declare a RunContext parameter (by type or name 'ctx'),
and expose get_run_context() for programmatic access.
2. Handle None responses: Log a warning when a response value is None,
and document the behavior in request_info docstring.
3. Add executor_bypassed event type: Replace executor_invoked +
executor_completed with a single executor_bypassed event when a step
replays from cache, making cached vs live execution explicit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add regression tests for PR review comments on functional workflow API
The three review comments (request_info in @step, None response handling,
executor_bypassed event type) were already addressed in 7da7db4e. This
commit adds cross-cutting regression tests that exercise the interactions
between these features:
- HITL in step with caching: preceding step bypassed on resume
- Full checkpoint lifecycle with HITL step (interrupt -> resume -> restore)
- None response inside step-level request_info logs warning
- WorkflowInterrupted from step does not emit executor_failed
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #4238 review comments on functional workflow API
Comment 1 (request_info in @step): Already supported. Added comment in
StepWrapper.__call__ explaining why WorkflowInterrupted (BaseException)
safely bypasses the except Exception handler.
Comment 2 (None response): Added docstring to _get_response clarifying
the (found, value) return tuple semantics and None handling.
Comment 3 (bypass event type): executor_bypassed is already a dedicated
event type in WorkflowEventType. Updated comment at the bypass site to
make the deliberate event type choice explicit.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add experimental API warnings to functional workflow module
Mark all public classes and decorators (workflow, step, RunContext,
FunctionalWorkflow, StepWrapper, FunctionalWorkflowAgent) as
experimental and subject to change or removal.
* Address PR #4238 review comments from @eavanvalkenburg
- RunContext docstring leads with purpose (opt-in handle for HITL,
custom events, state) so readers importing it from the public surface
understand its role before the mechanics (#2993513452).
- Rename `06_first_functional_workflow.py` to
`06_functional_workflow_basics.py`; the previous filename was
confusing since it followed `05_functional_workflow_with_agents.py`
(#2993531979).
- Simplify `05_functional_workflow_with_agents.py` to call agents
directly without a @step wrapper; the step-vs-no-step contrast lives
in `03-workflows/functional/agent_integration.py`, keeping the
get-started sample minimal (#2993525532).
- Switch functional samples to `FoundryChatClient` for consistency with
the rest of 01-get-started and 03-workflows (follow-up on #2876988570).
- Use walrus in `hitl_review.py` final-state assertion (#2993572182).
- Add expected-output block to `basic_streaming_pipeline.py` (#2993557609).
- Clarify in `parallel_pipeline.py` that `@step` composes with
`asyncio.gather` (#2993597282).
- `naive_group_chat.py` threads `list[Message]` between turns instead
of stringifying the transcript, preserving role/authorship (#2993583231).
Drive-by: pre-commit hook sorts an unrelated import block in
`samples/04-hosting/foundry-hosted-agents/responses/02_local_tools/main.py`.
* Fix 10 functional-workflow API bugs from /ultrareview pass
- bug_001: `ctx.request_info()` without an explicit `request_id` now derives
a deterministic `auto::<index>` id from the call-counter, so HITL resume
works correctly on the documented default path. A uuid was regenerated on
every replay, making resume impossible.
- bug_002: `StepWrapper.__call__` no longer deepcopies arguments on the
cache-hit replay branch. The copy is only performed on the live-execution
path (for the event log) and falls back to the original mapping if deepcopy
fails, so steps whose args aren't deepcopyable (locks, sockets, sessions)
can still resume from checkpoint.
- bug_007: `_set_responses` now prunes each resolved `request_id` from
`_pending_requests`, and the cache-hit branch in `request_info` does the
same. Previously, answered requests were re-serialized into every
subsequent checkpoint and the final checkpoint falsely claimed pending
requests even after the workflow completed.
- bug_008: `_compute_signature_hash` now mixes the function's `co_code` and
`co_names` into the checkpoint signature, so changes to the workflow body
invalidate older checkpoints even when steps are accessed via module /
class attributes (which `_discover_step_names` can't see statically).
`RunContext._record_observed_step` records observed step names for
diagnostics.
- bug_010: `FunctionalWorkflow.run()` docstring corrected — says "at least
one of message/responses/checkpoint_id" and explicitly notes `responses`
may be combined with `checkpoint_id` (the validator already allowed this).
- bug_013: `FunctionalWorkflowAgent` now surfaces `request_info` events as
`FunctionApprovalRequestContent` items (mirroring graph `WorkflowAgent`),
threads `responses=` and `checkpoint_id=` through to the underlying
workflow, and exposes `pending_requests`. Previously `.as_agent()`
returned empty `AgentResponse` for HITL workflows — effectively unusable.
- bug_014: `FunctionalWorkflow` now clears `_last_message`,
`_last_step_cache`, and `_last_pending_request_ids` on clean completion.
`run()` validates that `responses=` keys intersect the currently-pending
request set (or raises with a clear error) instead of silently replaying
against stale singleton state from a prior run.
- bug_015: `FunctionalWorkflow.as_agent` signature now matches graph
`Workflow.as_agent`: accepts `name`, `description`, `context_providers`,
and `**kwargs`. `FunctionalWorkflowAgent` stores the overrides.
- bug_017: `RunContext.set_state` raises `ValueError` for underscore-
prefixed keys (the framework's `_step_cache` / `_original_message` keys
would silently clobber user state on checkpoint save and user
underscore-prefixed state was dropped on restore). Docstring documents
the reserved prefix.
- merged_bug_003: Workflow function arity is validated at decoration time.
Multiple non-ctx parameters raise `ValueError` immediately (previously
every arg past the first was silently dropped at call time). Passing a
non-None `message` to a ctx-only workflow raises `ValueError` instead of
silently discarding the message.
Test coverage: +18 regression tests covering every fix. Full workflow
suite now 766 passed, 1 skipped, 2 xfailed; full core suite 2338 passed.
* Deslop functional.py fix commit
- Remove dead instrumentation added in the prior commit that was never
consumed: `RunContext._observed_step_names`,
`RunContext._record_observed_step`, `FunctionalWorkflow._runtime_step_names`,
and `FunctionalWorkflowAgent._extra_kwargs`. The signature hash relies on
`co_code` alone, which covers the attribute-access case without the
collection-scaffolding.
- Trim over-explanatory comments that restated what the code does or what
it no longer does. Keep only the comments that answer "why" for the
non-obvious bits (deterministic id contract, defensive deepcopy, stale
replay guard).
- Compress the `_compute_signature_hash` and FunctionalWorkflow `__init__`
block docstrings without losing the user-facing reasoning.
Net -49 lines. Regression lock preserved (766 passed, 1 skipped, 2 xfailed).
* Fix functional workflow review feedback
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <copilot@github.com>
* fixes to FoundryAgent to connect to new hosted agents
Co-authored-by: Copilot <copilot@github.com>
* fix mypy
Co-authored-by: Copilot <copilot@github.com>
* Python: remove Foundry service session helpers
Remove the public hosted-agent service session CRUD helpers from FoundryAgent and drop the related feature-stage inventory entry.
Update the hosted-agent sample to create and delete service sessions directly through the preview AIProjectClient APIs, and tighten a few test harnesses surfaced by full workspace validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix from merge
* fix hosted env detection
Co-authored-by: Copilot <copilot@github.com>
* reverted sample update
* fix tests and code
Co-authored-by: Copilot <copilot@github.com>
* remove aenter
* skipping some tests
Co-authored-by: Copilot <copilot@github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add OpenTelemetry integration for GitHubCopilotAgent
- Split GitHubCopilotAgent into RawGitHubCopilotAgent (core, no OTel) and
GitHubCopilotAgent(AgentTelemetryLayer, RawGitHubCopilotAgent) with tracing
- Add default_options property to expose model for span attributes
- Export RawGitHubCopilotAgent from all public namespaces
- Add github_copilot_with_observability.py sample and update README
* Python: Fix OTEL_SERVICE_NAME default in GitHub Copilot README
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* Python: Add unit tests for RawGitHubCopilotAgent.default_options property
* Python: Address review feedback on GitHubCopilotAgent OTel integration
- Add middleware param to GitHubCopilotAgent.run() overloads so per-call
middleware is explicitly forwarded through AgentTelemetryLayer
- Remove github_copilot_with_observability.py sample per feedback; replace
with inline snippet + link to observability samples in README
* Python: Address review feedback on log_level and session kwargs typing
- Add middleware param to RawGitHubCopilotAgent.run() overloads for interface
compatibility with AgentTelemetryLayer
- Fix import in README observability snippet to use agent_framework.github
* Python: Add AgentMiddlewareLayer to GitHubCopilotAgent MRO
Follow FoundryAgent pattern: AgentMiddlewareLayer runs outside the telemetry
span so middleware execution time is not captured in traces. Overloads removed
as AgentMiddlewareLayer.run() handles dispatch via MRO.
* Python: Add explicit __init__ to GitHubCopilotAgent for auto-complete and docstrings
* Python: Address review feedback on middleware warning and test assertions
- Add assert "timeout" not in opts to test_default_options_includes_model_for_telemetry
to document the intentional asymmetry where timeout is extracted into _settings
and not returned in default_options.
- Replace silent del middleware with a logged warning when per-run middleware is
passed to RawGitHubCopilotAgent, making it clear that the GitHub Copilot SDK
handles tool execution internally and chat/function middleware cannot be injected.
* Python: Use Self for __aenter__ return type in RawGitHubCopilotAgent
Address review feedback: use typing.Self (3.11+) / typing_extensions.Self
(3.10) for __aenter__ so subclasses like GitHubCopilotAgent get the correct
return type from async context manager usage.
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: Add Agent Framework to A2A bridge support
- Implement A2A event adapter for converting agent messages to A2A protocol
- Add A2A execution context for managing agent execution state
- Implement A2A executor for running agents in A2A environment
- Add comprehensive unit tests for event adapter, execution context, and executor
- Update agent framework core A2A module exports and type stubs
- Integrate thread management utilities for async execution
- Add getting started sample for A2A agent framework integration
- Update dependencies in uv.lock
This integration enables agent framework agents to communicate and execute within the A2A (Agent to Agent) infrastructure.
* fix: Update references from agent_thread_storage to _agent_thread_storage in A2A executor tests
* Refactor A2A agent framework and improve code structure
- Reordered imports in various files for consistency and clarity.
- Updated `__all__` definitions to maintain a consistent order across modules.
- Simplified method signatures by removing unnecessary line breaks.
- Enhanced readability by adjusting formatting in several sections.
- Removed redundant comments and example scenarios in the execution context.
- Improved handling of agent messages in the event adapter.
- Added type hints for better clarity and type checking.
- Cleaned up test cases for better organization and readability.
* fix: Lint fix new line added
* test: Add unit tests for AgentThreadStorage and InMemoryAgentThreadStorage
* refactor: Update type hints to use new syntax for Union and List
* fix: Validate RequestContext for context_id and message before execution
* Refactor tests and remove A2aExecutionContext references
- Deleted the test file for A2aExecutionContext as it is no longer needed.
- Updated A2aExecutor tests to remove dependencies on A2aExecutionContext and adjusted method calls accordingly.
- Modified event adapter tests to use ChatMessage instead of AgentRunResponseUpdate.
- Removed A2aExecutionContext from imports in agent_framework.a2a module and updated type hints accordingly.
* Refactor A2AExecutor tests and remove event adapter
- Updated test cases to use A2AExecutor instead of A2aExecutor for consistency.
- Removed mock_event_adapter fixture and related tests as A2aEventAdapter is deprecated.
- Consolidated event handling tests into TestA2AExecutorEventAdapter.
- Adjusted imports in various files to reflect the removal of deprecated components.
- Ensured all references to A2aExecutor are updated to A2AExecutor across the codebase.
* refactor: Remove AgentThreadStorage and InMemoryAgentThreadStorage classes from threads and tests
* feat: A2AExecutor to have its own override able save and get threads methods for persistent storage.
* fix: linter bugs
* removed unnecessary changes form core package
* new line added
* Refactor A2AExecutor tests and update imports
- Consolidated mock agent fixtures in test_a2a_executor.py to simplify agent mocking.
- Removed redundant tests related to thread storage and agent types, focusing on A2AExecutor's core functionality.
- Updated test assertions to reflect changes in message handling with new Message and Content classes.
- Enhanced integration tests to ensure compatibility with the new agent framework structure.
- Added A2AExecutor to the module exports in __init__.py and __init__.pyi for better accessibility.
* Update A2A documentation: enhance usage examples for A2AAgent and A2AExecutor
* Updated uv lock
* Fix metadata assertion in TestA2AExecutorHandleEvents and reorder load_dotenv call in agent_framework_to_a2a.py
* Update agent card configuration: add default input and output modes, and fix agent creation method
* Fix assertion for metadata in TestA2AExecutorHandleEvents
* Fix formatting issues in TestA2AExecutorExecute and TestA2AExecutorIntegration
* Enhance A2AExecutor documentation with examples and clarify agent execution process
* Revert uv lock to main
* Refactor A2AExecutor: Improve formatting and streamline constructor parameters
* Apply suggestions from code review
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Refactor A2AExecutor to use SupportsAgentRun and enhance logging; update agent framework sample for flight and hotel booking capabilities
* Enhance A2AExecutor with streaming support and custom run arguments; update tests for initialization and execution scenarios
* Enhance A2AExecutor event handling with streamed artifact tracking; update tests for new behavior
* Refactor A2AExecutor to enforce type hints for stream and run_kwargs attributes
* Refactor A2AExecutor and tests: replace AsyncMock with MagicMock for response stream handling; clean up imports in agent_framework_to_a2a.py
* refactor: streamline imports and improve code readability across multiple files
* feat: enhance A2AExecutor cancel method with context validation and fixed review comments
* feat: implement get_uri_data utility function for extracting base64 data from data URIs and update references
* fix: update import path for get_uri_data utility function in A2AExecutor and A2AAgent
* fix: correct error message handling in A2AExecutor and update test assertions
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Fix AG-UI reasoning role and multimodal media value field parsing
Fix two spec compliance issues in the AG-UI integration:
1. ReasoningMessageStartEvent now uses role='reasoning' instead of
role='assistant', matching the AG-UI specification for reasoning
messages.
2. _parse_multimodal_media_part now reads the 'value' field from source
dicts (with fallback to 'data' for backward compatibility), matching
the current AG-UI InputContentSource specification.
Bump ag-ui-protocol dependency from ==0.1.13 to >=0.1.16,<0.2 to pick
up the SDK fix that accepts role='reasoning' in ReasoningMessageStartEvent.
Fix pre-existing pyright reportMissingImports errors for orjson in sample
files, and fix import ordering in foundry-hosted-agents sample.
Fixes#5340
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix AG-UI reasoning role and multimodal media parsing to follow specification
Fixes#5340
* Remove unintended .maf-runtime-ready marker file
Address PR review feedback: the .maf-runtime-ready file is not referenced anywhere in the repo and was left over from automation.
Fixes#5340
* Python: Fix duplicate AG-UI multimodal 'value' parsing in snapshot path
The snapshot normalization path used a second copy of the multimodal source
parsing logic that still read the deprecated 'data' field. When clients sent
base64 media with source={"type": "base64", "value": ...}, the snapshot event
emitted by the server dropped the payload, causing AG-UI-compatible clients
to crash on ingest.
Extract the shared source-field extraction into _extract_multimodal_source_fields
so both _parse_multimodal_media_part and the snapshot _legacy_binary_part stay
in sync with the AG-UI spec. Add snapshot-path regression tests covering
value-only, value-preferred-over-data, and the legacy data-field fallback.
Addresses review feedback on #5389 from @Rickyneer.
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Foundry: make response tool sanitizer internal, drop TOOLBOXES warning
sanitize_foundry_response_tool runs on every tool passed to the Foundry
Responses API, so its @experimental(TOOLBOXES) decorator was emitting a
[TOOLBOXES] ExperimentalWarning for any FoundryChatClient call, even when
no toolbox was involved. The function isn't in __all__ and has no external
callers. Rename to _sanitize_foundry_response_tool and drop the decorator;
the actual toolbox-facing public helpers remain gated.
* Python: Foundry: silence pyright on intentional cross-module private import
The 0.5b model was too small to reliably follow simple prompts like
'Say Hello World', causing test assertion failures. The 1.5b model
follows instructions more reliably while still being small enough
for fast CI pulls (~1GB).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- Install Ollama, cache models (qwen2.5:0.5b + nomic-embed-text), and start
server in the Misc integration job for both workflow files
- Set OLLAMA_MODEL and OLLAMA_EMBEDDING_MODEL env vars so the 5 Ollama tests
are no longer skipped
- Rename Flaky Test Report to Integration Test Report throughout (job names,
artifact names, cache keys, file names, script titles/docstrings)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* update a2a agent to the latest a2a sdk (#5257)
* Move A2A samples from 04-hosting to 02-agents (#5267)
Move the A2A sample projects (A2AAgent_AsFunctionTools and
A2AAgent_PollingForTaskCompletion) from samples/04-hosting/A2A/ to
samples/02-agents/A2A/ to better align with the sample directory
structure. Update solution file and samples README accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix stream reconnection for A2AAgent (#5275)
* Add SSE stream reconnection support to A2AAgent
Implement automatic reconnection for SSE streams that disconnect mid-task,
using the Last-Event-ID header to resume from where the stream left off.
Changes:
- Add InvokeStreamingWithReconnectAsync method to A2AAgent with configurable
max retries and delay between attempts
- Add new log messages for reconnection events
- Add A2AAgent_StreamReconnection sample demonstrating the feature
- Update existing polling sample to use simplified SendMessageAsync API
- Add unit tests for stream reconnection logic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address comments
* Address PR review feedback
- Dispose SSE enumerator before GetTaskAsync fallback to release HTTP connection
- Wrap StreamWriter in using blocks with leaveOpen:true and explicit UTF-8 encoding
- Print update.Text instead of update object in stream reconnection sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Use IA2AClientFactory to create A2AClient (#5277)
* Refactor A2A extensions to use IA2AClientFactory and add ProtocolSelection sample
- Update A2AAgentCardExtensions to accept IA2AClientFactory instead of A2AClientOptions
- Update A2ACardResolverExtensions to accept IA2AClientFactory
- Update A2AClientExtensions to accept IA2AClientFactory
- Update A2AAgent to use IA2AClientFactory for client creation
- Add A2AAgent_ProtocolSelection sample demonstrating protocol selection
- Add comprehensive unit tests for all changes
- Update README files with new sample reference
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Reorder params: options before loggerFactory in A2A extensions
Move A2AClientOptions parameter before ILoggerFactory in AsAIAgent
and GetAIAgentAsync extension methods to follow the repo convention
of keeping LoggerFactory and CancellationToken as the last parameters.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Migrate A2A hosting to A2A SDK v1 (#5363)
* .NET: Migrate A2A hosting to A2A SDK v1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* remove unused agent card
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Split A2A endpoint mapping into protocol-specific methods (#5413)
* .NET: Refactor A2A hosting registration into A2AServerServiceCollectionExtensions
- Rename A2AHostingOptions to A2AServerRegistrationOptions
- Move server registration logic from A2AEndpointRouteBuilderExtensions
and AIAgentExtensions into new A2AServerServiceCollectionExtensions
- Remove A2AProtocolBinding and AIAgentExtensions (consolidated)
- Update samples and tests to use the new registration API
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address copilot comments
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unnecessary using directive in AgentWebChat.AgentHost
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* restore AsyncEnumerable package version
* address copilot initial feedback
* address automated code review and formatting issues
* fix formatting issues
* Add streaming support to A2A agent handler
Add HandleNewMessageStreamingAsync to A2AAgentHandler that routes
StreamingResponse requests through RunStreamingAsync, enqueuing an A2A
Message for each AgentResponseUpdate.
Add MessageConverter.ToParts(AgentResponseUpdate) extension to convert
streaming update contents to A2A Parts with unsupported-content filtering.
Add CreateMessageFromUpdate to map AgentResponseUpdate to A2A Message.
Add 16 new tests covering the streaming path and converter.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add streaming edge-case tests for A2AAgentHandler
Add two tests covering gaps in the streaming path:
- ExecuteAsync_Streaming_WhenNoUpdates_EnqueuesNoMessagesAndSavesSessionAsync:
Verifies that when RunStreamingAsync yields an empty async enumerable,
no messages are enqueued and only SaveSessionAsync runs.
- ExecuteAsync_Streaming_CancellationTokenIsPropagatedToRunStreamingAsyncAsync:
Verifies that the CancellationToken from ExecuteAsync is propagated
through to the inner agent's RunCoreStreamingAsync call.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address copilot comments
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Scopes the triage job to the integration GitHub Environment, adds
the azure/login OIDC step, and exposes the same OpenAI / Azure
OpenAI / Foundry / Anthropic env vars the integration test
workflow uses. This lets the triage agent write repro code that
constructs model clients from the environment without any secrets
entering the agent prompt or generated-code literals.
Azure OpenAI and Foundry continue to authenticate via AAD
(DefaultAzureCredential), so there is no API key to leak for
those providers.
* Automated issue triage workflow
* Bump dependencies
* Fix issue-triage workflow: security, reliability, and testability
Address six review comments on the issue-triage workflow:
1. Change trigger from issues:opened to issues:labeled so the
secret-backed triage flow is only triggered by a maintainer-
controlled signal.
2. Include inputs.issue_number in the concurrency group so
workflow_dispatch runs for the same issue are properly
de-duplicated.
3. Improve team membership error handling to fail closed: verify
the team exists before checking membership, and only treat a
404 as 'not a member' (all other errors fail the job).
4. Use optional chaining (issue.user?.login) for the API-fetched
issue to handle deleted GitHub accounts without crashing.
5. Extract the inline github-script into a testable module at
.github/scripts/check_team_membership.js with 10 tests in
.github/tests/test_check_team_membership.js covering all
code paths (payload/API author resolution, deleted accounts,
team lookup failure, 404 vs non-404 membership errors).
6. Make the spam gate actually stop the job by exiting non-zero
instead of just logging, so future steps cannot accidentally
run for spam issues.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Make issue-triage workflow manually triggered only for initial testing
Remove the 'issues' event trigger, keeping only 'workflow_dispatch' so the
workflow can be tested manually before enabling automatic triggers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* improved parsing of tool call results and tweaks
* Address PR review: skip_parsing flag, broader registry close, comment fix
- FunctionTool.invoke now takes a boolean skip_parsing flag instead of the
SKIP_PARSING sentinel; the sentinel is still accepted as result_parser at
construction time to opt out of parsing for every call. The two paths are
equivalent.
- _SandboxRegistry.close now invokes any sandbox close/shutdown hook on the
entry's own worker thread (PyO3 unsendable), then shuts the worker down,
then cleans up the per-entry temporary directories.
- Clarified the _SandboxWorker.shutdown comment to describe the actual
ThreadPoolExecutor.shutdown(wait=False, cancel_futures=False) semantics.
- Hyperlight host callback uses skip_parsing=True (the new flag).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Drop redundant 'is not SKIP_PARSING' guard that mypy 1.x flags
After callable(configured_parser) the sentinel is already excluded; the extra
identity check tripped mypy's non-overlapping identity warning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fixed sandbox working on copy of tool
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* update a2a agent to the latest a2a sdk (#5257)
* Move A2A samples from 04-hosting to 02-agents (#5267)
Move the A2A sample projects (A2AAgent_AsFunctionTools and
A2AAgent_PollingForTaskCompletion) from samples/04-hosting/A2A/ to
samples/02-agents/A2A/ to better align with the sample directory
structure. Update solution file and samples README accordingly.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Fix stream reconnection for A2AAgent (#5275)
* Add SSE stream reconnection support to A2AAgent
Implement automatic reconnection for SSE streams that disconnect mid-task,
using the Last-Event-ID header to resume from where the stream left off.
Changes:
- Add InvokeStreamingWithReconnectAsync method to A2AAgent with configurable
max retries and delay between attempts
- Add new log messages for reconnection events
- Add A2AAgent_StreamReconnection sample demonstrating the feature
- Update existing polling sample to use simplified SendMessageAsync API
- Add unit tests for stream reconnection logic
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address comments
* Address PR review feedback
- Dispose SSE enumerator before GetTaskAsync fallback to release HTTP connection
- Wrap StreamWriter in using blocks with leaveOpen:true and explicit UTF-8 encoding
- Print update.Text instead of update object in stream reconnection sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Use IA2AClientFactory to create A2AClient (#5277)
* Refactor A2A extensions to use IA2AClientFactory and add ProtocolSelection sample
- Update A2AAgentCardExtensions to accept IA2AClientFactory instead of A2AClientOptions
- Update A2ACardResolverExtensions to accept IA2AClientFactory
- Update A2AClientExtensions to accept IA2AClientFactory
- Update A2AAgent to use IA2AClientFactory for client creation
- Add A2AAgent_ProtocolSelection sample demonstrating protocol selection
- Add comprehensive unit tests for all changes
- Update README files with new sample reference
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Reorder params: options before loggerFactory in A2A extensions
Move A2AClientOptions parameter before ILoggerFactory in AsAIAgent
and GetAIAgentAsync extension methods to follow the repo convention
of keeping LoggerFactory and CancellationToken as the last parameters.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Migrate A2A hosting to A2A SDK v1 (#5363)
* .NET: Migrate A2A hosting to A2A SDK v1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* remove unused agent card
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Split A2A endpoint mapping into protocol-specific methods (#5413)
* .NET: Refactor A2A hosting registration into A2AServerServiceCollectionExtensions
- Rename A2AHostingOptions to A2AServerRegistrationOptions
- Move server registration logic from A2AEndpointRouteBuilderExtensions
and AIAgentExtensions into new A2AServerServiceCollectionExtensions
- Remove A2AProtocolBinding and AIAgentExtensions (consolidated)
- Update samples and tests to use the new registration API
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address copilot comments
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unnecessary using directive in AgentWebChat.AgentHost
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* restore AsyncEnumerable package version
* address copilot initial feedback
* address automated code review and formatting issues
* fix formatting issues
* Add DI wiring verification tests for AddA2AServer
Add three tests to A2AServerServiceCollectionExtensionsTests that verify
custom keyed services are actually wired through to the A2AServer, not
just that the server resolves non-null:
- Custom IAgentHandler: verifies the keyed handler is invoked when
processing a SendMessageRequest instead of the default A2AAgentHandler.
- Custom AgentSessionStore (no handler): verifies the keyed session
store's GetSessionAsync is called during request processing when no
custom handler is registered.
- Default stores end-to-end: verifies the InMemoryAgentSessionStore and
InMemoryTaskStore defaults successfully process a request. Uses a new
CreateAgentMockForRequests helper that includes SerializeSessionCoreAsync
setup needed by InMemoryAgentSessionStore.
All tests call A2AServer.SendMessageAsync directly (no HTTP layer needed)
and use CancellationToken timeouts to guard against hangs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump Python version for a release.
* Revert lockstep bumps on unchanged connectors
Per PR review: only connectors that changed (or whose published metadata
changed) should get new versions. Keeps released tier at 1.1.1, a2a/ag-ui
at 1.0.0b260422, foundry-hosting at 1.0.0a260422; reverts the 19 unchanged
betas and 2 unchanged alphas to 1.0.0b260421/1.0.0a260421. Reverts all 26
non-core agent-framework-core floors to >=1.1.0,<2 since no connector
actually depends on a 1.1.1 API or bug fix.
* Restore lockstep prerelease bumps and raise core floors to >=1.1.1
Reverses the lean-revert: all beta packages stamped 1.0.0b260423 and alpha
packages stamped 1.0.0a260423 (Asia date, matching release cut time). All
26 non-core packages raise agent-framework-core lower bound from >=1.1.0,<2
to >=1.1.1,<2 to signal the validated cohort for this release. CHANGELOG
date updated to 2026-04-23.
* Add flaky test trend reporting to CI workflows
Parse JUnit XML (pytest.xml) from each integration test job and
aggregate results into a markdown trend report showing per-test
pass/fail/skip status across the last 5 runs.
Changes:
- Add python/scripts/flaky_report/ package (JUnit XML parser + trend
report generator following the sample_validation pattern)
- Add upload-artifact steps to all 6 integration test jobs in both
python-merge-tests.yml and python-integration-tests.yml
- Add python-flaky-test-report aggregation job with history caching
- Add --junitxml=pytest.xml to integration-tests.yml jobs (already
present in merge-tests.yml)
- Fix Cosmos job --junitxml path (use absolute path since uv run
--directory changes cwd)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix flaky report: handle missing test results gracefully
- Guard against missing reports directory in load_current_run()
- Only run report job when at least one integration test job completed
(skip when all jobs are skipped, e.g. on pull_request events)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: fix provider names and if-expression precedence
- Use explicit provider name mapping in _derive_provider() so OpenAI
renders correctly instead of 'Openai'
- Fix operator precedence in workflow if-expressions by wrapping
success/failure checks in parentheses
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add File column and xfail detection to flaky test report
- Add File column showing module name (e.g., test_openai_chat_client)
to disambiguate tests with the same function name across files
- Detect pytest xfail tests in JUnit XML (type=pytest.xfail) and
show them with a distinct warning emoji instead of skip emoji
- Update legend to include xfail explanation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Foundry embedding env vars to merge-tests workflow
Sync the Foundry integration job in python-merge-tests.yml with
python-integration-tests.yml by adding FOUNDRY_MODELS_ENDPOINT,
FOUNDRY_MODELS_API_KEY, FOUNDRY_EMBEDDING_MODEL, and
FOUNDRY_IMAGE_EMBEDDING_MODEL. Once the repo variables/secrets
are configured, the embedding integration test will run in CI.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix File column showing class name instead of module name
When a test is inside a class, pytest writes the classname as e.g.
'pkg.test_file.TestClass'. The previous rsplit logic extracted
'TestClass' instead of 'test_file'. Now detect uppercase-starting
segments as class names and use the preceding segment instead.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review: UTC timestamps, XML error handling, summary fix, docstring
- Use datetime.now(timezone.utc) for accurate UTC timestamps
- Catch ET.ParseError per-file so corrupt XML doesn't crash the report
- Remove separate 'error' key from summary (errors folded into 'failed')
- Fix _short_name docstring to show actual dotted classname::name format
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Pass thread_id as session_id when constructing AgentSession in AG-UI
run_agent_stream() was constructing AgentSession without passing the
client's thread_id as session_id, causing every request to receive a
random UUID. This broke session continuity for HistoryProvider
implementations that rely on session_id matching the client's thread_id.
Pass session_id=thread_id in both the service-session and non-service
code paths so the session identity is consistent with the AG-UI client.
Fixes#5357
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add test for service_session with no thread_id edge case (#5357)
When use_service_session=True but no thread_id/threadId is in the payload,
verify session_id is a generated UUID and service_session_id is None.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Propagate session.service_session_id as A2A context_id
When A2AAgent is used behind the AG-UI protocol, the client thread_id is
stored in session.service_session_id but was never forwarded as the A2A
context_id. This broke session continuity across the AG-UI → A2A boundary.
Add an optional context_id keyword argument to _prepare_message_for_a2a()
and pass session.service_session_id from run(). The explicit
message.additional_properties["context_id"] still takes precedence.
Fixes#5345
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add integration tests for session context_id wiring in run() (#5345)
- Enhance MockA2AClient.send_message to capture last_message for assertions
- Add test_run_passes_session_service_session_id_as_context_id: verifies
run() passes session.service_session_id through to A2A message context_id
- Add test_run_message_context_id_takes_precedence_over_session: verifies
explicit message context_id wins over session fallback
- Update _prepare_message_for_a2a docstring to document context_id param
and its precedence rules
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5345: Python: [Bug]: Inconvenient passing of context_id / thread_id in A2A/AG-UI implementations
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry): reconcile toolbox hosted-tool payloads with Responses API
* docs(foundry): update create_sample_toolbox docstring to reflect all tools created
* Fix streaming response losing created_at from response.completed event (#5347)
The streaming path in _parse_chunk_from_openai did not extract created_at
from the response.completed event, unlike the non-streaming path in
_parse_responses_response. This caused durabletask persistence warnings
when created_at was None.
Extract created_at in the response.completed case and pass it to the
returned ChatResponseUpdate.
Also fix pre-existing pyright errors for optional orjson import in sample
files.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix orjson import suppression to use pyright instead of mypy (#5347)
Replace `# type: ignore[import-not-found]` with
`# pyright: ignore[reportMissingImports]` on optional orjson imports
in conversation sample files, matching the repo's Pyright strict
configuration.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Azure AI Foundry Responses hosting adapter
Implement Microsoft.Agents.AI.Hosting.AzureAIResponses to host agent-framework
AIAgents and workflows within Azure Foundry as hosted agents via the
Azure.AI.AgentServer.Responses SDK.
- AgentFrameworkResponseHandler: bridges ResponseHandler to AIAgent execution
- InputConverter: converts Responses API inputs/history to MEAI ChatMessage
- OutputConverter: converts agent response updates to SSE event stream
- ServiceCollectionExtensions: DI registration helpers
- 336 unit tests across net8.0/net9.0/net10.0 (112 per TFM)
- ResponseStreamValidator: SSE protocol validation tool for samples
- FoundryResponsesHosting sample app
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump System.ClientModel to 1.10.0 for Azure.Core 1.52.0 compat
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clean up tests and sample formatting
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update Azure.AI.AgentServer packages to 1.0.0-alpha.20260401.5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add hosted package version suffix (0.9.0-hosted) to distinguish from mainline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Move Foundry Responses hosting into Microsoft.Agents.AI.Foundry package
Move source and test files from the standalone Hosting.AzureAIResponses project
into the Foundry package under a Hosting/ subfolder. This consolidates the
Foundry-specific hosting adapter into the main Foundry package.
- Source: Microsoft.Agents.AI.Foundry.Hosting namespace
- Tests: merged into Foundry.UnitTests/Hosting/
- Conditionally compiled for .NETCoreApp TFMs only (net8.0+)
- Deleted standalone Hosting.AzureAIResponses project and test project
- Updated sample and solution references
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump package version to 0.9.0-hosted.260402.2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump OpenTelemetry packages to fix NU1109 downgrade errors
- OpenTelemetry/Api/Exporter.Console/Exporter.InMemory: 1.13.1 -> 1.15.0
- OpenTelemetry.Exporter.OpenTelemetryProtocol: already 1.15.0
- OpenTelemetry.Extensions.Hosting: already 1.14.0
- OpenTelemetry.Instrumentation.AspNetCore/Http: already 1.14.0
- OpenTelemetry.Instrumentation.Runtime: 1.13.0 -> 1.14.0
- Azure.Monitor.OpenTelemetry.Exporter: 1.4.0 -> 1.5.0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CA1873: guard LogWarning with IsEnabled check
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix model override bug and add client REPL sample
- InputConverter: stop propagating request.Model to ChatOptions.ModelId
Hosted agents use their own model; client-provided model values like
'hosted-agent' were being passed through and causing server errors.
- Add FoundryResponsesRepl sample: interactive CLI client that connects
to a Foundry Responses endpoint using ResponsesClient.AsAIAgent()
- Bump package version to 0.9.0-hosted.260403.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Catch agent errors and emit response.failed with real error message
Previously, unhandled exceptions from agent execution would bubble up
to the SDK orchestrator, which emits a generic 'An internal server
error occurred.' message — hiding the actual cause (e.g., 401 auth
failures, model not found, etc.).
Now AgentFrameworkResponseHandler catches non-cancellation exceptions
and emits a proper response.failed event containing the real error
message, making it visible to clients and in logs.
OperationCanceledException still propagates for proper cancellation
handling by the SDK.
Also bumps package version to 0.9.0-hosted.260403.2.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Renaming and merging hosting extensions. (#5091)
* Rename AddAgentFrameworkHandler to AddFoundryResponses and add MapFoundryResponses
- Rename extension methods: AddAgentFrameworkHandler -> AddFoundryResponses, MapAgentFrameworkHandler -> MapFoundryResponses
- AddFoundryResponses now calls AddResponsesServer() internally
- Add MapFoundryResponses() extension on IEndpointRouteBuilder
- Update sample and tests to use new API names
- Remove redundant AddResponsesServer() and /ready endpoint from sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing numbering in sample.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address breaking changes in 260408
* Bump hosted internal package version
* Add UserAgent middleware tests for Foundry hosting
* Hosting Samples update
* Hosting Samples update
* Hosting Samples update
* Hosting Samples update
* ChatClientAgent working
* Adding SessionStorage and SessionManagement, improving samples to align Consumption vs Hosting
* Using updates
* Update chat client agent for contributor and devs
* Foundry Agent Hosting
* Address text rag sample working
* Version bump
* Adding LocalTools + Workflow samples
* Removing extra using samples
* Add Hosted-McpTools sample with dual MCP pattern
Demonstrates two MCP integration layers in a single hosted agent:
- Client-side MCP: McpClient connects to Microsoft Learn, agent handles
tool invocations locally (docs_search, code_sample_search, docs_fetch)
- Server-side MCP: HostedMcpServerTool delegates tool discovery and
invocation to the LLM provider (Responses API), no local connection
Includes DevTemporaryTokenCredential for Docker local debugging,
Dockerfile.contributor for ProjectReference builds, and the openai/v1
route mapping for AIProjectClient compatibility in Development mode.
* .NET: Bump Azure.AI.AgentServer packages to 1.0.0-beta.1/beta.21 and fix br… (#5287)
* Bump Azure.AI.AgentServer packages to 1.0.0-beta.1/beta.21 and fix breaking API changes
- Azure.AI.AgentServer.Core: 1.0.0-beta.11 -> 1.0.0-beta.21
- Azure.AI.AgentServer.Invocations: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1
- Azure.AI.AgentServer.Responses: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1
- Azure.Identity: 1.20.0 -> 1.21.0 (transitive requirement)
- Azure.Core: 1.52.0 -> 1.53.0 (transitive requirement)
- Remove azure-sdk-for-net dev feed (packages now on nuget.org)
- Fix OutputConverter for new builder API (auto-tracked children, split EmitTextDone/EmitDone)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing small issues.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Azure AI Foundry Responses hosting adapter
Implement Microsoft.Agents.AI.Hosting.AzureAIResponses to host agent-framework
AIAgents and workflows within Azure Foundry as hosted agents via the
Azure.AI.AgentServer.Responses SDK.
- AgentFrameworkResponseHandler: bridges ResponseHandler to AIAgent execution
- InputConverter: converts Responses API inputs/history to MEAI ChatMessage
- OutputConverter: converts agent response updates to SSE event stream
- ServiceCollectionExtensions: DI registration helpers
- 336 unit tests across net8.0/net9.0/net10.0 (112 per TFM)
- ResponseStreamValidator: SSE protocol validation tool for samples
- FoundryResponsesHosting sample app
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump System.ClientModel to 1.10.0 for Azure.Core 1.52.0 compat
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Clean up tests and sample formatting
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update Azure.AI.AgentServer packages to 1.0.0-alpha.20260401.5
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add hosted package version suffix (0.9.0-hosted) to distinguish from mainline
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Move Foundry Responses hosting into Microsoft.Agents.AI.Foundry package
Move source and test files from the standalone Hosting.AzureAIResponses project
into the Foundry package under a Hosting/ subfolder. This consolidates the
Foundry-specific hosting adapter into the main Foundry package.
- Source: Microsoft.Agents.AI.Foundry.Hosting namespace
- Tests: merged into Foundry.UnitTests/Hosting/
- Conditionally compiled for .NETCoreApp TFMs only (net8.0+)
- Deleted standalone Hosting.AzureAIResponses project and test project
- Updated sample and solution references
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump package version to 0.9.0-hosted.260402.2
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump OpenTelemetry packages to fix NU1109 downgrade errors
- OpenTelemetry/Api/Exporter.Console/Exporter.InMemory: 1.13.1 -> 1.15.0
- OpenTelemetry.Exporter.OpenTelemetryProtocol: already 1.15.0
- OpenTelemetry.Extensions.Hosting: already 1.14.0
- OpenTelemetry.Instrumentation.AspNetCore/Http: already 1.14.0
- OpenTelemetry.Instrumentation.Runtime: 1.13.0 -> 1.14.0
- Azure.Monitor.OpenTelemetry.Exporter: 1.4.0 -> 1.5.0
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CA1873: guard LogWarning with IsEnabled check
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix model override bug and add client REPL sample
- InputConverter: stop propagating request.Model to ChatOptions.ModelId
Hosted agents use their own model; client-provided model values like
'hosted-agent' were being passed through and causing server errors.
- Add FoundryResponsesRepl sample: interactive CLI client that connects
to a Foundry Responses endpoint using ResponsesClient.AsAIAgent()
- Bump package version to 0.9.0-hosted.260403.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Catch agent errors and emit response.failed with real error message
Previously, unhandled exceptions from agent execution would bubble up
to the SDK orchestrator, which emits a generic 'An internal server
error occurred.' message — hiding the actual cause (e.g., 401 auth
failures, model not found, etc.).
Now AgentFrameworkResponseHandler catches non-cancellation exceptions
and emits a proper response.failed event containing the real error
message, making it visible to clients and in logs.
OperationCanceledException still propagates for proper cancellation
handling by the SDK.
Also bumps package version to 0.9.0-hosted.260403.2.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Renaming and merging hosting extensions. (#5091)
* Rename AddAgentFrameworkHandler to AddFoundryResponses and add MapFoundryResponses
- Rename extension methods: AddAgentFrameworkHandler -> AddFoundryResponses, MapAgentFrameworkHandler -> MapFoundryResponses
- AddFoundryResponses now calls AddResponsesServer() internally
- Add MapFoundryResponses() extension on IEndpointRouteBuilder
- Update sample and tests to use new API names
- Remove redundant AddResponsesServer() and /ready endpoint from sample
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing numbering in sample.
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address breaking changes in 260408
* Bump hosted internal package version
* Add UserAgent middleware tests for Foundry hosting
* Hosting Samples update
* Hosting Samples update
* Hosting Samples update
* Hosting Samples update
* ChatClientAgent working
* Adding SessionStorage and SessionManagement, improving samples to align Consumption vs Hosting
* Using updates
* Update chat client agent for contributor and devs
* Foundry Agent Hosting
* Address text rag sample working
* Version bump
* Adding LocalTools + Workflow samples
* Removing extra using samples
* Add Hosted-McpTools sample with dual MCP pattern
Demonstrates two MCP integration layers in a single hosted agent:
- Client-side MCP: McpClient connects to Microsoft Learn, agent handles
tool invocations locally (docs_search, code_sample_search, docs_fetch)
- Server-side MCP: HostedMcpServerTool delegates tool discovery and
invocation to the LLM provider (Responses API), no local connection
Includes DevTemporaryTokenCredential for Docker local debugging,
Dockerfile.contributor for ProjectReference builds, and the openai/v1
route mapping for AIProjectClient compatibility in Development mode.
* Bump Azure.AI.AgentServer packages to 1.0.0-beta.1/beta.21 and fix breaking API changes
- Azure.AI.AgentServer.Core: 1.0.0-beta.11 -> 1.0.0-beta.21
- Azure.AI.AgentServer.Invocations: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1
- Azure.AI.AgentServer.Responses: 1.0.0-alpha.20260408.4 -> 1.0.0-beta.1
- Azure.Identity: 1.20.0 -> 1.21.0 (transitive requirement)
- Azure.Core: 1.52.0 -> 1.53.0 (transitive requirement)
- Remove azure-sdk-for-net dev feed (packages now on nuget.org)
- Fix OutputConverter for new builder API (auto-tracked children, split EmitTextDone/EmitDone)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fixing small issues.
* Fix IDE0009: add 'this' qualification in DevTemporaryTokenCredential
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix IDE0009: add 'this' qualification in all HostedAgentsV2 samples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CHARSET: add UTF-8 BOM to Hosted-LocalTools and Hosted-Workflows
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix dotnet format: add Async suffix to test methods (IDE1006), fix encoding and style
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Register AgentSessionStore in test DI setups
Add InMemoryAgentSessionStore registration to all ServiceCollection
setups in AgentFrameworkResponseHandlerTests and WorkflowIntegrationTests.
This is needed after the AgentSessionStore infrastructure was introduced
in the responses-hosting feature. Tests still have NotImplementedException
stubs for CreateSessionCoreAsync which will be fixed when the session
infrastructure is fully available.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Invocations protocol samples (hosted echo agent + client) (#5278)
Add Hosted-Invocations-EchoAgent: a minimal echo agent hosted via the
Invocations protocol (POST /invocations) using AddInvocationsServer and
MapInvocationsServer, bridged to an Agent Framework AIAgent through a
custom InvocationHandler.
Add SimpleInvocationsAgent: a console REPL client that wraps HttpClient
calls to the /invocations endpoint in a custom InvocationsAIAgent,
demonstrating programmatic consumption of the Invocations protocol.
Both samples default to port 8088 for consistency with other hosted
agent samples.
* Restructure FoundryHostedAgents samples into invocations/ and responses/
Align dotnet hosted agent samples with the Python side (PR #5281) by
reorganizing the directory structure:
- Remove HostedAgentsV1 entirely (old API pattern)
- Split HostedAgentsV2 into invocations/ and responses/ based on protocol
- Move Using-Samples accordingly (SimpleAgent to responses, SimpleInvocationsAgent to invocations)
- Update slnx with new project paths and add previously missing invocations projects
- Update README cd paths from HostedAgentsV2 to invocations or responses
- Rename .env.local to .env.example to match Python naming convention
- Fix format violations in newly included invocations projects
* Remove launchSettings, use .env for port configuration
- Delete all launchSettings.json files (port 8088 now comes from ASPNETCORE_URLS in .env)
- Add DotNetEnv to Hosted-Invocations-EchoAgent so it loads .env like the responses samples
- Create .env.example for EchoAgent with ASPNETCORE_URLS and ASPNETCORE_ENVIRONMENT
- Add AGENT_NAME to ChatClientAgent and FoundryAgent .env.example (required by those samples)
- Add AZURE_BEARER_TOKEN=DefaultAzureCredential to all .env.example files
- Update DevTemporaryTokenCredential in all 6 samples to treat the sentinel value
as unavailable, allowing ChainedTokenCredential to fall through to DefaultAzureCredential
- Update EchoAgent README with Configuration section
* Use placeholder for AGENT_NAME in Hosted-FoundryAgent .env.example
* Move FoundryResponsesHosting to responses/Hosted-WorkflowHandoff, use GetResponsesClient
* Rename Hosted-Workflows to Hosted-Workflow-Simple, Hosted-WorkflowHandoff to Hosted-Workflow-Handoff
* Remove FoundryResponsesRepl and empty FoundryResponsesHosting directory
* Add Dockerfiles, README, agent yamls and bearer token support to Hosted-Workflow-Handoff
- Add Dockerfile and Dockerfile.contributor for Docker-based testing
- Add agent.yaml and agent.manifest.yaml with triage-workflow as primary agent
- Add README.md following sibling pattern, noting Azure OpenAI vs Foundry endpoint
- Add DevTemporaryTokenCredential and ChainedTokenCredential for Docker auth
- Register triage-workflow as non-keyed default so azd invoke works without model
- Update .env.example with AZURE_BEARER_TOKEN sentinel
- Add .gitignore to 04-hosting to suppress VS-generated launchSettings.json
- Fix docker run image name in Hosted-Workflow-Simple README
* Fix AgentFrameworkResponseHandlerTests: implement session methods in test mock agents
* .NET: Auto-instrument resolved AIAgents with OpenTelemetry for Foundry Hosted Agents (#5316)
* Auto-instrument resolved AIAgents with OpenTelemetry using Core ResponsesSourceName
* Add OTel telemetry capture tests for Foundry hosted agent handler
* Net: Prepare Foundry Preview Release (#5336)
* Prepare Foundry preview release 1.2.0-preview.*
Bump VersionPrefix to 1.2.0 and update the preview stamp date. Invert packaging opt-in so only the Foundry preview set produces NuGet packages:
- Microsoft.Agents.AI.Abstractions
- Microsoft.Agents.AI
- Microsoft.Agents.AI.Workflows
- Microsoft.Agents.AI.Workflows.Generators
- Microsoft.Agents.AI.Foundry
Flip IsReleased=false on the preview set so they pick up the -preview.YYMMDD.N suffix. Gate GeneratePackageOnBuild on IsPackable=true. Remove the global IsPackable=true from nuget-package.props so the repo-level default (false) applies to everything else.
* Lower preview VersionPrefix to 0.0.1
Retroactive preview publish: bump VersionPrefix and GitTag from 1.2.0 to 0.0.1 so the 5 Foundry preview packages emit as 0.0.1-preview.260417.1.
* Net: Publish all packages as 0.0.1-preview.260417.2 (#5341)
Revises the Foundry pre-release approach to publish ALL normally packable src projects as preview packages stamped 0.0.1-preview.260417.2, including projects previously flagged IsReleased=true or with a non-default VersionSuffix (rc/alpha).
nuget-package.props:
- Collapse the four conditional PackageVersion expressions (IsReleaseCandidate, VersionSuffix, default preview, IsReleased stable) into a single unconditional 0.0.1-preview.260417.2. On this preview-only branch every package ships with the same pre-release stamp regardless of per-project flags.
- Restore the global IsPackable=true default (offsetting the repo-wide IsPackable=false in Directory.Build.props). Projects that opt out (Mem0, Declarative) already set IsPackable=false AFTER importing this file so they remain non-packable.
- Remove the IsReleased-gated EnablePackageValidation line. Package validation does not apply to a 0.0.1 preview.
csproj reverts (Abstractions, Agents.AI, Workflows, Workflows.Generators, Foundry):
- Revert the IsPackable=true opt-in block introduced in #5336 (now redundant since the props default is true again).
- Restore IsReleased=true to its pre-PR value. The setting is now a no-op because the props no longer branches on it.
* Bump preview version to 260420.1 and fix AgentServer package deps (#5367)
- Bump PackageVersion to 0.0.1-preview.260420.1
- Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by
Azure.AI.AgentServer.Responses beta.3)
- Replace AgentHostTelemetry.ResponsesSourceName with local constant
(type made internal in AgentServer.Core beta.22)
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Hosted agents toolbox support (#5368)
* feat: Add Foundry Toolbox (MCP) support to AgentFrameworkResponseHandler
Adds support for Foundry Toolsets MCP proxy integration in the hosted agent
response handler. Toolsets connect at startup via IHostedService, gating the
readiness probe per spec §3.1. MCP tools are injected into every request's
ChatOptions and OAuth consent errors (-32006) are intercepted and surfaced as
mcp_approval_request + incomplete SSE events.
New files:
- FoundryToolboxOptions.cs: configuration POCO for toolset names and API version
- FoundryToolboxBearerTokenHandler.cs: DelegatingHandler with Azure Bearer token
auth, Foundry-Features header injection, and 3x exponential backoff on 429/5xx
- McpConsentContext.cs: AsyncLocal-based per-request consent state shared between
the tool wrapper and the response handler
- ConsentAwareMcpClientTool.cs: AIFunction wrapper that catches -32006 errors and
signals consent via shared state and linked CancellationTokenSource
- FoundryToolboxService.cs: IHostedService that creates McpClient per toolset at
startup and exposes cached tools
Modified files:
- AgentFrameworkResponseHandler.cs: injects toolbox tools into ChatOptions, sets
up linked CTS consent interception, emits mcp_approval_request on -32006
- ServiceCollectionExtensions.cs: adds AddFoundryToolboxes(params string[]) extension
- Microsoft.Agents.AI.Foundry.csproj: adds ModelContextProtocol and Azure.Identity
dependencies under NETCoreApp condition
Sample:
- Hosted-Toolbox: minimal hosted agent sample using AddFoundryToolboxes
* Rename toolset to toolbox in user-facing API; rename ConsentAwareMcpClientTool to ConsentAwareMcpClientAIFunction
* Add HostedMcpToolboxAITool for client-selectable Foundry toolboxes
Introduces HostedMcpToolboxAITool, a marker tool subclassing HostedMcpServerTool that rides the OpenAI Responses 'mcp' wire format to let clients request a specific Foundry toolbox per request.
- New FoundryAITool.CreateHostedMcpToolbox(name, version?) factory.
- FoundryToolboxOptions.StrictMode (default true) rejects unregistered toolboxes; set to false to allow lazy-open on first use.
- FoundryToolboxService.GetToolboxToolsAsync(name, version?) resolves cached or lazy-opened MCP tools.
- AgentFrameworkResponseHandler parses request.Tools for foundry-toolbox://name[?version=v] markers and injects resolved tools per request, merging with pre-registered ones.
- Unit tests for marker parsing and strict-mode resolution.
* Bump Azure.AI.Projects to 2.1.0-alpha; add ToolboxRecord/ToolboxVersion factory overloads + tests
* Fix PR review issues: retry off-by-one, URI encoding, docs, tests, build
- Fix off-by-one in FoundryToolboxBearerTokenHandler retry loop (4 attempts → 3)
- URI-encode version parameter in HostedMcpToolboxAITool.BuildAddress
- Add XML doc clarifying version pinning is reserved for future use
- Add comment clarifying AddHostedService deduplication safety
- Fix DevTemporaryTokenCredential expiry to use DateTimeOffset.MaxValue
- Fix AgentCard ambiguity in A2AServer sample with using alias
- Add 18 new unit tests for retry handler and ReadMcpToolboxMarkers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Hosted agent adapter (#5371)
* Bump preview version to 260420.1 and fix AgentServer package deps
- Bump PackageVersion to 0.0.1-preview.260420.1
- Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by
Azure.AI.AgentServer.Responses beta.3)
- Replace AgentHostTelemetry.ResponsesSourceName with local constant
(type made internal in AgentServer.Core beta.22)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService
Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy
the CA1873 analyzer rule which flags potentially expensive argument
evaluation when logging is disabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Hosted agent adapter (#5374)
* Bump preview version to 260420.1 and fix AgentServer package deps
- Bump PackageVersion to 0.0.1-preview.260420.1
- Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by
Azure.AI.AgentServer.Responses beta.3)
- Replace AgentHostTelemetry.ResponsesSourceName with local constant
(type made internal in AgentServer.Core beta.22)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService
Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy
the CA1873 analyzer rule which flags potentially expensive argument
evaluation when logging is disabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bumping NuGet version
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Hosted agent adapter (#5406)
* Bump preview version to 260420.1 and fix AgentServer package deps
- Bump PackageVersion to 0.0.1-preview.260420.1
- Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by
Azure.AI.AgentServer.Responses beta.3)
- Replace AgentHostTelemetry.ResponsesSourceName with local constant
(type made internal in AgentServer.Core beta.22)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService
Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy
the CA1873 analyzer rule which flags potentially expensive argument
evaluation when logging is disabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bumping NuGet version
* Restore conditional versioning, remove dev feed, bump Azure.AI.Projects to beta.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Hosted agent adapter (#5408)
* Bump preview version to 260420.1 and fix AgentServer package deps
- Bump PackageVersion to 0.0.1-preview.260420.1
- Bump Azure.AI.AgentServer.Core beta.21 -> beta.22 (required by
Azure.AI.AgentServer.Responses beta.3)
- Replace AgentHostTelemetry.ResponsesSourceName with local constant
(type made internal in AgentServer.Core beta.22)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CA1873: guard LogError with IsEnabled check in FoundryToolboxService
Wrap the LogError call with an IsEnabled(LogLevel.Error) guard to satisfy
the CA1873 analyzer rule which flags potentially expensive argument
evaluation when logging is disabled.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bumping NuGet version
* Restore conditional versioning, remove dev feed, bump Azure.AI.Projects to beta.1
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #5312 review comments
- Add comment explaining NU1903 suppression (Microsoft.Bcl.Memory transitive vuln)
- Remove NU1903 from sample/test projects where not needed
- Fix Dockerfile ENTRYPOINT mismatch in Hosted-Workflow-Simple
- Align agent name to 'hosted-workflow-simple' in agent.yaml and README
- Fix Hosted-McpTools README: replace GitHub PAT refs with Microsoft Learn
- Fix session persistence: only persist when client provides conversation ID
- Upgrade IsNullOrEmpty to IsNullOrWhiteSpace for session ID checks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Split Foundry into stable V1 and preview Hosting package
Extract hosted agent functionality from Microsoft.Agents.AI.Foundry into a
new Microsoft.Agents.AI.Foundry.Hosting preview package. This resolves NU5104
build errors caused by the stable Foundry package depending on prerelease
Azure SDK packages (Azure.AI.AgentServer.Responses, Azure.AI.Projects beta).
Changes:
- Create Microsoft.Agents.AI.Foundry.Hosting with VersionSuffix=preview,
targeting .NET Core only (net8.0/9.0/10.0)
- Move all Hosting/ source files to the new project
- Move ToolboxRecord/ToolboxVersion overloads to FoundryAIToolExtensions
- Revert Azure.AI.Projects to 2.0.0 in Directory.Packages.props;
Hosting uses VersionOverride for 2.1.0-beta.1
- Clean V1 Foundry csproj: remove beta deps, ASP.NET Core ref, hosting conditionals
- Update 8 hosted agent sample projects to reference Foundry.Hosting
- Split unit tests: ToolboxRecord/ToolboxVersion tests moved to Hosting/
- Add Foundry.Hosting to solution file
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR review comments: experimental attrs, doc fixes, token propagation
- Add [Experimental(OPENAI001)] to all 7 public Hosting types per reviewer request
- Fix McpConsentContext XML doc: 'Thread-static' -> 'Async-local' (AsyncLocal
flows with ExecutionContext, not thread-static)
- Expand UserAgentMiddleware test regex to match prerelease versions (e.g. 1.0.0-rc.4)
- Propagate CancellationToken in AgentFrameworkResponseHandler session save
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove unnecessary MEAI001 suppression from stable Foundry package
MEAI001 was a leftover from when Hosting code lived in the same project.
The stable V1 Foundry package builds clean without it, and suppressing
experimental diagnostics in a released package can hide unintentional
exposure of experimental APIs to consumers.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Foundry.Hosting to release solution filter
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
* Fix OpenAIEmbeddingClient with /openai/v1 endpoint (#5068)
When base_url ends with /openai/v1/ and a credential is provided,
load_openai_service_settings was creating an AsyncAzureOpenAI client.
The Azure SDK rewrites deployment-based endpoints (including /embeddings)
by inserting /deployments/{model}/ into the URL, producing 404s on the
OpenAI-compatible /openai/v1 endpoint.
Use AsyncOpenAI instead of AsyncAzureOpenAI when the resolved base_url
targets /openai/v1, converting the Azure token provider to an async
api_key callable. The responses_mode path is unaffected because the
Responses API (/responses) is not in the SDK's rewrite list.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix OpenAIEmbeddingClient to use AsyncOpenAI for /openai/v1 endpoints
Fixes#5068
* Address review feedback: improve test coverage and remove unrelated changes
- Revert unrelated formatting change in test_a2a_agent.py
- Fix test_init_with_openai_v1_base_url_and_api_key_uses_openai_client to
exercise the Azure settings path (via AZURE_OPENAI_BASE_URL env var)
instead of the plain OpenAI path, covering the elif api_key branch
- Add _ensure_async_token_provider unit tests for both sync and async
token providers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5068: Python: [Bug]: `OpenAIEmbeddingClient` does not work with `/openai/v1` endpoint
---------
Co-authored-by: MAF Dashboard Bot <maf-dashboard-bot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* feat(evals): add ground_truth support for similarity evaluator
- Include expected_output as ground_truth in Foundry JSONL dataset rows
- Add ground_truth to item schema and data mapping for similarity evaluator
- Add expected_output parameter to evaluate_workflow
- Add similarity Pattern 3 to evaluate_agent and evaluate_workflow samples
- Add tests for ground_truth in dataset, schema, and evaluate_workflow
* Apply suggestions from code review
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* fix: wrap long line to satisfy ruff E501
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* refactor: remove dead code
* refactor: remove ignore YieldsMessageAttribute
- the correct one to use is YieldsOutputAttribute
- fixes a comment that mistakenly refers to `.YieldsMessage()` which does not exist.
* fix: ChatForwardingExecutor does not use correct role for string messages
- make ChatForwardingExecutor use its configured role for string messages rather than always use ChatRole.User
- add ChatForwardingExecutor tests
* fixup: remove unused attribute
* test: Add tests for failure when .AsAgent used on a non-ChatProtocol workflow
* test: Add FunctionExecutor tests
- also fixes Send and YieldOutput type registration for synchronous output-returning delegates
* test: Suppress CodeCoverage for obsolete names
* fix: Re-add Obsolete attributes
- avoid hard-breaking change
- properly notify users that these attributes get ignored
Some providers, e.g. Gemini, do not use the CallId mechanism to disambiguate simultaneous function calls. This can result in message lists containing multiple turn to fail to filter properly.
The fix is to take advantage of the expectation that Handoff Orchestration is a "single-speaker" flow, which only has a single active AIAgent per "turn" and an agent's turn is not finished until all outstanding function calls are finished.
This allows us to expect that any ambiguous-CallId FunctionCallContent are either in separate turns or will have had a response before the next issued call with the same Id.
* Add set_stop_loss tool to concurrent_builder_tool_approval sample
Add a second approval-gated tool (set_stop_loss) to the concurrent workflow
tool approval sample to demonstrate handling approval requests for different
tools in the same concurrent workflow.
Changes:
- Add set_stop_loss(symbol, stop_price) with approval_mode='always_require'
- Include new tool in both agents' tool lists
- Update agent instructions and prompt to encourage stop-loss usage
- Update docstring to reflect two approval-gated tools
- Update sample output to show mixed approval requests
Fixes#4874
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Print tool name and arguments in concurrent sample's process_event_stream (#4874)
Align process_event_stream in concurrent_builder_tool_approval.py to print
the tool name and arguments when collecting approval requests, matching the
sample output comment and the sequential_builder_tool_approval.py pattern.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add None-guard for function_call access in tool approval sample (#4874)
Add explicit None-checks before accessing function_call.name and
function_call.arguments in concurrent_builder_tool_approval.py. The
function_call field is typed Content | None, so direct attribute access
without a guard could raise AttributeError and required type: ignore
comments. The None-guard is consistent with the pattern used in
_agent_run.py and removes the suppression comments.
Also add a regression test verifying that function_call defaults to None
and that the None-guard pattern is safe.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Apply same function_call None-guard to sibling tool-approval samples (#4874)
Apply the same fix to sequential_builder_tool_approval.py and
group_chat_builder_tool_approval.py, which had the identical pattern
of accessing function_call.name/arguments without a None-guard.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Wrapper + Samples 1st (#5177)
* Experiment
* Update dependency and add non streaming
* Add more samples
* Rename samples
* Add invocations
* Comments 1
* Comments 2
* Comments 3
* Improve README
* Add local shell sample
* WIP: Add eval and memory samples
* Update user agent prefix
* Update user agent prefix doc
* Update dependency (#5215)
* Add tests and more content types (#5235)
* Add tests
* fix tests and sample
* Fix formatting
* Remove function approval contents
* Python: Refine samples and upgrade packages (#5261)
* Refine samples and upgrade pacakges
* Upgrade to a new package that fixes a bug
* Update model env var
* Move samples (#5281)
* Python: Upgrade agentserver packages (#5284)
* Upgrade agentserver packages
* Fix new types
* Python: Add special handling for workflows (#5298)
* Add special handling for workflows
* Address comments
* Improve samples (#5372)
* Python: Add more types (#5378)
* Add more type supports
* Upgrade packages
* Remove TODOs in README
* Fix README
* Comments and mypy
* User agent scoped
* Fix README
* Fix pre commit
* Fix pre commit 2
* Fix pre commit 3
* Fix pre commit 4
* Fix pre commit 5
* Fix pre commit 6
* Add azure-monitor-opentelemetry to dev deps
Fixes Samples & Markdown CI failure. The PR's new transitive dep on
azure-monitor-opentelemetry-exporter (via azure-ai-agentserver-core) makes
pyright resolve the azure.monitor.opentelemetry namespace, flipping the
check_md_code_blocks diagnostic for `configure_azure_monitor` from
reportMissingImports (filtered) to reportAttributeAccessIssue (not filtered).
Installing the umbrella azure-monitor-opentelemetry package in dev makes
pyright resolve the symbol correctly, matching the install guidance the
observability README already gives users.
---------
Co-authored-by: Evan Mattson <evan.mattson@microsoft.com>
* Expose forwarded_props to agents and tools via session metadata (#5239)
Include forwarded_props from AG-UI request input_data in session.metadata
(agent runner) and function_invocation_kwargs (workflow runner) so that
agents, tools, and workflow executors can access request-level metadata
such as invocation source flags from CopilotKit.
- Add forwarded_props to base_metadata in _agent_run.py when present
- Add 'forwarded_props' to AG_UI_INTERNAL_METADATA_KEYS to filter it
from LLM-bound client metadata
- Extract forwarded_props in _workflow_run.py and pass via
function_invocation_kwargs to workflow.run()
- Accept both snake_case and camelCase keys (forwarded_props/forwardedProps)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(ag-ui): pass stream=True as literal to satisfy pyright overload resolution (#5239)
The previous fix passed stream=True via **kwargs dict, which prevented
pyright from resolving the Workflow.run() overload to the streaming
variant. Pass stream=True as an explicit keyword argument so pyright
can correctly infer the ResponseStream return type.
Also remove unused pytest import in test file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: address PR review feedback for forwarded_props (#5239)
- Use key-presence checks instead of truthiness for forwarded_props so
empty dict {} is forwarded correctly
- Gate function_invocation_kwargs on workflow.run() signature inspection
to avoid TypeError for workflows without **kwargs
- Change _build_safe_metadata to drop (with warning) keys whose
serialized values exceed 512 chars instead of truncating into invalid
JSON
- Rewrite metadata tests to exercise _build_safe_metadata directly with
JSON-decodability and truncation assertions
- Add workflow tests for empty dict forwarded_props, stream=True
assertion, and signature-gated kwarg dropping
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* test: add stream=True assertions to CapturingWorkflow tests (#5239)
Guard against accidental removal of the explicit stream=True kwarg
in all forwarded_props CapturingWorkflow test cases.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5239: Python: Expose forwardedProps to agents and tools via session metadata
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add support for the Foundry Toolbox in MAF
Introduces a Foundry Toolbox integration: FoundryChatClient gains a
get_toolbox() helper plus select_toolbox_tools(), normalize_tools in
the core package flattens tool-collection wrappers (ToolboxVersionObject
and generic iterables, while leaving Pydantic BaseModel instances
alone), and the new agent_framework.foundry namespace re-exports the
toolbox helpers. Ships with unit tests, a sample, and a design doc.
azure-ai-projects is pinned to the public >=2.0.0,<3.0 range and the
lockfile resolves from public PyPI. The toolbox test module skips when
Toolbox* types are unavailable so CI stays green until the public 2.1.0
SDK lands. OMC tooling directories (.omc/, .omx/) are gitignored.
* Update to latest azure ai projects package
* Improve sample
* Rename ADR to 0025
* Update ADR
* Apply suggestion from @alliscode
Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
* Improve samples
* Update test
---------
Co-authored-by: Ben Thomas <ben.thomas@microsoft.com>
* adds devui integration and samples
* adds unit tests for devui integration
* fix: correct formatting of copyright notice in unit test files
* fixes formatting issues
* fixes build for net8 target
* fixes formatting errors on test apphost
* adds copyright notice to multiple files and removes unnecessary using directives
* Update dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/tests/Aspire.Hosting.AgentFramework.DevUI.UnitTests/Aspire.Hosting.AgentFramework.DevUI.UnitTests.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/samples/DevUIIntegration/DevUIIntegration.AppHost/DevUIIntegration.AppHost.csproj
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update dotnet/aspire-integration/Aspire.Hosting.AgentFramework.DevUI/DevUIAggregatorHostedService.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Refactor project files to use TargetFrameworks instead of TargetFramework for multi-targeting support; add optional port property to DevUIResource class.
* Add unit tests for DevUIAggregatorHostedService; refactor project files for TargetFrameworks support
* Refactor project files to use TargetFrameworks for multi-targeting support in DevUIIntegration samples
* Remove unnecessary using directive for Aspire.Hosting in DevUIAggregatorHostedServiceTests
* merge
* fixes Conversation routing for non-first backends
* add documentation for devui integration sample
* update project references in solution file for improved integration
* fixes package versions post merge
* move Aspire.Hosting.AgentFramework.DevUI to dotnet/src
Move the project from aspire-integration/ to src/ to be consistent
with the location of all other projects in the repo.
* move DevUI sample to samples/05-end-to-end/DevUIAspireIntegration
Move the sample from samples/DevUIIntegration/ to
samples/05-end-to-end/DevUIAspireIntegration/ to match the location
of other end-to-end samples.
* remove unnecessary net472 framework condition from sample csproj files
These projects only target net10.0, so the
Condition="'$(TargetFramework)' != 'net472'" on ItemGroup is unnecessary.
* update sample model name from gpt-4.1 to gpt-5.4
Use a more up-to-date model name in the DevUI integration samples.
* Revert "remove unnecessary net472 framework condition from sample csproj files"
This reverts commit 08cf41253b.
* fix: use TargetFrameworks to override multi-targeting from Directory.Build.props
The parent Directory.Build.props sets TargetFrameworks to net10.0;net472,
which overrides the singular TargetFramework in each csproj. Use the plural
TargetFrameworks property set to net10.0 only to properly override it, and
remove the now-unnecessary net472 condition on ItemGroup.
* fixes aspire config
* fix: update Microsoft.Extensions packages to version 10.0.1
* Address Copilot review feedback on DevUI Aspire integration
- Fix request body dropping in ProxyConversationsAsync: always read the
body when ContentLength > 0 before routing, then pass it through to
all proxy calls (previously null was passed when backend was resolved
from query param or conversation map)
- Fix resource leak: dispose aggregator on startup failure in catch block
- Fix XML docs: accurately describe embedded resource serving behavior
- Remove reflection from DevUIResourceTests (InternalsVisibleTo already set)
- Make sensitive telemetry conditional on Development environment in samples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: update chat client version to gpt41 in both EditorAgent and WriterAgent
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Roger Barreto <19890735+rogerbarreto@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix CopilotStudioAgent to reuse existing conversation on session (#5285)
CopilotStudioAgent unconditionally called _start_new_conversation() in both
_run_impl and _run_stream_impl, ignoring any existing service_session_id on
the session. Add a guard to only start a new conversation when there is no
existing service_session_id, matching the pattern used by other agents.
Also fix pre-existing pyright reportMissingImports errors for orjson in
file_history_provider samples.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Revert out-of-scope sample file changes
Remove unrelated orjson type-ignore comment changes from sample files
that were outside the scope of the conversation-ID reuse fix.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Add session support for Handoff-hosted Agents
In order to better support using `Workflows` hosted as `AIAgents` inside of Handoff workflows, we need to make proper use of AgentSession. This causes potential issues around checkpointing and making sure that we properly compute only the new incoming messages for each agent invocation.
* fix: AgentSession checkpointing using AIAgent's Serialize/Deserialize methods
We cannot rely on implicit serialization through `HandoffHostState` because we are missing type information.
* fix: Thread safety issue in `MultiPartyConversation.AllMessages`
* fix: Enable unwrapping of FunctionResultContent when ExternalRequest was wrapped into FunctionCallContent
* fix: Foundry Agents without description in Handoff
Foundry Agents without a description set will return an empty string (rather than null) for the description. This was breaking the fallback logic for `handoffReason`.
* test: Add unit tests
* Foundry Evals integration for .NET
- Core evaluation framework: EvalItem, LocalEvaluator, FunctionEvaluator, EvalChecks
- IAgentEvaluator interface with MeaiEvaluatorAdapter bridge
- AgentEvaluationExtensions for agent.EvaluateAsync() overloads
- FoundryEvals wrapping MEAI quality/safety evaluators
- ConversationSplitters (LastTurn, Full) and IConversationSplitter
- EvalItem.PerTurnItems() for multi-turn decomposition
- HasImageContent for multimodal content detection
- WorkflowEvaluationExtensions for per-agent workflow evaluation
- 7 eval samples mirroring Python parity:
02-agents/Evaluation: SimpleEval, ExpectedOutputs, Multimodal
03-workflows/Evaluation: WorkflowEval
05-end-to-end/Evaluation: FoundryQuality, MixedProviders, ConversationSplits
- Comprehensive unit tests (1958 passing)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rewrite FoundryEvals to use real Foundry Evals API
Replace MEAI evaluator shim with actual OpenAI EvaluationClient protocol
methods. FoundryEvals now creates eval definitions, submits runs, polls
for completion, and fetches per-item results server-side.
- New constructor: FoundryEvals(AIProjectClient, model, evaluators)
- Add FoundryEvalConverter for MEAI ChatMessage -> Foundry JSON format
- Add EvalId, RunId, ReportUrl to AgentEvaluationResults
- All 20 built-in evaluator constants now work (agent, tool, quality, safety)
- Remove Microsoft.Extensions.AI.Evaluation.Quality/Safety dependencies
- Update all samples for new constructor (no more ChatConfiguration)
- Replace BuildEvaluators tests with ResolveEvaluator tests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add response output to CustomEvals and ExpectedOutputs samples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review: pagination, validation, error handling, tests
FoundryEvals fixes:
- Add pagination for output items (has_more/after cursor)
- Add guard clauses for pollIntervalSeconds/timeoutSeconds <= 0
- Fix double TryGetProperty for passed field parsing
- Throw on all-tool-evaluators with no tool definitions
- Fix XML doc (default 300s, not 180s)
New tests (30 added, 1989 total):
- EvalChecks: NonEmpty, ContainsExpected (pass/fail/skip/case),
HasImageContent, ToolCallsPresent
- FoundryEvalConverter: ConvertMessage (text, image, function call,
function results fan-out, empty fallback, mixed content),
ConvertEvalItem, BuildTestingCriteria (quality/agent/tool/groundedness
data mappings), BuildItemSchema
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix review: null-refs, Data.ToString() bug, ContainsExpected, add tests
- Fix NullReferenceException in sample Response display (pattern matching)
- Fix WorkflowEvaluationExtensions Data?.ToString() producing type names
instead of message text (pattern-match ChatMessage/AgentResponse/list)
- Change EvalChecks.ContainsExpected to return Passed=false when no
ExpectedOutput (was silently passing, masking misconfiguration)
- Add EvalItem constructor tests with LastTurn/Full/null splitters
- Add FoundryEvalConverter.ConvertMessage DataContent (base64 image) test
- Add ExtractAgentData tests with ChatMessage, list, and AgentResponse data
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix review: conversation fidelity, eval caching, fallback tests
- WorkflowEvaluationExtensions: preserve full response messages (tool calls,
intermediate) instead of synthetic 2-message conversation. Cast completed
Data to AgentResponse and use Messages when available, fallback to text.
- FoundryEvals: cache evalId per schema shape (hasContext, hasTools) so
subsequent EvaluateAsync calls create runs under the same eval definition.
- MeaiEvaluatorAdapter: code already correctly passes queryMessages (not full
conversation) to IEvaluator — no change needed, verified by inspection.
- Add tests: AgentResponse full messages preservation, unknown object
ToString() fallback for ExtractAgentData.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Rename AzureAI→Foundry: move eval files, update references
- Move FoundryEvals.cs and FoundryEvalConverter.cs from
Microsoft.Agents.AI.AzureAI to Microsoft.Agents.AI.Foundry
- Update namespace from AzureAI to Foundry in both files
- Add explicit usings required by Foundry project (no implicit usings)
- Move FoundryEvalConverter tests to Foundry.UnitTests project
(avoids ReplacingRedactor type conflict from dual project refs)
- Update all sample csproj references and using statements
- Remove Foundry project reference from AI UnitTests
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* PR review round 4: wire up tool extraction, remove eval cache, fix null safety
- BuildEvalItem: extract tools from agent via GetService<ChatOptions>() into EvalItem.Tools (Python parity)
- FoundryEvals: remove eval ID cache - each call creates fresh definition (matches Python behavior)
- FoundryEvals: replace null-forgiving operators with descriptive InvalidOperationException
- MixedProviders sample: remove unnecessary explicit PackageReferences (transitively provided)
- FoundryEvalConverter: document that tool results take precedence over text content
- Add LocalEvaluator zero-checks test documenting 0 metrics = failed behavior
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python-dotnet parity: 9 feature gaps filled
New checks:
- ToolCallArgsMatch() — verify tool call names + argument subset match
- ToolCalledCheck(ToolCalledMode.Any, ...) — match any of the specified tools
- ToolCalledMode enum (All/Any)
FoundryEvals enhancements:
- Default evaluators now [Relevance, Coherence, TaskAdherence] (was Relevance, Coherence)
- Auto-add ToolCallAccuracy when items have tool definitions
- EvaluateTracesAsync — evaluate by response_ids, trace_ids, or agent_id
- EvaluateFoundryTargetAsync — evaluate deployed Foundry targets
Result type enrichment:
- AgentEvaluationResults: added Status, Error, PerEvaluator, DetailedItems
- New EvalItemResult/EvalScoreResult/PerEvaluatorResult types
- FoundryEvals populates all new fields from API responses
Workflow fix:
- Skip internal executors (_*, input-conversation, end-conversation, end)
Tests: 8 new tests covering ToolCallArgsMatch, ToolCalledMode.Any, internal executor filtering
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add MeaiEvaluatorAdapter and PerTurnItems edge case tests
- 3 tests for MeaiEvaluatorAdapter: query message forwarding, synthetic
response fallback, multiple items aggregation
- 3 tests for EvalItem.PerTurnItems: empty conversation, no user messages,
system+assistant only
- StubEvaluator and StubChatClient test helpers
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Blocking link check for outdated package in DevUI.
* Replace Dictionary<string, object> payloads with typed wire models
Introduce internal FoundryEvalWireModels.cs with compile-time-safe types
for the OpenAI Evals API wire format. The OpenAI .NET SDK (2.9.1) only
provides protocol-level methods with BinaryContent/ClientResult — no
typed request models. These internal models replace scattered dictionary
literals with [JsonPropertyName]-annotated classes, giving:
- Compile-time safety (typos become build errors)
- Single point of change when the API evolves
- IntelliSense discoverability
- Cleaner serialization via JsonPolymorphic for content items
Models: WireContentItem hierarchy (text, image, tool_call, tool_result),
WireMessage, WireEvalItemPayload, WireTestingCriterion, WireItemSchema,
WireCreateEvalRequest, WireCreateRunRequest, and data source variants.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Skip metric when Foundry returns neither score nor passed
When an evaluator returns no score and no passed value, the previous
code created BooleanMetric(name, false), which falsely failed items
via ItemPassed. Now we skip the MEAI metric entirely for indeterminate
results — the raw data remains available in DetailedItems for diagnostics.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #4914 review comments: fix tool evaluator bug and add tests
- Fix duplicate ToolCallAccuracy: resolve evaluator names before checking
against ToolEvaluators set (Comment 2)
- Make FilterToolEvaluators internal for testability; add tests for the
ArgumentException edge case when all evaluators are tool-type (Comment 3)
- Add CancellationToken test for LocalEvaluator (Comment 4)
- Add EvaluateAsync integration test on Run with sequential workflow and
per-agent SubResults verification (Comment 5)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address Peter's review comments on PR #4914
- Add trailing newline to Evaluation_FoundryQuality.csproj (Comment 6)
- Make evaluator name lookups case-insensitive: switch BuiltinEvaluators,
ToolEvaluators, AgentEvaluators, and ResolveEvaluator's StartsWith check
from Ordinal to OrdinalIgnoreCase (Comment 7)
- Add Trace.TraceWarning when Foundry returns fewer results than submitted
items, indicating expected vs actual count before padding (Comment 8)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add Microsoft.Extensions.AI.Evaluation packages to Directory.Packages.props
These were removed in #5269 as unused, but are needed by the Foundry
and core evaluation integration added in this PR.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: alliscode <bentho@microsoft.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: add finish_reason support to AgentResponse and AgentResponseUpdate
Add finish_reason field to AgentResponse and AgentResponseUpdate classes,
propagate it through _process_update() and map_chat_to_agent_update(),
and add comprehensive unit tests.
Fixes#4622
* feat: add finish_reason to AgentResponse and AgentResponseUpdate
* style: add copyright header to test_finish_reason.py
* docs: add finish_reason to AgentResponse and AgentResponseUpdate docstrings
* refactor: move finish_reason tests into test_types.py per review feedback
Move all finish_reason test cases from the separate test_finish_reason.py
file into test_types.py as requested by eavanvalkenburg. Tests are placed
in a new '# region finish_reason' section at the end of the file.
* fix: use model instead of model_id in _process_update
Address PR review feedback from @eavanvalkenburg — ChatResponse and
ChatResponseUpdate both use 'model', not 'model_id'.
* fix: resolve SIM102 lint error in _process_update
Combine nested if statements for AgentResponse finish_reason check
to satisfy ruff SIM102 rule, with line wrapping to stay under 120 chars.
* fix: resolve pyright reportArgumentType in map_chat_to_agent_update
Add type: ignore[arg-type] for FinishReason NewType widening when
passing ChatResponseUpdate.finish_reason to AgentResponseUpdate.
Matches existing patterns in the codebase (40+ similar ignores).
* Fix url_citation annotations dropped in streaming (#5029)
Add url_citation branch to the streaming annotation handler in
_parse_chunk_from_openai, mirroring the existing non-streaming path.
The handler creates an Annotation with type='citation', title, url,
and annotated_regions (TextSpanRegion), wrapped in Content.from_text.
Update test_streaming_annotation_added_with_unknown_type to use a
truly unknown type, and add new tests for url_citation (with and
without url).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5029: Python: [Bug]: url_citation annotations silently dropped in Foundry streaming (SharePoint grounding citations lost)
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
- Update Anthropic from 12.11.0 to 12.13.0
- Update Anthropic.Foundry from 0.4.2 to 0.5.0
- Change Anthropic project from release candidate to preview
- Add new IBetaService members (Agents, Environments, Sessions, Vaults) to test mock
Fixes#5246
When a custom @executor transforms agent output and sends a plain str,
the downstream AgentExecutor.from_str handler loses the full conversation
context. This adds a with_text() helper that creates a new
AgentExecutorResponse with replaced text while preserving the prior
conversation chain, so AgentExecutor.from_response is invoked instead.
- Add with_text(text) method to AgentExecutorResponse dataclass
- Add 3 regression tests in test_full_conversation.py
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* Improve workflow unit tests
* Update test name prefix for clarity.
* Update tests to surface any errors.
* fix check-point restore-time race in off-thread workflow event stream
* Fixes an intermittent checkpoint-restore race in in-process workflow runs.
The local MCP server can't be used for hosted tools tests because
Anthropic's backend needs to reach the MCP URL from their infrastructure
(not localhost on the CI runner). Revert to learn.microsoft.com/api/mcp
but catch BadRequestError, InternalServerError, APIConnectionError, and
APITimeoutError and pytest.skip so upstream outages don't block the
merge queue.
* Python: use local MCP server for hosted tools test and broaden image assertion
The hosted tools integration test was hitting rate limits on the external
learn.microsoft.com MCP server, causing persistent failures that retries
couldn't recover from. Switch to the local MCP server already spun up in
CI via LOCAL_MCP_URL, skipping when the env var isn't set.
Also broaden the image description assertion to accept common synonyms
(cottage, mansion, villa, etc.) instead of just "house", since the model
legitimately uses varied vocabulary for the same image.
* Address review feedback: validate LOCAL_MCP_URL scheme and use word boundaries
- Skip hosted tools test when LOCAL_MCP_URL lacks http/https scheme,
matching the pattern used in test_mcp.py.
- Use regex word boundaries for image assertion to avoid false matches
like "villain" matching "villa".
The misc-integration job (Anthropic, Ollama, MCP) frequently fails on merge to main when the upstream MCP server (e.g. learn.microsoft.com/api/mcp) returns a transient rate-limit error. The previous 5s retry delay is too short to ride out the upstream backoff window, so all retries fail and the merge queue is blocked. Bumping to 30s gives the upstream a chance to recover before pytest-retry re-runs the test.
* Add agent-framework-gemini package
* Add AGENTS.md documentation
* Add LICENSE file
* Add README.md for agent-framework-gemini package
* Add Google Gemini API keys to .env.example
* Add Google Gemini chat client implementation
* Add tests for GeminiChatClient
* Add Google Gemini agent examples
* Fix client inheritence order
* Update Gemini agent examples
* Update documentation
* Update AGENTS.md
* Add tests for JSON string handling in GeminiChatClient
* Add final response assembly test in GeminiChatClient
* Add tests for handling empty candidates in GeminiChatClient
* Improve Pydantic response handling in GeminiChatClient
* Add tests for function result resolution and callable tool normalization
* Add test for function result resolution when call_id is generated
* Refactor GeminiChatClient to correct inheritance order
Also updates constructor parameter order for environment file handling
* Enhance documentation and clarify Gemini-specific fields
* Update ThinkingConfig with new attributes and type
* Add tests for GoogleSearch and GoogleMaps configs
* Suppress valid-type mypy error on GeminiChatOptionsT
* Move service_url method near overrides
* Order _prepare_config kwargs by base then Gemini-specific
* Use FunctionCallingConfigMode for clarity and type safety
* Fix code_execution doc
* Add agent-framework-gemini to project dependencies
* Remove package from core dependencies
Initial release will be done without agent-framework-gemini in
core[all].
* Move integration tests into one file
* Remove __init__.py file from gemini tests directory
* Introduce RawGeminiChatClient as lightweight chat client
Updated GeminiChatClient to inherit from RawGeminiChatClient, maintaining full functionality with added features.
* Updated variable names from `model_id` to `model`
Across the codebase, including environment variables and client initialization. Adjusted related tests and sample scripts to reflect this change, ensuring consistency in the usage of the Gemini model identifier.
* Update AGENTS.md
* Update Gemini package to alpha status
* Fix docstrings in Gemini tests
* Change 'model_id' to 'model' in response handling
* Fix model property change in response handling
* Add built-in tool factory methods to Gemini client
Replaces boolean tool options (code_execution, google_search_grounding,
google_maps_grounding) with static factory methods that return types.Tool
objects: get_code_interpreter_tool, get_web_search_tool, get_mcp_tool,
get_file_search_tool, and get_maps_grounding_tool.
Simplifies _prepare_tools to a single translation boundary between
FunctionTool (framework) and FunctionDeclaration (Gemini API), with
types.Tool objects passed through unchanged.
* Surface code execution parts
_parse_parts now maps executable_code and code_execution_result
parts to text Content objects so callers can see the code run
and its output. Unknown part types log at debug level rather than
being silently dropped.
* Update Gemini client documentation
* Unify Gemini model name
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Update Agent Framework core version
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Add Python 3.14 in classifiers
* Replace kwargs with parameters in tool factories
* Refactor chat options handling in Gemini client
* Add tests for handling unknown and consumed keys
* Update Gemini documentation
Now reflects new options and built-in tool factory methods
* Change build system to flit
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Fix build system in pyproject.toml
* Fix type checking for generate_content_stream
---------
Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com>
* Python: Skip get_final_response in OTel _finalize_stream when stream errored
When a streaming error occurs, _finalize_stream (a cleanup hook registered by
AgentTelemetryLayer) was unconditionally calling get_final_response(), which
triggers all registered result hooks including after_run context providers.
This caused providers to fire incorrectly on error paths.
Guard against this by checking result_stream._consumed: True only after
StopAsyncIteration (normal completion), False when an exception was raised.
The fix applies to both the chat client and agent telemetry layers.
Closes#5231
* Python: Expose consumed/stream_error on ResponseStream and capture error in OTel span
Address Copilot review feedback on #5232:
- Add `_stream_error: Exception | None` to ResponseStream, set in __anext__'s
except branch so cleanup hooks can inspect the failure.
- Expose public `consumed` and `stream_error` properties to avoid coupling
observability.py to private stream internals.
- Update both _finalize_stream closures (chat and agent layers) to use the
public properties and call capture_exception() with the stream error before
returning early, ensuring the OTel span records the failure rather than
closing silently.
* Python: Address Copilot review feedback on stream error handling
- Use stream_error is not None as the guard in _finalize_stream instead of
not consumed, so the early-return path is keyed precisely to actual errors
rather than any non-normal completion state.
- Clear _stream_error after _run_cleanup_hooks() completes to avoid retaining
the exception traceback (and any large object graphs it references) on the
stream instance beyond the cleanup phase.
* Python: Remove consumed/stream_error properties, use private attrs directly
Per review feedback: since observability.py and _types.py are in the same
package, accessing _stream_error directly is fine and the public properties
are unnecessary.
* Python: Fix Pyright reportPrivateUsage via inline ignore comments
Keep _stream_error private (consistent with rest of ResponseStream), and
suppress reportPrivateUsage at the call sites in observability.py with
inline pyright: ignore comments — access is intentional within the package.
* AG-UI deterministic state updates from tool results
* fix(ag-ui): address PR #5201 review comments
1. Add missing AGUIEventConverter, AGUIHttpService, __version__ to
_IMPORTS in core ag_ui lazy-export list to match the .pyi stub.
2. Coalesce predictive and deterministic state snapshots into a single
StateSnapshotEvent when both mechanisms are active on the same tool
result, reducing redundant snapshot traffic.
3. Update state_update() docstring to clarify that a predictive snapshot
may be emitted before the deterministic one when predict_state_config
is active.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix HandoffBuilder dropping function-level middleware when cloning agents (#5173)
_clone_chat_agent() was using agent.agent_middleware (agent-level only)
instead of agent.middleware (all types), which silently dropped any
function middleware registered on the original agent.
Changed to use agent.middleware to preserve all middleware types
(agent, function, and chat) during cloning.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix HandoffBuilder dropping function-level middleware when cloning agents
Fixes#5173
* Fix false-positive middleware regression test (#5173)
The test used isinstance(m, FunctionMiddleware) which matched
_AutoHandoffMiddleware (always appended during build) instead of the
user's @function_middleware decorator. Assert directly that
tracking_middleware is present in the cloned agent's middleware list.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5173: Python: [Bug]: HandoffBuilder drops function-level middleware when cloning agents
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add allowed_checkpoint_types support to CosmosCheckpointStorage (#5200)
Add allowed_checkpoint_types parameter to CosmosCheckpointStorage for
parity with FileCheckpointStorage. This ensures both providers use the
same restricted pickle deserialization by default.
Changes:
- Accept allowed_checkpoint_types kwarg in __init__, stored as frozenset
- Convert _document_to_checkpoint from @staticmethod to instance method
- Forward allowed_types to decode_checkpoint_value on all load paths
- Update class docstring to describe the new parameter
- Add tests covering built-in safe types, app type opt-in/blocking,
and all load paths (load, list_checkpoints, get_latest)
- Add changelog entry noting the breaking behavior change
BREAKING CHANGE: CosmosCheckpointStorage now uses restricted pickle
deserialization by default. Checkpoints containing application-defined
types will require passing those types via allowed_checkpoint_types.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Add `allowed_checkpoint_types` support to `CosmosCheckpointStorage` for parity with `FileCheckpointStorage`
Fixes#5200
* Address PR review: add pickle security warning and fix docstring examples
- Reintroduce explicit security warning about pickle deserialization risks
- Convert Example:: block to .. code-block:: python with imports for
consistency with other docstring examples
- Note: PR title should be updated to include [BREAKING] prefix per
changelog convention (comment #3, requires GitHub UI change)
Fixes#5200
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix python-feature-lifecycle skill YAML frontmatter
Remove copyright comment that preceded the YAML frontmatter delimiter,
which prevented the skill from loading. The --- block must be the very
first line of SKILL.md.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: update broken eslint-react plugin links in devui README
The upstream eslint-react repo moved plugins from packages/plugins/
to the top-level plugins/ directory, causing 404 errors detected by
linkspector CI.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat: Refactor Handoff Orchestration and add HITL support
* Change HandoffAgentExecutor to use factory-based instantiation
* Extract shared request collection logic in AIAgentUnservicedRequestsCollector
* Refactor HandoffAgentExecutor to use the "ContinueTurn" pattern as in AIAgentHostExecutor
* fix: Remove '$' from exception strings
Rename authored identifiers, XML docs, log messages, and comments
from 'folder' to 'directory' across the file skills codebase for
consistency with the agentskills.io specification and .NET conventions.
Public API changes (experimental):
- ScriptFolders → ScriptDirectories
- ResourceFolders → ResourceDirectories
.NET BCL API calls (Directory.Exists, Path.GetDirectoryName, etc.)
were already using 'directory' and are unchanged.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* support reflection for discovery of resources and scripts in class-based skills
* fix format issues
* refactor samples to use reflection
* Validate resource member signatures during discovery
Add discovery-time validation in AgentClassSkill.DiscoverResources() to
fail fast when [AgentSkillResource] is applied to members with incompatible
signatures:
- Reject indexer properties (getter has parameters)
- Reject methods with parameters other than IServiceProvider or
CancellationToken
Throws InvalidOperationException with actionable error messages instead of
allowing silent runtime failures when ReadAsync invokes the AIFunction with
no named arguments.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* prevent duplicates
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Bump Python version to 1.1.0 for a release
* Fix changelog
* 1.0.1 instead of 1.1.0
* Update CHANGELOG.md
* update version and changelog
* Bump lower bounds
* Python: Migrate GitHub Copilot package to SDK 0.2.x
Replace all imports from the non-existent copilot.types module with
correct SDK 0.2.x module paths (copilot.session, copilot.client,
copilot.tools, copilot.generated.session_events). Fix PermissionRequest
attribute access from dict-style .get() to dataclass attribute access.
Add OTel telemetry support to Copilot samples via configure_otel_providers
and document new telemetry environment variables in samples README.
* Python: Fix remaining copilot.types import in sample validation script
* Python: Include model in default_options for telemetry span attributes
* Python: Address review feedback on log_level and session kwargs typing
* Python: Scope PR to SDK 0.2.x migration only, remove net-new OTel features
- Remove RawGitHubCopilotAgent split and AgentTelemetryLayer inheritance
- Remove TelemetryConfig plumbing and OTLP/file telemetry settings
- Remove configure_otel_providers() calls from samples
- Remove telemetry env var rows from samples README
- Retain only: import path fixes, PermissionRequest attribute access fix,
log_level default fix, session kwargs typed fix, dependency pin
* Python: Update tests for SDK 0.2.x API changes
- SubprocessConfig replaces CopilotClientOptions dict
- create_session and resume_session now use keyword args
- send and send_and_wait take plain string prompt instead of MessageOptions
- on_permission_request is always required; deny-all fallback replaces omission
* Python: Pin github-copilot-sdk to >=0.2.0,<=0.2.0
Tighten the upper bound from <0.3.0 to <=0.2.0 to avoid pulling in 0.2.1+
which has breaking API changes relative to 0.2.0. The lower bound stays at
>=0.2.0 since this migration requires the 0.2.x import paths; 0.1.x would
fail at import time.
* Python: Pin github-copilot-sdk to >=0.2.1,<=0.2.1
---------
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* Harden Python checkpoint persistence defaults
Add RestrictedUnpickler to _checkpoint_encoding.py that limits which
types may be instantiated during pickle deserialization. By default
FileCheckpointStorage now uses the restricted unpickler, allowing only:
- Built-in Python value types (primitives, datetime, uuid, decimal,
collections, etc.)
- All agent_framework.* internal types
- Additional types specified via the new allowed_checkpoint_types
parameter on FileCheckpointStorage
This narrows the default type surface area for persisted checkpoints
while keeping framework-owned scenarios working without extra
configuration. Developers can extend the allowed set by passing
"module:qualname" strings to allowed_checkpoint_types.
The decode_checkpoint_value function retains backward-compatible
unrestricted behavior when called without the new allowed_types kwarg.
Fixes#4894
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: resolve mypy no-any-return error in checkpoint encoding
Add explicit type annotation for super().find_class() return value
to satisfy mypy's no-any-return check.
Fixes#4894
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Simplify find_class return in _RestrictedUnpickler (#4894)
Remove unnecessary intermediate variable and apply # noqa: S301 # nosec
directly on the super().find_class() call, matching the established
pattern used on the pickle.loads() call in the same file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #4894: Python: Harden Python checkpoint persistence defaults
* Restore # noqa: S301 on line 102 of _checkpoint_encoding.py (#4894)
The review feedback correctly identified that removing the # noqa: S301
suppression from the find_class return statement would cause a ruff S301
lint failure, since the project enables bandit ("S") rules. This
restores consistency with lines 82 and 246 in the same file.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #4894: Python: Harden Python checkpoint persistence defaults
* Address PR review comments on checkpoint encoding (#4894)
- Move module docstring to proper position after __future__ import
- Fix find_class return type annotation to type[Any]
- Add missing # noqa: S301 pragma on find_class return
- Improve error message to reference both allowed_types param and
FileCheckpointStorage.allowed_checkpoint_types
- Add -> None return annotation to FileCheckpointStorage.__init__
- Replace tempfile.mktemp with TemporaryDirectory in test
- Replace contextlib.suppress with pytest.raises for precise assertion
- Remove unused contextlib import
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address PR #4941 review comments: fix docstring position and return type
- Move module docstring before 'from __future__' import so it populates
__doc__ (comment #4)
- Change find_class return annotation from type[Any] to type to avoid
misleading callers about non-type returns like copyreg._reconstructor
(comment #2)
Comments #1, #3, #5, #6, #7, #8 were already addressed in the current code.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #4894: review comment fixes
* fix: use pickle.UnpicklingError in RestrictedUnpickler and improve docstring (#4894)
- Change _RestrictedUnpickler.find_class to raise pickle.UnpicklingError
instead of WorkflowCheckpointException, since it is pickle-level concern
that gets wrapped by the caller in _base64_to_unpickle.
- Remove now-unnecessary WorkflowCheckpointException re-raise in
_base64_to_unpickle (pickle.UnpicklingError is caught by the generic
except Exception handler and wrapped).
- Expand decode_checkpoint_value docstring to show a concrete example of
the module:qualname format with a user-defined class.
- Add regression test verifying find_class raises pickle.UnpicklingError.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: address PR #4941 review comments for checkpoint encoding
- Comment 1 (line 103): Already resolved in prior commit — _RestrictedUnpickler
now raises pickle.UnpicklingError instead of WorkflowCheckpointException.
- Comment 2 (line 140): Add concrete usage examples to decode_checkpoint_value
docstring showing both direct allowed_types usage and FileCheckpointStorage
allowed_checkpoint_types usage. Rename 'SafeState' to 'MyState' across all
docstrings for consistency, making it clear this is a user-defined class name.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: replace deprecated 'builtin' repo with pre-commit-hooks in pre-commit config
pre-commit 4.x no longer supports 'repo: builtin'. Merge those hooks into
the existing pre-commit-hooks repo entry.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* style: apply pyupgrade formatting to docstring example
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: resolve pre-commit hook paths for monorepo git root
The poe-check and bandit hooks referenced paths relative to python/
but pre-commit runs hooks from the git root (monorepo root). Fix
poe-check entry to cd into python/ first, and update bandit config
path to python/pyproject.toml.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix pre-commit config paths for prek --cd python execution
Revert bandit config path from 'python/pyproject.toml' to 'pyproject.toml'
and poe-check entry from explicit 'cd python' wrapper to direct invocation,
since prek --cd python already sets the working directory to python/.
Also apply ruff formatting fixes to cosmos checkpoint storage files.
Fixes#4894
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: add builtins:getattr to checkpoint deserialization allowlist
Pickle uses builtins:getattr to reconstruct enum members (e.g.,
WorkflowMessage.type which is a MessageType enum). Without it in the
allowlist, checkpoint roundtrip tests fail with
WorkflowCheckpointException.
Fixes#4894
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #4894: review comment fixes
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix reasoning text done events duplicating streamed delta content (#5157)
The OpenAI Responses API sends both reasoning_text.delta (incremental
chunks) and reasoning_text.done (full accumulated text) events. The
chat client was emitting Content for both, causing ag-ui to append the
full done text onto already-accumulated delta text, producing
duplicated reasoning output.
Stop emitting Content for reasoning_text.done and
reasoning_summary_text.done events, matching how output_text.done is
already handled (not emitted). The deltas contain all the content;
the done event is redundant.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(openai): emit reasoning done content as fallback when no deltas observed (#5157)
Address PR review feedback:
- Track item_ids that received reasoning deltas via seen_reasoning_delta_item_ids set
- Emit content from done events only when no deltas were received for the
item_id, preventing silent content loss on stream resumption
- Add comment documenting code_interpreter done event asymmetry
- Replace redundant ag-ui test with deduplication-focused test
- Add integration test for delta+done sequence in OpenAI chat client tests
- Add fallback path tests for done events without preceding deltas
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5157: Python: [Bug]: "type": "response.reasoning_text.delta" and "response.reasoning_text.done" both get exposed as "text_reasoning"
* Fix AG-UI reasoning streaming to use proper Start/End pattern (#5157)
_emit_text_reasoning now follows the same streaming pattern as _emit_text:
- Emits ReasoningStartEvent/ReasoningMessageStartEvent only on the first
delta for a given message_id
- Emits only ReasoningMessageContentEvent for subsequent deltas
- Defers ReasoningMessageEndEvent/ReasoningEndEvent until
_close_reasoning_block is called (on content type switch or end-of-run)
This produces the correct protocol pattern:
ReasoningStartEvent
ReasoningMessageStartEvent
ReasoningMessageContentEvent(delta1)
ReasoningMessageContentEvent(delta2)
ReasoningMessageEndEvent
ReasoningEndEvent
Instead of wrapping every delta in a full Start→End sequence.
Backward compatibility is preserved: calling _emit_text_reasoning without
a flow argument still produces the full sequence per call.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix import ordering lint error in AG-UI test file (#5157)
Move inline import of TextMessageContentEvent to the top-level import
block and ensure alphabetical ordering to satisfy ruff I001 rule.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Fix mypy error: rename loop variable to avoid type conflict with WorkflowEvent
The 'event' variable was already typed as WorkflowEvent[Any] from the
async for loop at line 590. Reusing it in the _close_reasoning_block
loop (which returns list[BaseEvent]) caused an incompatible assignment
error. Renamed to 'reasoning_evt' to avoid the conflict.
Fixes#5162
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #5157: review comment fixes
* narrow test result reporting to explicit pytest JUnit XML
* Fix test args
* Fix pytest-results-action in merge workflow and remove committed test artifacts
Apply the same JUnit XML fix from python-tests.yml to python-merge-tests.yml:
add --junitxml=pytest.xml to all test commands and narrow the results action
path from ./python/**.xml to ./python/pytest.xml. Also remove accidentally
committed pytest.xml and python-coverage.xml and add them to .gitignore.
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* .NET: Add JsonSerializerOptions support to programmatic skill APIs
Allow callers to pass custom JsonSerializerOptions when creating inline
resources and scripts via AgentInlineSkill, AgentClassSkill,
AgentInlineSkillResource, and AgentInlineSkillScript. A skill-level
default can be set on AgentInlineSkill and overridden per-resource/
script call.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Update dotnet/tests/Microsoft.Agents.AI.UnitTests/AgentSkills/TestSkillTypes.cs
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
_prepare_options() now removes tools, tool_choice, and parallel_tool_calls
from run_options after injecting agent_reference. The Foundry API rejects
requests containing both fields. FunctionTools are still invoked client-side
by the function invocation layer.
Fixes#5087
Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com>
* Guard against empty text in _parse_structured_response_value (#5145)
When using response_format with background=True (Responses API), polling
an in-progress response produces empty text. _parse_structured_response_value
unconditionally passed this to model_validate_json/json.loads, causing
ValidationError or JSONDecodeError.
Add an early return of None when text is empty, matching the existing
guard for response_format=None. This allows .value to safely return None
for in-progress background responses.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix `response_format` crash on background polling with empty text
Fixes#5145
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Raise clear handler registration error for unresolved TypeVar (#4943)
Detect unresolved TypeVar in message parameter annotations during handler
registration in both _validate_handler_signature (Executor) and
_validate_function_signature (FunctionExecutor). Raises a ValueError with
an actionable message recommending @handler(input=..., output=...) or
@executor(input=..., output=...) instead of letting TypeVar leak through
to a confusing TypeCompatibilityError during workflow edge validation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Address review feedback for #4943: reorder checks and harden function executor
- Move TypeVar check before validate_workflow_context_annotation in
_executor.py so users see the more actionable error first
- Wrap get_type_hints in try/except in _function_executor.py matching
the defensive pattern in _executor.py
- Repurpose duplicate test to cover bounded TypeVar rejection
- Add test_function_executor_allows_concrete_types for test symmetry
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Narrow get_type_hints except clause and add missing tests (#4943)
- Narrow `except Exception` to `except (NameError, AttributeError, RecursionError)`
in both _executor.py and _function_executor.py so unexpected failures in
get_type_hints are not silently swallowed.
- Add test_handler_unresolvable_annotation_raises to test_function_executor_future.py
exercising the except branch of get_type_hints in the function executor path.
- Add test_function_executor_rejects_bounded_typevar_in_message_annotation to
test_function_executor.py for parity with the Executor bounded TypeVar test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add error ordering test for TypeVar vs WorkflowContext priority (#4943)
Add test_handler_typevar_error_takes_priority_over_context_error to verify
that when a handler has both a TypeVar message and an unannotated ctx, the
TypeVar error is raised first (the more actionable issue).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Python: Fix image content serialization sending null file_id to Foundry API
Omit file_id from input_image dict when not present instead of including
it as null, which Azure AI Foundry's stricter schema validation rejects.
* Python: Fix Foundry API rejecting rich content in function_call_output
Azure AI Foundry does not support list-format output in function_call_output
items. Add SUPPORTS_RICH_FUNCTION_OUTPUT flag (default True) to
RawOpenAIChatClient, set to False in RawFoundryChatClient so Foundry
falls back to string output for tool results with images/files.
Also omit file_id from input_image dicts when not set, since Foundry
rejects explicit nulls.
* Python: Surface rich tool content as user message when Foundry lacks support
When SUPPORTS_RICH_FUNCTION_OUTPUT is False, image/file items from tool
results are injected as a follow-up user message so the model can still
process the visual content via Foundry's supported user message format.
* Xfail Foundry image integration test for the meantime
---------
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix: Concurrent Workflow Sample
* Switch to using Azure AI Projects APIs
* Remove agent streaming outputs by changing emitEvents to false on TurnToken
* Disable forwarding input from agent host executors
* Make output format more legible
* refactor: Update Concurrent sample to use message delivery event callback
Adds a public CreateSessionAsync(string conversationId, CancellationToken)
method to FoundryAgent that delegates to the inner ChatClientAgent,
allowing users to create sessions with existing server-side conversation IDs.
Fixes#5138
* add class-based skills
* address formating issues
* Remove generated filtered-unit.slnx and add to .gitignore
The filtered solution file is generated dynamically by
eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in
risks it becoming stale and out-of-sync with the real solution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove generated filtered-unit.slnx and add to .gitignore
The filtered solution file is generated dynamically by
eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in
risks it becoming stale and out-of-sync with the real solution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* discover scripts and resource from folders defined in spec
* Remove Step05 and Step06 DI skill samples
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address review comments
* fix build error
* Fix mixed path separators in skill folder discovery on .NET Framework
Path.Combine with forward-slash folder names (e.g. "scripts/f1") produces
mixed separators on Windows, causing the StartsWith containment check to
fail against Path.GetFullPath-resolved file paths. Wrap in Path.GetFullPath
to canonicalize separators before the containment comparison.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* address comment
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Improve workflow unit tests
* Update test name prefix for clarity.
* Update tests to surface any errors.
* fix check-point restore-time race in off-thread workflow event stream
* add class-based skills
* address formating issues
* Remove generated filtered-unit.slnx and add to .gitignore
The filtered solution file is generated dynamically by
eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in
risks it becoming stale and out-of-sync with the real solution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Remove generated filtered-unit.slnx and add to .gitignore
The filtered solution file is generated dynamically by
eng/scripts/New-FilteredSolution.ps1 during CI. Checking it in
risks it becoming stale and out-of-sync with the real solution.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* consolidate DI samples into one
* fix file encoding
* suppress compatibility warning
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* Add github actions workflow for verify-samples
* Make workflow run as part of PR (for now)
* Update workflow to remove pr trigger
* Address PR comments
2026-04-03 09:58:24 +00:00
1077 changed files with 87249 additions and 11430 deletions
@@ -28,7 +28,7 @@ Welcome to Microsoft's comprehensive multi-language framework for building, orch
Python
```bash
pip install agent-framework --pre
pip install agent-framework
# This will install all sub-packages, see `python/packages` for individual packages.
# It may take a minute on first install on Windows.
```
@@ -90,7 +90,7 @@ Still have questions? Join our [weekly office hours](./COMMUNITY.md#public-commu
Create a simple Azure Responses Agent that writes a haiku about the Microsoft Agent Framework
```python
# pip install agent-framework --pre
# pip install agent-framework
# Use `az login` to authenticate with Azure CLI
importos
importasyncio
@@ -120,38 +120,38 @@ if __name__ == "__main__":
```
### Basic Agent - .NET
Create a simple Agent, using Microsoft Foundry with token-based auth, that writes a haiku about the Microsoft Agent Framework
```c#
// dotnet add package Microsoft.Agents.AI.Foundry
// Use `az login` to authenticate with Azure CLI
using Azure.AI.Projects;
using Azure.Identity;
using System;
using Azure.AI.Projects;
using Azure.Identity;
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-5.4-mini";
var agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
.AsAIAgent(model: deploymentName, name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
```
Create a simple Agent, using OpenAI Responses, that writes a haiku about the Microsoft Agent Framework
var endpoint = Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT") ?? throw new InvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
var deploymentName = Environment.GetEnvironmentVariable("AZURE_AI_MODEL_DEPLOYMENT_NAME") ?? "gpt-4o-mini";
var agent = new AIProjectClient(new Uri(endpoint), new DefaultAzureCredential())
.AsAIAgent(model: deploymentName, name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
.GetResponsesClient()
.AsAIAgent(model: "gpt-5.4-mini", name: "HaikuBot", instructions: "You are an upbeat assistant that writes beautifully.");
Console.WriteLine(await agent.RunAsync("Write a haiku about Microsoft Agent Framework."));
```
@@ -207,4 +207,9 @@ The samples typically read configuration from environment variables. Common requ
## Important Notes
If you use the Microsoft Agent Framework to build applications that operate with third-party servers or agents, you do so at your own risk. We recommend reviewing all data being shared with third-party servers or agents and being cognizant of third-party practices for retention and location of data. It is your responsibility to manage whether your data will flow outside of your organization's Azure compliance and geographic boundaries and any related implications.
> [!IMPORTANT]
> If you use Microsoft Agent Framework to build applications that operate with any third-party servers, agents, code, or non-Azure Direct models (“Third-Party Systems”), you do so at your own risk. Third-Party Systems are Non-Microsoft Products under the Microsoft Product Terms and are governed by their own third-party license terms. You are responsible for any usage and associated costs.
>
>We recommend reviewing all data being shared with and received from Third-Party Systems and being cognizant of third-party practices for handling, sharing, retention and location of data. It is your responsibility to manage whether your data will flow outside of your organization’s Azure compliance and geographic boundaries and any related implications, and that appropriate permissions, boundaries and approvals are provisioned.
>
>You are responsible for carefully reviewing and testing applications you build using Microsoft Agent Framework in the context of your specific use cases, and making all appropriate decisions and customizations. This includes implementing your own responsible AI mitigations such as metaprompt, content filters, or other safety systems, and ensuring your applications meet appropriate quality, reliability, security, and trustworthiness standards. See also: [Transparency FAQ](./TRANSPARENCY_FAQ.md)
# CodeAct integration through backend-specific context providers and an `execute_code` tool
## Introduction
**CodeAct** is a pattern in which the model writes executable code — rather than emitting a fixed function-call JSON schema — to plan, transform data, and orchestrate tool calls inside a single sandbox invocation. Instead of requiring a separate model round-trip for every tool call, conditional branch, or data transformation, the model produces a short program that runs in a controlled runtime, calls host-provided tools through a `call_tool(...)` bridge, and returns structured results. This reduces latency, lowers token cost, and lets the model express richer multi-step logic that is difficult to capture in a flat tool-call sequence.
Throughout this ADR, **CodeAct** is the primary term. **Code mode** and **programmatic tool calling** refer to the same capability.
## Context and Problem Statement
We need an architecture design that supports CodeAct in both Python and .NET. This is a necessary capability for the current generation of long-running agents, which need to plan, iterate, transform tool outputs, and execute bounded code inside a controlled runtime — for example, filtering a large result set, computing derived values, or chaining several tool calls with conditional logic — instead of requiring a separate model round-trip for each of those steps. The design should preserve the same behavioral contract across SDKs, but it does not need to use the same internal extension point in each runtime. We also want to standardize on Hyperlight as the initial backend, using the existing Python package and an anticipated .NET binding package once it is available.
Throughout this ADR, **CodeAct** is the primary term. **Code mode** and **programmatic tool calling** refer to the same capability. This ADR uses **CodeAct** consistently.
Model-generated code is treated as untrusted relative to the host process. This ADR assumes the selected backend provides the primary isolation boundary, while the framework is responsible for configuring approvals and capabilities, integrating telemetry, and translating outputs and failures into framework-native shapes. If a backend cannot provide isolation appropriate for its trust model, it is not a suitable CodeAct backend.
The core design question is: **where should CodeAct integrate into the agent pipeline so that both SDKs can offer the same functionality without invasive changes to their core function-calling loops?**
## Decision Drivers
- CodeAct must shape the model-facing surface before model invocation, not only after the model has already chosen tools.
- The design should let users control which tools are available through CodeAct and which remain regular tools only.
- The design must preserve existing session, approval, telemetry, and tool invocation behavior as much as possible.
- The design should define the minimum cross-SDK telemetry and failure semantics for `execute_code`, so Python and .NET do not diverge on basic observability or error handling.
- The design must fit naturally into the extension points that already exist in each SDK.
- The design must be safe for concurrent runs and must not rely on mutating shared agent configuration during invocation.
- The chosen structure should allow multiple backend-specific providers to fit under the same conceptual design over time, even though Hyperlight is the initial target.
- The abstraction should not assume that every backend is a VM-style sandbox; alternative execution models such as Pydantic's Monty should also fit.
- The design should allow `execute_code` to be reused both as a tool-enabled CodeAct runtime and as a standard code interpreter tool implementation.
- The design should remain open to alternative language/runtime modes, such as JavaScript on Hyperlight, rather than baking the abstraction to Python only.
- The design should provide a portable way to configure sandbox capabilities such as file access and network access, including allow-listed outbound domains.
- Using CodeAct should be optional, and installing its runtime or backend dependencies should also be optional.
- Backend-specific dependencies should be isolated behind a small adapter so SDK code is not tightly coupled to an unstable package surface.
## Considered Options
- **Option 1**: Standardize on context provider-based CodeAct with a shared cross-SDK contract and backend-specific public types
- **Option 2**: Implement CodeAct as a dedicated chat-client decorator/wrapper
- **Option 3**: Integrate CodeAct directly into the function invocation layer/FunctionInvokingChatClient
## Pros and Cons of the Options
### Option 1: Standardize on context provider-based CodeAct with a shared cross-SDK contract and backend-specific public types
This option uses `ContextProvider` in Python and `AIContextProvider` in .NET, but standardizes the public concept and behavior.
In this option, the CodeAct tool set is provider-owned: only tools explicitly configured on the concrete CodeAct provider instance are available inside CodeAct, and the provider exposes direct CRUD-style management for tools, file mounts, and outbound network allow-list configuration rather than requiring a separate runtime setup object.
The agent's direct tool surface remains separate. If a tool should be available both through CodeAct and as a normal direct tool, it is configured in both places.
- Good, because both SDKs already have first-class provider concepts intended for per-invocation context shaping.
- Good, because providers operate before model invocation, which is where CodeAct must add instructions and reshape tools.
- Good, because this lets us preserve existing function invocation behavior rather than rewriting it.
- Good, because slightly different internals are acceptable while the public behavior remains aligned.
- Good, because convenience builder/decorator helpers can still be added later on top of the provider model without changing the core design.
- Good, because backend-specific runtime logic can stay inside concrete provider implementations or internal helpers instead of being forced into a lowest-common-denominator public abstraction.
- Good, because the same provider structure can support either an all-or-nothing tool surface or a mixed side-by-side tool surface.
- Good, because users can keep some tools direct-only while allowing other tools to be used from inside CodeAct.
- Good, because a provider-owned CodeAct tool registry avoids mutating or inferring the agent's direct tool surface and can work consistently in both SDKs.
- Good, because the same conceptual design can remain open to `HyperlightCodeActProvider`, a future `MontyCodeActProvider`, and other backend-specific providers over time.
- Good, because `execute_code` can evolve into multiple backend-specific runtime modes rather than being hard-wired to one Python-plus-tools mode.
- Bad, because the provider indirection adds per-run overhead — snapshotting the tool registry, dispatching lifecycle hooks, and building instructions — that a deeper integration point could skip. In practice this overhead is negligible relative to model inference latency and sandbox startup cost.
### Option 2: Implement CodeAct as a dedicated chat-client decorator/wrapper
This option would introduce a CodeAct-specific chat-client decorator that injects instructions and tools directly into the chat request pipeline.
- Good, because this is a natural fit for .NET's `DelegatingChatClient` pipeline.
- Good, because it can also support advanced custom chat-client stacks.
- Good, because backend-specific runtime selection could be hidden inside the decorator implementation.
- Good, because the decorator could also encapsulate mode-specific instruction shaping for tool-enabled versus standalone interpreter behavior.
- Good, because the decorator can decide per request whether the tool surface is exclusive or mixed.
- Bad, because Python can support this by building a custom layering stack on top of a `Raw...Client` and swapping in a different `FunctionInvocationLayer`, but that composition path is more manual than the .NET `DelegatingChatClient` pipeline.
- Bad, because it duplicates responsibilities already handled by provider abstractions.
- Bad, because it makes CodeAct look more transport-specific than it really is.
- Bad, because swappable backends and reusable interpreter or language modes become coupled to chat-client composition rather than modeled as first-class CodeAct concepts.
### Option 3: Integrate CodeAct directly into the function invocation layer/FunctionInvokingChatClient
This option would push CodeAct into Python's `FunctionInvocationLayer` and .NET's `FunctionInvokingChatClient` or related middleware.
- Good, because it is close to tool execution and can observe concrete tool invocation behavior.
- Good, because function middleware may still be useful later for auxiliary auditing or policy around sandbox-originated tool calls.
- Bad, because this is the wrong layer for constructing the model-facing tool surface and prompt instructions.
- Bad, because it does not naturally control whether the model sees an exclusive CodeAct tool surface or a mixed side-by-side tool surface.
- Bad, because it would still require a second mechanism for hiding normal tools and advertising `execute_code`.
- Bad, because it is a weak fit for standalone interpreter modes where no tool-calling loop is needed.
- Bad, because backend selection and CodeAct mode behavior are orthogonal concerns that do not belong in the function invocation layer.
- Bad, because `.NET` would become more tightly coupled to `FunctionInvokingChatClient`, which sits below the agent framework abstraction and is not the natural cross-SDK design seam.
## Approval Model Options
- **Option A**: Bundled approval for the `execute_code` invocation
- **Option B**: Pre-execution inspection of `call_tool(...)` references before approving `execute_code`
- **Option C**: Nested per-tool approvals during `execute_code`
## Pros and Cons of the Approval Options
### Option A: Bundled approval for the `execute_code` invocation
This option grants approval once, before `execute_code` starts. Provider-owned tool calls made from inside that execution run under the same approval. The effective approval of `execute_code` is determined up front from the provider configuration rather than from inspecting which tools are actually called during execution.
- Good, because it is the simplest model to explain and implement consistently in both SDKs.
- Good, because it fits naturally with long-running CodeAct loops where repeated approval interruptions would be disruptive.
- Good, because it does not require static code analysis before execution begins.
- Good, because it keeps the first release focused on the provider integration rather than a more complex approval engine.
- Bad, because approval is coarse-grained and may cover more activity than the user expected.
- Bad, because it provides less visibility into which provider-owned tools or capabilities will be exercised during the run.
### Option B: Pre-execution inspection of `call_tool(...)` references before approving `execute_code`
This option inspects submitted code for statically discoverable `call_tool("tool_name", ...)` references before execution starts and uses that information to shape the approval request.
- Good, because it can show users more detail up front while still keeping approval at a single pre-execution moment.
- Good, because it matches the common case where tool names are spelled out directly in the generated code.
- Good, because it can coexist with bundled approval as a more informative variant of the same UX.
- Bad, because the analysis is inherently best-effort and cannot reliably predict dynamic behavior.
- Bad, because it requires duplicated parsing or inspection logic that does not replace runtime enforcement.
### Option C: Nested per-tool approvals during `execute_code`
This option requests approval when sandboxed code actually attempts to invoke a provider-owned tool that requires approval.
- Good, because it aligns approval with real behavior rather than predicted behavior.
- Good, because it gives precise visibility into which provider-owned tools are being used.
- Good, because it can allow some tool calls while rejecting others within the same execution.
- Bad, because it interrupts long-running CodeAct flows and can degrade the user experience significantly.
- Bad, because it requires more complex runtime plumbing and approval UX in both SDKs.
- Bad, because repeated approval pauses may make CodeAct less useful for the exact long-running scenarios that motivate this feature.
## Decision Outcomes
### Decision 1: Integration seam and public structure
Chosen option: **Option 1: Standardize on provider-based CodeAct with a shared cross-SDK contract and backend-specific public types**, because it is the only option that maps cleanly to both SDKs, lets us reshape instructions and tools before model invocation, and avoids invasive changes to the existing function invocation loops while still allowing multiple backend-specific providers and multiple runtime modes to fit under the same structure later.
### Decision 2: Initial approval model
Chosen option: **Option A: Bundled approval for the `execute_code` invocation**, because it is the smallest approval model that fits both SDKs, works well for long-running CodeAct flows, and does not force us to standardize a more complex inspection or policy engine in the first release.
This follows the spirit of the current Python tool approval flow, where `FunctionTool` uses `approval_mode="always_require" | "never_require"` and the auto-invocation loop escalates the whole batch when any called tool requires approval.
### Design summary
We standardize the **public concept** of CodeAct across SDKs while allowing each SDK to use the extension point that fits it best.
- Python uses a `ContextProvider`.
- .NET uses an `AIContextProvider`.
- The term **CodeAct context provider** is used throughout this ADR as a design concept, not as a required public base type. Public SDK APIs should prefer concrete backend-specific types such as `HyperlightCodeActProvider` rather than a public abstract `CodeActContextProvider` or a public `CodeActExecutor` parameter.
- CodeAct support should ship as an optional package in each SDK rather than as part of the core package, so users who do not need CodeAct do not take on its installation and dependency footprint. That optional package may still depend on a few small, backward-compatible hooks in the host SDK's core agent pipeline.
- There is no separate runtime setup object in the chosen design. Concrete providers manage their provider-owned CodeAct tool registry, file mounts, and outbound network allow-list configuration directly through CRUD-style methods on the provider itself.
- At a high level, CodeAct is exposed through backend-specific context providers that contribute an `execute_code` tool, own the CodeAct-specific tool registry, and carry backend capability configuration such as filesystem and network access.
- The initial approval model is bundled approval for `execute_code`, using the same `approval_mode="always_require" | "never_require"` vocabulary as regular tools.
- The CodeAct provider exposes a default `approval_mode` for `execute_code`. If the provider default is `always_require`, `execute_code` is always treated as `always_require` regardless of the provider-owned tool registry. If the provider default is `never_require`, the effective approval for `execute_code` is derived from the provider-owned CodeAct tool registry captured for the run.
- If every provider-owned CodeAct tool in that registry has `approval_mode="never_require"`, `execute_code` is treated as `never_require`. If any provider-owned CodeAct tool in that registry has `approval_mode="always_require"`, `execute_code` is treated as `always_require`, even if the generated code may not end up calling that tool.
- Approval is granted before `execute_code` starts, and provider-owned tool calls made from inside that execution run under the same approval.
- Direct-only agent tools do not affect the approval of `execute_code`; only the provider-owned CodeAct tool registry participates in that calculation.
- This approval model is intentionally conservative. If one sensitive provider-owned tool forces `execute_code` to require approval more often than desired, the mitigation is to keep that tool direct-only or split it into a different provider/tool surface rather than trying to infer per-run tool usage up front.
- Configuring filesystem and network capability state on the provider, including adding file mounts or outbound network allow-list entries, is itself the approval for those capabilities in the initial model.
- Each `execute_code` invocation must start from a clean execution state; in-memory variables and other ephemeral interpreter/runtime state must not persist across separate calls. When a provider exposes a workspace, mounted files, or a writable artifact/output area, those files are the supported persistence mechanism across calls and are treated as external state rather than interpreter state.
- Mutating the provider's tool registry or capability configuration while a run is in flight is allowed, but it only affects subsequent runs. Provider implementations must snapshot the effective state for each run and synchronize concurrent access so shared provider instances remain safe across concurrent runs.
- The minimum cross-SDK telemetry contract is that `execute_code` is traced as a normal tool invocation nested inside the surrounding agent run, and provider-owned tool calls made from inside CodeAct continue to emit ordinary tool-invocation telemetry. Backend-specific resource metrics are optional extensions, not a required new top-level cross-SDK event model.
- Timeout, out-of-memory, backend crash, and similar sandbox failures are all execution failures of `execute_code` and should surface as structured error results rather than backend-specific public DTOs. Partial textual or file outputs may be returned only when the backend can report them unambiguously; callers must not rely on partial-output recovery as a portable guarantee.
- The provider-based structure preserves room for future pre-execution inspection and nested per-tool approvals if later experience shows they are needed.
- Concrete backend-specific providers may still use small SDK-local helpers or adapters internally, but that split is an implementation detail rather than a public API requirement.
Detailed language-specific implementation notes are specified in:
### Minimal core hooks required by the optional package
CodeAct remains optional at the package level, but the optional package depends on a small number of hooks that must live in the host SDK because the agent pipeline owns model invocation and per-run tool resolution.
- Python depends on the existing `ContextProvider` lifecycle, `SessionContext.extend_instructions(...)`, `SessionContext.extend_tools(...)`, per-run runtime tool access via `SessionContext.options["tools"]`, and the shared `ApprovalMode` vocabulary used by `FunctionTool`.
- .NET depends on the existing `AIContextProvider` seam, agent/runtime support for applying providers before model invocation, and the existing chat-client or function-invocation seams that concrete implementations use to contribute `execute_code`.
These hooks are backward-compatible because they only expose or forward per-run state that core already owns. Behavior changes only when a concrete CodeAct provider opts in and uses them.
### Concrete provider implementation contract
The design does not require a public abstract `CodeActContextProvider` base class, but it does require a stable implementation contract for concrete providers.
- Concrete providers should expose a standard capability surface at construction time, with SDK-appropriate naming for:
- approval mode
- workspace root
- file mounts
- allowed outbound targets plus any per-target method or policy restrictions needed by the backend
- Separate public `filesystem_mode` / `network_mode` flags are not required by the cross-SDK contract. Filesystem access may be disabled implicitly until a workspace or file mounts are configured, and outbound network may be disabled implicitly until an allow-list or equivalent outbound policy entry is configured.
- Concrete providers should expose direct CRUD-style methods for managing the provider-owned CodeAct tool registry, file mounts, and outbound network allow-list configuration, rather than requiring callers to construct a separate runtime setup object.
- Concrete providers should implement their host SDK's provider lifecycle hooks to:
- build CodeAct instructions,
- add `execute_code`,
- snapshot the effective CodeAct tool registry and capability settings for the run,
- compute the effective approval requirement for `execute_code`,
- configure file access and network access for the backend,
- prepare or restore execution state,
- execute code,
- and translate backend output into framework-native content.
- Any internal abstract/helper surface shared by multiple concrete providers should standardize responsibilities for:
- instruction construction,
- file-access configuration,
- network-access configuration,
- environment preparation/restoration,
- code execution,
- and output-to-content conversion.
- Backend execution output should reuse existing framework-native content/message primitives rather than introducing backend-specific public result DTOs.
Enable Agent Framework users to consume Foundry **toolboxes** — named, versioned bundles of tool definitions stored server-side in an Azure AI Foundry project — directly from `FoundryChatClient`, without dropping to the raw `azure-ai-projects` SDK.
A user who has configured a toolbox in the Foundry portal (or via the raw SDK) should be able to load it into an agent with a single call:
**Success metric:** an agent can consume a toolbox with no manual handling of version-resolution logic on the user's side.
## What is the problem being solved?
`azure-ai-projects==2.1.0a20260409002` ships a new `BetaToolboxesOperations` surface, reachable as `AIProjectClient.beta.toolboxes` on the raw SDK client (and therefore as `FoundryChatClient.project_client.beta.toolboxes` through our wrapper), that lets teams:
- Group related hosted tools (code interpreter, file search, MCP, web search, etc.) under a named toolbox
- Version toolboxes immutably, so agents can pin to a specific configuration for production stability
- Share toolboxes across multiple agents in a project
However, consuming a toolbox from the framework today requires:
1. Knowing the raw SDK accessor path (`client.project_client.beta.toolboxes`)
2. Making two calls for the common case — `.get(name)` to find the default version, then `.get_version(name, version)` to actually retrieve tools
3. Manually unpacking `toolbox.tools` before passing them to `Agent(tools=...)`
None of this is hard, but it's the kind of boilerplate that should live in the client. Every other hosted tool in `FoundryChatClient` (code interpreter, file search, web search, image generation, MCP) already has a factory method (`get_code_interpreter_tool()`, etc.). Toolbox support should fit the same shape on the chat-client composition surface.
## API Changes
### One new method on the FoundryChatClient surface
The public toolbox-consumption surface lands on:
-`RawFoundryChatClient` (inherited by `FoundryChatClient`) in `_chat_client.py`
The implementation delegates to shared helper functions in `_tools.py` so there is a single source of truth for the SDK calls.
**Scope note:**`FoundryAgent` is intentionally not part of this design. `FoundryAgent` is the runtime surface for invoking an already-configured server-side Foundry agent; if that agent should use a toolbox, the toolbox/tools should already be configured on the Foundry side (UI or `azure-ai-projects` authoring flow) before MAF connects to it.
**Scope note:** Authoring a server-side agent whose definition references a toolbox (via `PromptAgentDefinition(tools=toolbox.tools, ...)` + `client.agents.create_version(...)`) is deliberately outside MAF scope. That is an `azure-ai-projects` / service-resource authoring concern, not a future MAF feature. Users who need it should use the raw Azure SDK directly.
```python
asyncdefget_toolbox(
self,
name:str,
*,
version:str|None=None,
)->ToolboxVersionObject:
"""Fetch a Foundry toolbox by name.
If ``version`` is ``None``, resolves the toolbox's current default version
(two requests). If ``version`` is specified, fetches that version directly
(single request).
:param name: The name of the toolbox.
:param version: Optional immutable version identifier to pin to.
:return: A ``ToolboxVersionObject``. Pass its ``tools`` attribute to
``Agent(tools=toolbox.tools)``.
:raises azure.core.exceptions.ResourceNotFoundError: If the toolbox or
version does not exist.
"""
```
### Return types: raw SDK models, no custom wrappers
Methods return the `azure.ai.projects.models` types directly:
No custom wrapper classes are defined. Returning the SDK types directly:
- Eliminates maintenance overhead of keeping a custom wrapper aligned with SDK changes
- Matches the existing convention — `get_code_interpreter_tool()` returns the raw `CodeInterpreterTool` SDK type
- Means any new fields the SDK adds to these types flow through automatically
`Agent(..., tools=...)` will accept the fetched toolbox object directly by flattening to `toolbox.tools` internally.
### Design decisions
**Instance methods, not `@staticmethod` factories.** Existing `get_code_interpreter_tool()` / `get_mcp_tool()` / etc. are `@staticmethod` because they're pure factories with no network I/O. Toolbox fetching requires the project client, so these new methods must be instance methods. This is a deliberate departure from the existing-factory pattern, justified by the async-with-I/O nature of the operation.
**Raw SDK type passthrough (no custom wrappers).** There is only one toolbox type in the Foundry SDK and maintaining a shadow wrapper would create alignment risk as the SDK evolves. The raw `ToolboxVersionObject` and `ToolboxObject` carry all the fields users need. Individual tools inside `toolbox.tools` are the same `azure.ai.projects.models.Tool` subclasses returned by other factory methods.
**Two-request default-version path.** When `version=None`, implementation calls `.get(name)` to find `default_version`, then `.get_version(name, default_version)` for the tools. Caching the default-version mapping was considered and rejected — default versions can change server-side via `update(default_version=...)`, and a stale cache would silently give callers the wrong tools. Two requests at agent setup is acceptable.
**No discovery/listing surface in MAF.** Discovery is intentionally left to the raw `azure-ai-projects` client. MAF does not currently expose project-resource listing surfaces for many other Foundry resources (deployments, vector stores, agents, etc.), so the toolbox design stays narrowly focused on explicit retrieval by name/version.
**Shared helpers in `_tools.py`.** The SDK-call helper function (`fetch_toolbox`) lives in a shared module so the chat-client surface stays thin and the request logic remains centralized.
**`tools=toolbox` convenience, not a new wrapper type.** Although `get_toolbox()` returns the raw `ToolboxVersionObject`, Agent Framework can still support `tools=toolbox` / `tools=[toolbox]` by flattening the toolbox's `.tools` internally. That matches existing SDK ergonomics where some higher-level objects can be placed directly in `tools=` and unpacked underneath, without introducing a public `FoundryToolbox` wrapper.
**Errors pass through unchanged.**`ResourceNotFoundError`, `HttpResponseError`, etc. from the SDK propagate as-is. No framework-specific exception hierarchy.
## E2E Code Samples
### Primary sample
New file: `samples/02-agents/providers/foundry/foundry_chat_client_with_toolbox.py`
Normalized name precedence for `include_names` / `exclude_names`:
1. MCP `server_label`
2. generic tool `name`
3. fallback tool `type`
This keeps `get_toolbox()` as a thin fetch API and makes selection an explicit,
local post-processing step, while still allowing the ergonomic
`select_toolbox_tools(toolbox, ...)` call shape.
## Native vs MCP consumption of a Foundry toolbox
A Foundry toolbox can be consumed two ways. This design adds new implementation work only for the first:
1.**Native consumption (in scope).** Tools execute inside Foundry's agent runtime. `get_toolbox()` returns the `ToolboxVersionObject` whose `.tools` attribute carries typed tool configs that the runtime interprets server-side. This design is specifically for `FoundryChatClient`-backed local agent composition.
2.**MCP consumption (already supported through existing MCP abstractions).** A Foundry toolbox can also be exposed as an MCP server. In that case, use the existing `MCPStreamableHTTPTool(name=..., url=...)` — it already handles this path with any chat client (Foundry, OpenAI, Anthropic, etc.). No new Foundry-specific API is needed for MCP-exposed toolboxes in this design.
### MCPStreamableHTTPTool example for a Foundry toolbox endpoint
If Foundry gives you an MCP endpoint for the toolbox (for example from the
toolbox details UI / endpoint surface), the existing MCP client path is:
This is a different integration shape than `get_toolbox(...).tools`:
-`get_toolbox(...).tools` = **native Foundry hosted-tool configs** interpreted by the
Foundry runtime
-`MCPStreamableHTTPTool(name=..., url=...)` = **live MCP server connection** to a
toolbox endpoint
The design in this spec adds first-class support only for the native hosted-tool
path. The MCP path is already served by the framework's existing MCP abstractions.
These paths are not unified because they have fundamentally different execution models. Native toolbox tools are declarative configs the Foundry runtime executes; MCP consumption is a live wire protocol to a running server.
**MCP authentication inside a toolbox** is handled server-side via `project_connection_id` on individual `MCPTool` entries (OAuth connection objects configured in the Foundry project). The client never holds bearer tokens. Consent flow handling (`CONSENT_REQUIRED` → user-visible consent URL) happens during `agent.run()`, not during toolbox fetching — see Non-goals.
## Testing Strategy
Unit tests in `packages/foundry/tests/test_toolbox.py` with mocked `project_client.beta.toolboxes`. A single opt-in live round-trip, `test_integration_get_toolbox_round_trip_against_real_project`, is marked `@pytest.mark.integration`; it is skipped by default and only runs when the required Foundry credentials are available.
Coverage:
-`get_toolbox(name, version="v3")` — explicit version, single request. Assert `.get` not called, `.get_version` awaited once, returns `ToolboxVersionObject`.
-`get_toolbox(name)` — default-version resolution. Assert `.get` then `.get_version` called in order with correct args.
- Error propagation — `ResourceNotFoundError` from `.get` propagates unchanged.
- Tool passthrough — heterogeneous tool list (`CodeInterpreterTool`, `MCPTool(project_connection_id=...)`) passes through unchanged. Asserts `project_connection_id` survives.
- Agent integration smoke — `tools=toolbox` / `tools=[toolbox]` flatten to the underlying toolbox tools.
- Multiple toolbox composition smoke — `tools=[toolbox_a, toolbox_b]` flattens into a single agent tool list.
-`get_toolbox_tool_name()` — selection-name precedence is MCP `server_label`, then `name`, then `type`.
-`select_toolbox_tools(toolbox, include_names=...)` — selects by normalized tool names directly from a fetched toolbox object.
-`select_toolbox_tools(toolbox, include_types=...)` — selects by tool types with `Literal`-guided IDE completion.
- Runtime consent-flow handling for OAuth MCP tools (see Non-goals).
- Toolbox discovery/listing (`list_toolboxes`, `list_toolbox_versions`) — deliberately left to the raw Azure SDK.
- Full CRUD (`create_version`, `update`, `delete`) and server-side agent authoring — see Non-goals.
Live Foundry API integration is exercised only through the opt-in `@pytest.mark.integration` round-trip noted above; it is not part of the default test run.
The core `normalize_tools` function in `packages/core/agent_framework/_tools.py` already supports flattening composite tool inputs. Toolbox support extends that behavior so a fetched `ToolboxVersionObject` is treated as a composite tool source and flattened to its `.tools`.
That enables:
-`tools=toolbox`
-`tools=[toolbox]`
-`tools=[local_tool, toolbox]`
-`tools=[toolbox_a, toolbox_b]`
while still keeping `select_toolbox_tools(toolbox.tools, ...)` available for partial selection before the final agent construction step.
## Telemetry
Telemetry for toolbox support has two separate goals:
1.**Observe toolbox API access** — `get_toolbox()`
2.**Observe toolbox usage during agent runs** — when users pass toolbox-derived tools into `Agent(..., tools=...)`
### Request telemetry for toolbox API access
When Agent Framework constructs the `AIProjectClient` internally for `FoundryChatClient`, it already sets:
```python
user_agent=AGENT_FRAMEWORK_USER_AGENT
```
That means toolbox API requests made through:
-`project_client.beta.toolboxes.get(...)`
-`project_client.beta.toolboxes.get_version(...)`
carry the standard MAF user-agent marker and can be queried in backend request logs the same way as other Foundry SDK calls made through framework-owned clients.
Important constraint: if the caller passes an already-constructed `project_client`, Agent Framework does **not** mutate it to inject the MAF user-agent. In that case, toolbox API request telemetry reflects whatever user-agent behavior that external client was configured with.
### Runtime telemetry for toolbox usage on agent runs
Tool-level telemetry already captures which hosted Foundry tools are available / invoked during agent execution. The remaining gap is **toolbox provenance**: once the user writes `tools=toolbox` (or otherwise flattens the toolbox into tool configs), the framework sees only raw tool configs and no longer knows which toolbox name/version supplied them.
The design for closing the **client-side** observability gap is **internal provenance tracking**, not user-supplied metadata and not a new public wrapper type.
#### Provenance model
Note: this section is still under investigation.
When `get_toolbox()` or `list_toolbox_versions()` returns a `ToolboxVersionObject`, Agent Framework will attach private provenance metadata to:
- the returned toolbox object
- each tool inside `toolbox.tools`
Recommended shape (private, internal-only):
```python
tool._maf_toolbox_sources=[
{
"id":toolbox.id,
"name":toolbox.name,
"version":toolbox.version,
}
]
```
Key properties of this approach:
- **No new public API surface** — users still work with raw `ToolboxVersionObject` / `ToolboxObject`
- **No user burden** — callers do not need to stamp metadata manually
- **Provenance follows the tool objects** — works with:
-`tools=toolbox.tools`
-`tools=[toolbox_a.tools, toolbox_b.tools]`
-`tools=[*toolbox_a.tools, *toolbox_b.tools]`
- **Private attributes are not serialized** into the actual request payload sent to the model/service, so this metadata does not leak into the tool definition body
This is intentionally preferred over introducing a new public `FoundryToolbox` wrapper purely for telemetry, and preferred over a separate global provenance registry. The provenance lives on the existing tool objects so list-copying and chat-option merging naturally preserve it.
#### Span enrichment
When Agent / chat telemetry computes span attributes for a run, it should inspect the final tool list and aggregate the private toolbox provenance from any tool objects that carry it. The aggregated values are then emitted as attributes on the existing run/chat spans.
Suggested custom attributes:
-`agent_framework.foundry.toolbox.ids`
-`agent_framework.foundry.toolbox.names`
-`agent_framework.foundry.toolbox.versions`
- or a single compact attribute such as `agent_framework.foundry.toolbox.sources=["research_tools@1","some_other_tools@3"]`
The single compact `toolbox.sources` form is preferred for initial implementation because it is easy to query and easy to render from combined tool lists.
#### Scope of telemetry changes
This design does **not** require new spans. It enriches existing telemetry:
- toolbox API access continues to rely on request logs + Azure SDK distributed tracing + MAF user-agent
- agent/chat execution spans gain toolbox provenance attributes when toolbox-derived tools are present
Implementation-wise, this design most likely touches:
-`packages/foundry/agent_framework_foundry/_tools.py` — to stamp provenance on fetched toolbox objects / tools
-`packages/core/agent_framework/observability.py` — to aggregate provenance into span attributes
#### Important limitation: no server-side toolbox telemetry solution yet
Private provenance attached to tool objects is only useful on the client side. It
does **not** go over the wire to the Foundry service because those private fields
are intentionally not serialized into the request payload.
That means this design can support:
- local OpenTelemetry / exporter spans emitted by Agent Framework
- local attribution of a run to one or more fetched toolboxes
but it does **not** solve:
- server-side request-log attribution of a model/tool run back to a toolbox
- backend/database queries that need the service itself to know "this tool came from toolbox X"
At the moment, we do not have a satisfactory design for server-side toolbox
telemetry. The service would require additional structured information on the
request, and there is no accepted mechanism in this design yet for projecting
toolbox provenance into a server-visible field/header/metadata shape.
So the telemetry story in this spec is explicitly limited to **client-side
toolbox telemetry**. Server-side toolbox attribution remains an open question and
requires either:
- new service/API support, or
- a later framework design for emitting additional server-visible request metadata.
#### Deliberate non-goals for telemetry
- No requirement for users to pass explicit toolbox metadata in `default_options["metadata"]` or `run(..., options=...)`
- No new public `FoundryToolbox` wrapper type just to preserve attribution
- No attempted server-side attribution mechanism in this design (for example a custom request header or request metadata field) until there is a validated end-to-end contract for it
## Non-goals / Future Work
Explicitly out of scope for this design. Each is a separate design and PR when needed.
1.**Create/update/delete toolboxes from code.** CRUD is rare in agent consumption flows. Users who need it drop to `client.project_client.beta.toolboxes.create_version(...)`, `.update(...)`, `.delete(...)` directly.
2.**Server-side agent authoring from toolbox.** Creating a `PromptAgentDefinition(tools=toolbox.tools)` + `client.agents.create_version(...)` is a future feature covering agent authoring from code. The toolbox read API provides the building blocks; the authoring helpers are a separate design.
3.**OAuth consent-flow runtime handling.** When a toolbox contains MCP tools with `project_connection_id` pointing to an OAuth connection, the runtime may return `CONSENT_REQUIRED` mid-run. This is a runtime concern separate from toolbox fetching.
4.**Live integration tests.** This PR ships unit tests only.
5.**Toolbox caching or refresh APIs.** Each `get_toolbox()` call hits the network. Users who want caching wrap the call themselves.
This document is intentionally focused on the .NET design and public API surface.
The initial public .NET type described here is `HyperlightCodeActProvider`. Future .NET backends, such as Monty, should follow the same conceptual model with their own concrete provider types rather than through a public abstract base class or a public executor parameter.
## What is the goal of this feature?
Goals:
- .NET developers can enable CodeAct through an `AIContextProvider`-based integration.
- Developers can configure a provider-owned CodeAct tool set that is separate from the agent's direct tool surface.
- Developers can use the same `execute_code` concept for both tool-enabled CodeAct and a standard code interpreter tool implementation.
- Developers can swap execution backends over time, starting with Hyperlight while keeping room for alternatives.
- Developers can configure execution capabilities such as workspace mounts and outbound network allow lists in a portable way.
Success Metric:
- .NET samples exist for both a tool-enabled CodeAct mode and a standard interpreter mode.
Implementation-free outcome:
- A .NET developer can attach a backend-specific CodeAct provider, choose which tools are available inside CodeAct, and configure execution capabilities without rewriting the function invocation loop or ChatClient pipeline.
## What is the problem being solved?
The cross-SDK problem statement and decision rationale live in the [ADR](../../decisions/0024-codeact-integration.md). The items below narrow that statement to .NET-specific design concerns:
- Today, the easiest way to prototype CodeAct in .NET is to manually configure an `AIFunction` and wire instructions — this is fragile and requires understanding internal sandbox lifecycle details.
- There is no first-class .NET design that simultaneously covers Hyperlight-backed CodeAct now, future backend-specific providers, and both tool-enabled and interpreter modes.
- Sandbox capabilities such as mounted file access and outbound network access need a portable configuration model instead of ad hoc backend-specific wiring.
- Approval behavior needs to be explicit and configurable, mapping to .NET's existing `ApprovalRequiredAIFunction` wrapper mechanism.
## API Changes
### CodeAct contract
#### Terminology
- **CodeAct** is the primary term.
-`execute_code` is the model-facing tool name used by the initial .NET provider in this spec.
- Tool-enabled versus interpreter behavior is derived from the presence of CodeAct-managed tools, not from a separate public profile object.
#### Provider-owned CodeAct tool registry
A concrete .NET CodeAct provider owns the set of tools available through `call_tool(...)` inside CodeAct.
Rules:
- Only tools explicitly configured on the concrete provider instance are available inside CodeAct.
- The provider must not infer its CodeAct-managed tool set from the agent's direct tool configuration (`ChatClientAgentOptions.Tools` or `AIContext.Tools`).
- Exclusive versus mixed behavior is achieved by where tools are configured, not by rewriting the agent's direct tool list.
Implications:
- **CodeAct-only tool**: configured on the concrete CodeAct provider only.
- **Direct-only tool**: configured on the agent only.
- **Tool available both ways**: configured on both the agent and the concrete CodeAct provider.
#### Managing tools and capabilities after provider construction
There is no separate runtime setup object in the .NET design. CodeAct tools, file mounts, and outbound network allow-list state are managed directly on the provider through CRUD-style registry methods.
- The provider-owned CodeAct tool registry is keyed by tool name (from `AIFunction.Name`).
-`AddTools(...)` adds new tools and replaces an existing provider-owned registration when the same tool name is added again.
-`GetTools()` returns the provider's current configured CodeAct tool registry.
-`RemoveTools(...)` removes provider-owned CodeAct tools by name.
-`ClearTools()` removes all provider-owned CodeAct tools.
- File mounts are keyed by sandbox mount path.
-`AddFileMounts(...)` adds new file mounts and replaces an existing mount when the same mount path is added again.
-`GetFileMounts()` returns the provider's current configured file mounts.
-`RemoveFileMounts(...)` removes file mounts by mount path.
-`ClearFileMounts()` removes all configured file mounts.
- Allowed domains are keyed by normalized target string.
-`AddAllowedDomains(...)` adds allow-list entries and replaces an existing entry when the same target is added again.
-`GetAllowedDomains()` returns the current outbound allow-list entries.
-`RemoveAllowedDomains(...)` removes allow-list entries by target.
-`ClearAllowedDomains()` removes all configured allow-list entries.
- Tool, file-mount, and network-allow-list mutations affect subsequent runs only; runs already in progress keep the snapshot captured at run start.
- The provider must snapshot its effective tool registry and capability state at the start of each run so concurrent execution remains deterministic.
#### Approval model
The initial .NET design follows the ADR's bundled approval decision and maps to the existing `ApprovalRequiredAIFunction` wrapper from `Microsoft.Extensions.AI.Abstractions`:
- The provider exposes a default `ApprovalMode` for `execute_code` (enum: `CodeActApprovalMode.AlwaysRequire` / `CodeActApprovalMode.NeverRequire`).
Effective `execute_code` approval is computed as follows:
- If the provider default is `AlwaysRequire`, `execute_code` requires approval.
- If the provider default is `NeverRequire`, the provider evaluates the provider-owned CodeAct tool registry snapshot for that run.
- If every provider-owned CodeAct tool in that snapshot is not an `ApprovalRequiredAIFunction`, `execute_code` does not require approval.
- If any provider-owned CodeAct tool in that snapshot is an `ApprovalRequiredAIFunction`, `execute_code` requires approval, even if the generated code may not call that tool.
- When the effective approval resolves to `AlwaysRequire`, the generated `execute_code` function is wrapped in `ApprovalRequiredAIFunction` before being added to the `AIContext.Tools`.
- Provider-owned tool calls made through `call_tool(...)` during that execution run use the approval already determined for `execute_code`.
- Direct-only agent tools are excluded from this calculation.
- File and network capabilities do not create a separate runtime approval check in the initial model; configuring them on the provider is itself the approval for those capabilities.
This is intentionally conservative and matches the shape of the existing .NET function-tool approval flow, where `ApprovalRequiredAIFunction` signals to the `ChatClientAgent` that user approval is needed before invocation.
#### Shared execution flow
On each run:
1.`ProvideAIContextAsync(...)` snapshots the current CodeAct-managed tool registry and capability settings.
2. Computes the effective approval requirement for `execute_code` from the provider default plus the snapshotted tool registry.
3. Builds provider-defined instructions.
4. Builds a run-scoped `execute_code``AIFunction` from the snapshot (optionally wrapped in `ApprovalRequiredAIFunction`).
5. Returns an `AIContext` containing the instructions and `execute_code` tool.
6. When `execute_code` is invoked by the model, the run-scoped function creates or reuses an execution environment.
7. If the current provider mode exposes host tools, `call_tool(...)` is bound only to the provider-owned tool registry snapshot.
8. Code is executed and results converted to a JSON result string.
Caching rules:
- The Hyperlight backend supports snapshots: the provider caches a reusable clean snapshot after the first sandbox initialization.
- No mutable per-run execution state may be shared across concurrent runs.
- In-memory interpreter state does not persist across separate `execute_code` calls.
- Configured workspace files, mounted files, and any writable artifact/output area are the supported persistence mechanism across calls when the backend exposes them.
### .NET public API
#### Core types
```csharp
/// <summary>
/// Represents a host-to-sandbox file mount configuration.
/// </summary>
/// <param name="HostPath">Absolute or relative path on the host filesystem.</param>
/// <param name="MountPath">Path inside the sandbox (e.g. "/input/data.csv").</param>
- snapshotting the current CodeAct-managed tool registry and capability settings for the run,
- computing the effective approval requirement for `execute_code` from the provider default and the snapshotted tool registry,
- building a short CodeAct guidance instruction string,
- building a run-scoped `execute_code``AIFunction` from the snapshot,
- optionally wrapping it in `ApprovalRequiredAIFunction` when approval is required,
- and returning an `AIContext` with `Instructions` and `Tools` set.
These steps run on every invocation rather than once at construction time because the provider supports CRUD mutations between runs, concurrent runs need independent snapshots, and the effective approval and instructions depend on the tool registry state captured at run start.
The provider overrides `StateKeys` to return the configured `StateKey` from options, enabling multiple provider instances on the same agent without key collisions.
Mutating the provider after `ProvideAIContextAsync(...)` has captured a run-scoped snapshot is allowed, but it affects subsequent runs only. Provider implementations synchronize state capture and CRUD operations so shared provider instances remain safe across concurrent runs.
#### AIFunction-to-sandbox tool bridging
The Hyperlight sandbox's `RegisterTool(name, Func<string, string>)` accepts a synchronous JSON-in / JSON-out delegate. Provider-owned CodeAct tools are `AIFunction` instances that are async and cancellation-aware.
Bridging strategy:
- At sandbox initialization time, the provider registers each CodeAct-managed tool with the sandbox using the raw JSON overload: `RegisterTool(name, Func<string, string>)`.
- When the sandbox guest calls `call_tool("name", ...)`, the bridge delegate:
1. Deserializes the JSON arguments.
2. Invokes `AIFunction.InvokeAsync(...)` synchronously (via `GetAwaiter().GetResult()`) since the sandbox FFI callback is inherently synchronous.
3. Serializes the result back to JSON.
- This sync-over-async bridge is a known pragmatic trade-off constrained by the Hyperlight FFI boundary. It is safe because:
- Sandbox execution already runs on the thread pool (via `Task.Run`).
- The FFI callback runs on a worker thread with no synchronization context.
- If the Hyperlight .NET SDK later adds async tool registration, the bridge should migrate to that.
#### Runtime behavior
-`ProvideAIContextAsync(...)` adds a short CodeAct guidance block through `AIContext.Instructions`.
-`ProvideAIContextAsync(...)` adds `execute_code` through `AIContext.Tools`.
- The detailed `call_tool(...)`, sandbox-tool, and capability guidance is carried by the `execute_code` function's `Description`.
-`execute_code` invokes the configured Hyperlight sandbox guest.
- If the current CodeAct tool registry snapshot is non-empty, the runtime injects `call_tool(...)` bound to the provider-owned tool registry.
- The provider does not inspect or mutate the agent's `ChatClientAgentOptions.Tools` or the incoming `AIContext.Tools` to determine its CodeAct tool set.
- The provider snapshots the current CodeAct tool registry and capability state at run start, so later registry and allow-list mutations only affect future runs.
- Interpreter versus tool-enabled behavior is derived from the presence of CodeAct-managed tools.
-`execute_code` is traced like a normal tool invocation within the surrounding agent run.
#### Backend integration
Initial public provider:
-`HyperlightCodeActProvider`
Backend-specific notes:
- **Hyperlight**
- The provider internally creates a `SandboxBuilder` from the options and uses the `Sandbox` API from `HyperlightSandbox.Api`.
- The provider uses snapshot/restore to ensure clean execution state per `execute_code` invocation: a "warm" snapshot is taken after the first no-op initialization run, and restored before each subsequent execution.
- Network access is denied by default and is enabled through `Sandbox.AllowDomain(...)` per-target allow-list entries.
- Guest module resolution: if `ModulePath` is null for the Wasm backend, the provider attempts to locate a packaged Python guest module (equivalent to the Python SDK's `python_guest.path` resolution).
#### Capability handling
Capabilities are first-class `HyperlightCodeActProviderOptions` properties and provider-managed CRUD surfaces:
-`WorkspaceRoot`
-`FileMounts`
-`AllowedDomains`
Enabling access means:
- Configuring `WorkspaceRoot` or any `FileMounts` enables the sandbox filesystem surface exposed through `/input` and `/output`.
- Leaving both `WorkspaceRoot` and `FileMounts` unset means no filesystem surface is configured.
- Adding any `AllowedDomains` entry enables outbound access only for the configured targets; leaving it empty means network access is disabled without a separate network mode flag.
Backends may implement stricter semantics than these top-level settings.
#### Execution output representation
Backend execution output maps to a JSON result string returned from the `execute_code``AIFunction`:
```json
{
"stdout":"Hello world\n",
"stderr":"",
"exit_code":0,
"success":true
}
```
Execution failures should surface readable error text in the `stderr` field and a non-zero `exit_code`. Timeouts, out-of-memory conditions, backend crashes, and similar sandbox failures are all `execute_code` failures and should surface as structured error results. Partial textual or file outputs may be returned only when the backend can report them unambiguously.
#### `execute_code` input contract
```json
{
"type":"object",
"properties":{
"code":{
"type":"string",
"description":"Code to execute using the provider's configured backend/runtime behavior."
}
},
"required":["code"]
}
```
#### Thread safety and concurrency
- All CRUD methods (`AddTools`, `RemoveTools`, `AddFileMounts`, etc.) are synchronized via an internal lock.
-`ProvideAIContextAsync(...)` acquires the lock to snapshot current state, then releases it before building the run-scoped function. The run-scoped function closes over the immutable snapshot, not mutable provider state.
- Concurrent `execute_code` invocations from different runs use independent sandbox instances or synchronized access to a shared sandbox with snapshot/restore.
- Workspace directories (`WorkspaceRoot`, `FileMounts`) are external shared state: concurrent runs against the same workspace can race on files. This is the user's responsibility to manage (e.g., by using per-run output directories or separate provider instances).
### HyperlightExecuteCodeFunction
The provider package also exports a standalone `HyperlightExecuteCodeFunction` for direct-tool scenarios where a provider lifecycle is not needed. This is the .NET equivalent of the Python `HyperlightExecuteCodeTool`.
```csharp
/// <summary>
/// A standalone execute_code AIFunction backed by a Hyperlight sandbox.
/// Use this for manual/static wiring when the AIContextProvider lifecycle
When the tool registry and capability configuration are fixed, the provider lifecycle can be skipped entirely. Build the `execute_code` function and instructions once and pass them directly to the agent:
// execute_code will be wrapped in ApprovalRequiredAIFunction because
// at least one managed tool (delete_records) requires approval.
varagent=chatClient.AsAIAgent(
instructions:"You are a helpful assistant.",
options:newChatClientAgentOptions
{
AIContextProviders=[codeact],
});
```
## Relationship to hyperlight-sandbox .NET SDK
This design depends on the .NET SDK being added in [hyperlight-dev/hyperlight-sandbox#46](https://github.com/hyperlight-dev/hyperlight-sandbox/pull/46). Key types consumed from that SDK:
| `SandboxSnapshot` | Checkpoint/restore for clean state per execution |
The provider package (`Microsoft.Agents.AI.Hyperlight`) takes a NuGet dependency on `Hyperlight.HyperlightSandbox.Api` and `Microsoft.Extensions.AI.Abstractions`. It does **not** depend on `HyperlightSandbox.Extensions.AI` (`CodeExecutionTool`) — the provider implements its own sandbox lifecycle management with run-scoped snapshots to support concurrent invocations safely.
## Package structure
The CodeAct Hyperlight provider ships as an optional NuGet package:
This keeps CodeAct and its native sandbox dependencies optional — users who do not need CodeAct do not take on the Hyperlight installation and dependency footprint.
## Open questions
1.**Guest module distribution**: How should the default Python guest module (`.aot` file) be distributed for .NET consumers? Options include a separate NuGet package with native assets, a runtime download, or requiring users to build/provide their own.
2.**Async tool registration**: If the Hyperlight .NET SDK adds async tool callback support in a future release, the sync-over-async bridge should be replaced. This is tracked as a known technical debt item.
3.**Output file access**: The Hyperlight sandbox exposes `GetOutputFiles()` and `OutputPath` for retrieving files written by guest code. The initial design returns these as part of the JSON result. A future iteration could surface output files as framework-native content (e.g., `DataContent` or URI references).
4.**Multiple sandbox instances for concurrency**: The current design uses synchronized access to a single sandbox with snapshot/restore. An alternative pooling strategy (one sandbox per concurrent run) could improve throughput at the cost of memory. This is deferred to implementation time.
This document is intentionally focused on the Python design and public API surface.
The initial public Python type described here is `HyperlightCodeActProvider`. Future Python backends, such as Monty, should follow the same conceptual model with their own concrete provider types rather than through a public abstract base class or a public executor parameter.
## What is the goal of this feature?
Goals:
- Python developers can enable CodeAct through a `ContextProvider`-based integration.
- Developers can configure a provider-owned CodeAct tool set that is separate from the agent's direct `tools=` surface.
- Developers can use the same `execute_code` concept for both tool-enabled CodeAct and a standard code interpreter tool implementation.
- Developers can swap execution backends over time, starting with Hyperlight while keeping room for alternatives such as Pydantic's Monty.
- Developers can configure execution capabilities such as workspace mounts and outbound network allow lists in a portable way.
Success Metric:
- Python samples exist for both a tool-enabled CodeAct mode and a standard interpreter mode.
Implementation-free outcome:
- A Python developer can attach a backend-specific CodeAct provider, choose which tools are available inside CodeAct, and configure execution capabilities without rewriting the function invocation loop.
## What is the problem being solved?
The cross-SDK problem statement and decision rationale live in the [ADR](../../decisions/0024-codeact-integration.md). The items below narrow that statement to Python-specific design concerns:
- Today, the easiest way to prototype CodeAct is to infer or reshape the agent's direct tool surface, which is fragile and hard to reason about.
- In Python, inferring a CodeAct tool surface from generic agent tool configuration is fragile and hard to reason about.
- There is no first-class Python design that simultaneously covers Hyperlight-backed CodeAct now, future backend-specific providers such as Monty, and both tool-enabled and interpreter modes.
- Sandbox capabilities such as mounted file access and outbound network access need a portable configuration model instead of ad hoc backend-specific wiring.
- Approval behavior needs to be explicit and configurable, especially when CodeAct and direct tool calling may both be available.
## API Changes
### CodeAct contract
#### Terminology
- **CodeAct** is the primary term.
- **Code mode**, **codemode**, and **programmatic tool calling** refer to the same concept in this document.
-`execute_code` is the model-facing tool name used by the initial Python providers in this spec.
#### Provider-owned CodeAct tool registry
A concrete Python CodeAct provider owns the set of tools available through `call_tool(...)` inside CodeAct.
Rules:
- Only tools explicitly configured on the concrete provider instance are available inside CodeAct.
- The provider must not infer its CodeAct-managed tool set from the agent's direct `tools=` configuration.
- Exclusive versus mixed behavior is achieved by where tools are configured, not by rewriting the agent's direct tool list.
Implications:
- **CodeAct-only tool**: configured on the concrete CodeAct provider only.
- **Direct-only tool**: configured on the agent only.
- **Tool available both ways**: configured on both the agent and the concrete CodeAct provider.
#### Managing tools and capabilities after provider construction
There is no separate runtime setup object in the Python design. CodeAct tools, file mounts, and outbound network allow-list state are managed directly on the provider through CRUD-style registry methods.
- The provider-owned CodeAct tool registry is keyed by tool name.
-`add_tools(...)` adds new tools and replaces an existing provider-owned registration when the same tool name is added again.
-`get_tools()` returns the provider's current configured CodeAct tool registry.
-`remove_tool(...)` removes provider-owned CodeAct tools by name.
-`clear_tools()` removes all provider-owned CodeAct tools.
- File mounts are keyed by sandbox mount path.
-`add_file_mounts(...)` adds new file mounts and replaces an existing mount when the same mount path is added again.
-`get_file_mounts()` returns the provider's current configured file mounts.
-`remove_file_mount(...)` removes file mounts by mount path.
-`clear_file_mounts()` removes all configured file mounts.
- Allowed domains are keyed by normalized target string.
-`add_allowed_domains(...)` adds allow-list entries and replaces an existing entry when the same target is added again.
-`get_allowed_domains()` returns the current outbound allow-list entries.
-`remove_allowed_domain(...)` removes allow-list entries by target.
-`clear_allowed_domains()` removes all configured allow-list entries.
- Tool, file-mount, and network-allow-list mutations affect subsequent runs only; runs already in progress keep the snapshot captured at run start.
- The provider must snapshot its effective tool registry and capability state at the start of each run so concurrent execution remains deterministic.
#### Approval model
The initial Python design follows the ADR's initial approval decision and reuses the existing tool approval vocabulary from `agent_framework._tools`:
-`approval_mode="always_require"`
-`approval_mode="never_require"`
The provider exposes a default `approval_mode` for `execute_code`.
Effective `execute_code` approval is computed as follows:
- If the provider default is `always_require`, `execute_code` requires approval.
- If the provider default is `never_require`, the provider evaluates the provider-owned CodeAct tool registry snapshot for that run.
- If every provider-owned CodeAct tool in that snapshot is `never_require`, `execute_code` is `never_require`.
- If any provider-owned CodeAct tool in that snapshot is `always_require`, `execute_code` is `always_require`, even if the generated code may not call that tool.
- Provider-owned tool calls made through `call_tool(...)` during that execution run use the approval already determined for `execute_code`.
- Direct-only agent tools are excluded from this calculation.
- File and network capabilities do not create a separate runtime approval check in the initial model; configuring them on the provider, including adding file mounts or outbound network allow-list entries, is itself the approval for those capabilities.
This is intentionally conservative and matches the shape of the current function-tool approval flow, where `FunctionTool` uses `always_require` / `never_require` and the auto-invocation loop escalates the whole batch if any called tool requires approval.
If one sensitive provider-owned tool causes `execute_code` to require approval more often than desired, the mitigation is to keep that tool direct-only or expose it through a different CodeAct provider/tool surface. The initial model does not try to infer whether generated code will actually call that tool before approval.
If the framework later standardizes pre-execution inspection or nested per-tool approvals, the Python provider surface can grow to expose that explicitly. The initial design does not assume that those extra modes are required.
#### Shared execution flow
On each run:
1. Resolve the provider's backend/runtime behavior, capabilities, provider default `approval_mode`, and provider-owned tool registry.
2. Compute the effective approval requirement for `execute_code` from the provider default plus the provider-owned tool registry snapshot.
3. Build provider-defined instructions.
4. Add `execute_code` to the model-facing tool surface.
5. Invoke the underlying model.
6. When `execute_code` is called, create or reuse an execution environment keyed by provider type, backend setup identity, capability configuration, and provider-owned tool signature.
7. If the current provider mode exposes host tools, expose `call_tool(...)` bound only to the provider-owned tool registry.
8. Execute code and convert results to framework-native content objects.
Caching rules:
- Backends that support snapshots may cache a reusable clean snapshot.
- Backends that do not support snapshots may still cache warm initialization artifacts.
- No mutable per-run execution state may be shared across concurrent runs.
- In-memory interpreter state does not persist across separate `execute_code` calls.
- Configured workspace files, mounted files, and any writable artifact/output area are the supported persistence mechanism across calls when the backend exposes them.
- snapshotting the current CodeAct-managed tool registry and capability settings for the run,
- computing the effective approval requirement for `execute_code` from the provider default and the snapshotted tool registry,
- adding a short CodeAct guidance block,
- adding `execute_code` to the run through `SessionContext.extend_tools(...)`,
- and wiring any backend-specific execution state needed for the run.
These steps run on every invocation rather than once at construction time because the provider supports CRUD mutations between runs, concurrent runs need independent snapshots, and the effective approval and instructions depend on the tool registry state captured at run start. When the tool registry and capability configuration are fixed for the lifetime of the agent, the manual wiring pattern (see `codeact_manual_wiring.py`) can be used instead, which passes the tool and instructions directly to the `Agent` constructor and avoids the per-run provider lifecycle entirely.
If the provider stores anything in `state`, that value must stay JSON-serializable.
Mutating the provider after `before_run(...)` has captured a run-scoped snapshot is allowed, but it affects subsequent runs only. Provider implementations should synchronize state capture and CRUD operations so shared provider instances remain safe across concurrent runs.
`after_run(...)` is responsible for any backend-specific cleanup or post-processing that must happen after the model invocation completes.
If shared internal helpers are introduced later for multiple concrete providers, they should standardize responsibilities for:
- building instructions,
- computing effective approval,
- configuring file access,
- configuring network access,
- preparing or restoring execution state,
- executing code,
- and converting backend output into framework-native `Content`.
#### Runtime behavior
-`before_run(...)` adds a short CodeAct guidance block through `SessionContext.extend_instructions(...)`.
-`before_run(...)` adds `execute_code` through `SessionContext.extend_tools(...)`.
- The detailed `call_tool(...)`, sandbox-tool, and capability guidance is carried by `execute_code.description`.
-`execute_code` invokes the configured Hyperlight sandbox guest.
- If the current CodeAct tool registry is non-empty, the runtime injects `call_tool(...)` bound to the provider-owned tool registry.
- The provider does not inspect or mutate `Agent.default_options["tools"]` or `context.options["tools"]` to determine its CodeAct tool set.
- The provider snapshots the current CodeAct tool registry and capability state at run start, so later registry and allow-list mutations only affect future runs.
- Interpreter versus tool-enabled behavior is derived from the concrete provider and the presence of CodeAct-managed tools, not from a separate public profile object.
-`execute_code` should be traced like a normal tool invocation within the surrounding agent run, and provider-owned tool calls executed through `call_tool(...)` should continue to emit ordinary tool invocation telemetry.
#### Backend integration
Initial public provider:
-`HyperlightCodeActProvider`
Backend-specific notes:
- **Hyperlight**
- Provider construction needs a guest artifact via `module`, which may be a packaged guest module name or a path to a compiled guest artifact.
- File access maps naturally to Hyperlight Sandbox's read-only `/input` and writable `/output` capability model.
- Network access is denied by default and is enabled through per-target allow-list entries.
- **Monty**
- A future `MontyCodeActProvider` should be a separate public type rather than a `HyperlightCodeActProvider` mode.
- Monty does not expose built-in filesystem or network access directly inside the interpreter.
- File and URL access are mediated through host-provided external functions, so a Monty provider would need to translate provider settings into virtual files and allow-checked callbacks.
- Monty setup may also include backend-specific inputs such as `script_name`, optional type-check stubs, or restored snapshots.
#### Capability handling
Capabilities are first-class `HyperlightCodeActProvider` init parameters and provider-managed CRUD surfaces:
-`workspace_root`
-`file_mounts`
-`allowed_domains`
Concrete providers should normalize these settings internally. Hyperlight can map them directly to sandbox capabilities, while Monty must enforce them through host-mediated file and network functions and may apply stricter URL-level checks than the public provider surface expresses.
Expected management split:
-`workspace_root` remains a direct configuration value on the provider,
- file mounts are managed through provider CRUD methods,
- outbound allow-list entries are managed through provider CRUD methods.
Enabling access means:
- Configuring `workspace_root` or any `file_mounts` enables the sandbox filesystem surface exposed through `/input` and `/output`.
- Leaving both `workspace_root` and `file_mounts` unset means no filesystem surface is configured.
- Adding any `allowed_domains` entry enables outbound access only for the configured targets; leaving it empty means network access is disabled without a separate `network_mode` flag.
- A string target allows all backend-supported methods for that target; an explicit tuple or `AllowedDomain` entry narrows the methods for that target.
Backends may implement stricter semantics than these top-level settings. For example, Hyperlight naturally maps file access to `/input` and `/output`, while Monty would enforce equivalent policy through host-provided callbacks rather than direct interpreter I/O.
#### Execution output representation
Backend execution output should be translated into existing AF `Content` values rather than a custom `CodeActExecutionResult` type.
Use the existing content model from `agent_framework._types`, for example:
-`Content.from_code_interpreter_tool_result(outputs=[...])` to surface the overall result of sandboxed code execution,
-`Content.from_text(...)` for plain textual output,
-`Content.from_data(...)` or `Content.from_uri(...)` for generated files or binary artifacts,
-`Content.from_error(...)` for execution failures,
- and `Content.from_function_result(..., result=list[Content])` when surfacing the final result of `execute_code` through the normal tool result path.
#### `execute_code` input contract
```json
{
"type":"object",
"properties":{
"code":{
"type":"string",
"description":"Code to execute using the provider's configured backend/runtime behavior."
}
},
"required":["code"]
}
```
Execution failures should surface readable error text and structured error `Content`, not a custom backend result object.
Timeouts, out-of-memory conditions, backend crashes, and similar sandbox failures are all `execute_code` failures and should surface as structured error content. Partial textual or file outputs may be returned only when the backend can report them unambiguously; callers should not rely on partial-output recovery as a portable contract.
## E2E Code Samples
### Tool-enabled CodeAct mode
```python
codeact=HyperlightCodeActProvider(
tools=[fetch_docs,query_data],
workspace_root="./workdir",
allowed_domains=[("api.github.com","GET")],
)
codeact.add_tools([lookup_user])
agent=Agent(
client=client,
name="assistant",
tools=[send_email],# direct-only tool
context_providers=[codeact],
)
```
### Standard code interpreter mode
```python
codeact=HyperlightCodeActProvider(
workspace_root="./data",
)
agent=Agent(
client=client,
name="interpreter",
context_providers=[codeact],
)
```
### Manual static wiring (no per-run provider lifecycle)
When the tool registry and capability configuration are fixed, the provider lifecycle can be skipped entirely. Build the `execute_code` tool and instructions once and pass them directly to the agent:
@@ -9,9 +9,16 @@ The `verify-samples` project (`dotnet/eng/verify-samples/`) is an automated tool
## Running verify-samples
**Important:** By default, samples must be pre-built before running verify-samples. Build the solution first, or pass `--build` to build samples during the run:
// Start the initial run with a long-running task.
AgentResponseresponse=awaitagent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.",session);
AgentResponseresponse=awaitagent.RunAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.",session,options:options);
This sample demonstrates how to select the A2A protocol binding when creating an `AIAgent` from an A2A agent card.
A2A agents can expose multiple interfaces with different protocol bindings (e.g., HTTP+JSON, JSON-RPC). By default, `AsAIAgent()` prefers HTTP+JSON with JSON-RPC as a fallback. This sample shows how to use `A2AClientOptions.PreferredBindings` to explicitly control which protocol binding is used.
The sample:
- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
- Configures `A2AClientOptions` to prefer the HTTP+JSON protocol binding
- Creates an `AIAgent` from the resolved agent card using the specified binding
- Sends a message to the agent and displays the response
## Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 10.0 SDK or later
- An A2A agent server running and accessible via HTTP
**Note**: These samples need to be run against a valid A2A server. If no A2A server is available, they can be run against the echo-agent that can be spun up locally by following the guidelines at: https://github.com/a2aproject/a2a-dotnet/blob/main/samples/AgentServer/README.md
Set the following environment variable:
```powershell
$env:A2A_AGENT_HOST="http://localhost:5000"# Replace with your A2A agent server host
awaitforeach(varupdateinagent.RunStreamingAsync("Conduct a comprehensive analysis of quantum computing applications in cryptography, including recent breakthroughs, implementation challenges, and future roadmap. Please include diagrams and visual representations to illustrate complex concepts.",session))
{
// Saving the continuation token to be able to reconnect to the same response stream later.
// Note: Continuation tokens are only returned for long-running tasks. If the underlying A2A agent
// returns a message instead of a task, the continuation token will not be initialized.
// A2A agents do not support stream resumption from a specific point in the stream,
// but only reconnection to obtain the same response stream from the beginning.
// So, A2A agents will return an initialized continuation token in the first update
// representing the beginning of the stream, and it will be null in all subsequent updates.
if(update.ContinuationTokenis{}token)
{
continuationToken=token;
}
// Imitating stream interruption
break;
}
// Reconnect to the same response stream using the continuation token obtained from the previous run.
// As a first update, the agent will return an update representing the current state of the response at the moment of calling
// RunStreamingAsync with the same continuation token, followed by other updates until the end of the stream is reached.
This sample demonstrates how to reconnect to an A2A agent's streaming response using continuation tokens, allowing recovery from stream interruptions without losing progress.
The sample:
- Connects to an A2A agent server specified in the `A2A_AGENT_HOST` environment variable
- Sends a request to the agent and begins streaming the response
- Captures a continuation token from the stream for later reconnection
- Simulates a stream interruption by breaking out of the streaming loop
- Reconnects to the same response stream using the captured continuation token
- Displays the response received after reconnection
This pattern is useful when network interruptions or other failures may disrupt an ongoing streaming response, and you need to recover and continue processing.
> **Note:** Continuation tokens are only available when the underlying A2A agent returns a task. If the agent returns a message instead, the continuation token will not be initialized and stream reconnection is not applicable.
# Prerequisites
Before you begin, ensure you have the following prerequisites:
- .NET 10.0 SDK or later
- An A2A agent server running and accessible via HTTP
Set the following environment variable:
```powershell
$env:A2A_AGENT_HOST="http://localhost:5000"# Replace with your A2A agent server host
These samples demonstrate how to work with Agent-to-Agent (A2A) specific features in the Agent Framework.
For other samples that demonstrate how to use AIAgent instances,
see the [Getting Started With Agents](../../02-agents/Agents/README.md) samples.
see the [Getting Started With Agents](../Agents/README.md) samples.
## Prerequisites
@@ -15,6 +15,8 @@ See the README.md for each sample for the prerequisites for that sample.
|---|---|
|[A2A Agent As Function Tools](./A2AAgent_AsFunctionTools/)|This sample demonstrates how to represent an A2A agent as a set of function tools, where each function tool corresponds to a skill of the A2A agent, and register these function tools with another AI agent so it can leverage the A2A agent's skills.|
|[A2A Agent Polling For Task Completion](./A2AAgent_PollingForTaskCompletion/)|This sample demonstrates how to poll for long-running task completion using continuation tokens with an A2A agent.|
|[A2A Agent Stream Reconnection](./A2AAgent_StreamReconnection/)|This sample demonstrates how to reconnect to an A2A agent's streaming response using continuation tokens, allowing recovery from stream interruptions.|
|[A2A Agent Protocol Selection](./A2AAgent_ProtocolSelection/)|This sample demonstrates how to select the A2A protocol binding (HTTP+JSON vs JSON-RPC) when creating an AIAgent from an A2A agent card using A2AClientOptions.|
varendpoint=Environment.GetEnvironmentVariable("AZURE_OPENAI_ENDPOINT")??thrownewInvalidOperationException("AZURE_OPENAI_ENDPOINT environment variable is not set.");
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-4o-mini"# Optional, defaults to gpt-4o-mini
$env:AZURE_OPENAI_DEPLOYMENT_NAME="gpt-5.4-mini"# Optional, defaults to gpt-5.4-mini
```
**Note**: This demo uses Azure CLI credentials for authentication. Make sure you're logged in with `az login` and have access to the Azure OpenAI resource.
varendpoint=Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")??thrownewInvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
@@ -22,5 +22,5 @@ Set the following environment variables:
```powershell
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"# Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"# Optional, defaults to gpt-4o-mini
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"# Optional, defaults to gpt-5.4-mini
varendpoint=Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")??thrownewInvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
@@ -22,5 +22,5 @@ Set the following environment variables:
```powershell
$env:AZURE_AI_PROJECT_ENDPOINT="https://your-foundry-service.services.ai.azure.com/api/projects/your-foundry-project"# Replace with your Microsoft Foundry resource endpoint
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-4o-mini"# Optional, defaults to gpt-4o-mini
$env:AZURE_AI_MODEL_DEPLOYMENT_NAME="gpt-5.4-mini"# Optional, defaults to gpt-5.4-mini
This sample demonstrates an **advanced scenario**: combining multiple skill types in a single agent using `AgentSkillsProviderBuilder`.
> **Tip:** For simpler, single-source scenarios, use the `AgentSkillsProvider` constructors directly — see [Step01](../Agent_Step01_FileBasedSkills/) (file-based), [Step02](../Agent_Step02_CodeDefinedSkills/) (code-defined), or [Step03](../Agent_Step03_ClassBasedSkills/) (class-based).
## What it demonstrates
- Combining file-based, code-defined, and class-based skills in one provider
- Using `UseFileSkill` and `UseSkill` on the builder to register different skill types
- Aggregating skills from all sources into a single provider with automatic deduplication
## When to use `AgentSkillsProviderBuilder`
The builder is intended for advanced scenarios where the simple `AgentSkillsProvider` constructors are insufficient:
| **Multiple file script runners** — use different script runners for different file skill directories | `UseFileSkill` / `UseFileSkills` with per-source `scriptRunner` |
| **Skill filtering** — include/exclude skills using a predicate | `UseFilter(predicate)` |
## Skills Included
### unit-converter (file-based)
Discovered from `skills/unit-converter/SKILL.md` on disk. Converts miles↔km, pounds↔kg.
### volume-converter (code-defined)
Defined as `AgentInlineSkill` in `Program.cs`. Converts gallons↔liters.
### temperature-converter (class-based)
Defined as `TemperatureConverterSkill` class in `Program.cs`. Converts °F↔°C↔K.
This sample demonstrates how to use **Dependency Injection (DI)** with Agent Skills. It shows two approaches side-by-side, each handling a different conversion domain:
Both skills resolve the same `ConversionService` from the DI container. When prompted with a question spanning both domains, the agent uses both skills.
## What It Shows
- Registering application services in a `ServiceCollection`
- Defining a **code-defined** skill (distance converter) with resources and scripts that resolve services from `IServiceProvider`
- Defining a **class-based** skill (weight converter) with resources and scripts that resolve services from `IServiceProvider`
- Passing the built `IServiceProvider` to the agent so skills can access DI services at execution time
- Running a single prompt that exercises both skills to show they work together
## How It Works
1. A `ConversionService` is registered as a singleton in the DI container
2.**Code-defined skill**: An `AgentInlineSkill` for distance conversions declares `IServiceProvider` as a parameter in its `AddResource` and `AddScript` delegates — the framework injects it automatically
3.**Class-based skill**: A `WeightConverterSkill` class extends `AgentClassSkill` for weight conversions and uses `CreateResource`/`CreateScript` factory methods with `IServiceProvider` parameters
4. Both skills resolve `ConversionService` from the provider — one for distance tables, the other for weight tables
5. A single agent is created with both skills registered, and the service provider flows through to skill execution
> **Tip:** Class-based skills can also accept dependencies through their **constructor**. Register the skill class in the `ServiceCollection` and resolve it from the container instead of calling `new` directly. This is useful when the skill itself needs injected services beyond what the resource/script delegates use.
@@ -6,19 +6,32 @@ Samples demonstrating Agent Skills capabilities. Each sample shows a different w
|--------|-------------|
| [Agent_Step01_FileBasedSkills](Agent_Step01_FileBasedSkills/) | Define skills as `SKILL.md` files on disk with reference documents. Uses a unit-converter skill. |
| [Agent_Step02_CodeDefinedSkills](Agent_Step02_CodeDefinedSkills/) | Define skills entirely in C# code using `AgentInlineSkill`, with static/dynamic resources and scripts. |
| [Agent_Step03_ClassBasedSkills](Agent_Step03_ClassBasedSkills/) | Define skills as C# classes using `AgentClassSkill`. |
| [Agent_Step04_MixedSkills](Agent_Step04_MixedSkills/) | **(Advanced)** Combine file-based, code-defined, and class-based skills using `AgentSkillsProviderBuilder`. |
| [Agent_Step05_SkillsWithDI](Agent_Step05_SkillsWithDI/) | Use Dependency Injection with both code-defined (`AgentInlineSkill`) and class-based (`AgentClassSkill`) skills. |
## Key Concepts
### File-Based vs Code-Defined Skills
### Skill Types
| Aspect | File-Based | Code-Defined |
|--------|-----------|--------------|
| Definition | `SKILL.md` files on disk | `AgentInlineSkill` instances in C# |
| Resources | All files in skill directory (filtered by extension) | `AddResource` (static value or delegate-backed) |
| Definition | `SKILL.md` files on disk | `AgentInlineSkill` instances in C# | Classes extending `AgentClassSkill` |
| Resources | All files in skill directory (filtered by extension) | `AddResource` (static value or delegate-backed) |`CreateResource` factory methods |
| Sharing pattern | Copy skill directory | Inline or shared instances | Package in shared assemblies/NuGet |
| DI support | No | Yes (via `IServiceProvider` parameter) | Yes (via `IServiceProvider` parameter) |
For single-source scenarios, use the `AgentSkillsProvider` constructors directly. To combine multiple skill types, use the `AgentSkillsProviderBuilder`.
### `AgentSkillsProvider` vs `AgentSkillsProviderBuilder`
For single-source scenarios, use the `AgentSkillsProvider` constructors directly — they accept a skill directory path, a set of skills, or a custom source.
Use `AgentSkillsProviderBuilder` for advanced scenarios where simple constructors are insufficient:
- **Mixed skill types** — combine file-based, code-defined, and class-based skills in one provider
- **Multiple file script runners** — use different script runners for different file skill directories
- **Skill filtering** — include or exclude skills using a predicate
See [Agent_Step04_MixedSkills](Agent_Step04_MixedSkills/) for a working example.
@@ -15,7 +15,7 @@ using Microsoft.Agents.AI.Foundry;
stringfoundryEndpoint=Environment.GetEnvironmentVariable("AZURE_AI_PROJECT_ENDPOINT")??thrownewInvalidOperationException("AZURE_AI_PROJECT_ENDPOINT is not set.");
This sample demonstrates how to download files generated by Code Interpreter when using the OpenAI Responses API.
## What this sample demonstrates
- Creating an agent with Code Interpreter tool using `ResponsesClient.AsAIAgent()`
- Generating files through Code Interpreter (e.g., CSV, Excel, images)
- Extracting container file citations from agent response annotations
- Downloading container files using the `ContainerClient` API
## Container files vs regular files
When Code Interpreter generates a file, the file is stored inside a **container** with a `cntr_` prefixed ID. The file itself gets a `cfile_` prefixed ID.
These container files **cannot** be downloaded using the standard Files API (`GetOpenAIFileClient`), which returns 404 for `cfile_` IDs. Instead, you must use the **Containers API** (`GetContainerClient`) to download them:
The container ID and file ID are available from the `ContainerFileCitationMessageAnnotation` annotation in the response, accessible via `CitationAnnotation.RawRepresentation`.
## Prerequisites
- .NET 10 SDK or later
- OpenAI API key with access to a model that supports Code Interpreter
Set the following environment variables:
```powershell
$env:OPENAI_API_KEY="sk-..."
$env:OPENAI_CHAT_MODEL_NAME="gpt-4o-mini"# Optional, defaults to gpt-4o-mini
```
## Run the sample
```powershell
dotnetrun
```
## See also
- [Code Interpreter File Download with Foundry](../../../02-agents/AgentsWithFoundry/Agent_Step24_CodeInterpreterFileDownload/) — same scenario using Microsoft Foundry
- [Code Interpreter](../../../02-agents/AgentsWithFoundry/Agent_Step14_CodeInterpreter/) — Code Interpreter without file download
@@ -14,4 +14,5 @@ Agent Framework provides additional support to allow OpenAI developers to use th
|[Using Reasoning Capabilities](./Agent_OpenAI_Step02_Reasoning/)|This sample demonstrates how to create an AI agent with reasoning capabilities using OpenAI's reasoning models and response types.|
|[Creating an Agent from a ChatClient](./Agent_OpenAI_Step03_CreateFromChatClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Chat.ChatClient instance using OpenAIChatClientAgent.|
|[Creating an Agent from an OpenAIResponseClient](./Agent_OpenAI_Step04_CreateFromOpenAIResponseClient/)|This sample demonstrates how to create an AI agent directly from an OpenAI.Responses.OpenAIResponseClient instance using OpenAIResponseClientAgent.|
|[Managing Conversation State](./Agent_OpenAI_Step05_Conversation/)|This sample demonstrates how to maintain conversation state across multiple turns using the AgentSession for context continuity.|
|[Managing Conversation State](./Agent_OpenAI_Step05_Conversation/)|This sample demonstrates how to maintain conversation state across multiple turns using the AgentSession for context continuity.|
|[Code Interpreter File Download](./Agent_OpenAI_Step06_CodeInterpreterFileDownload/)|This sample demonstrates how to download files generated by Code Interpreter using the Containers API (`cfile_`/`cntr_` IDs).|
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.