mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
4c317eb7cf
* refactor(foundry_hosting): build FoundryHostedAgentHistoryProvider on azure.ai.agentserver SDK
Rebuilds the Foundry hosted-agent history provider on top of
``azure.ai.agentserver``'s ``FoundryStorageProvider`` instead of the
in-house ``_HttpStorageBackend``. Splits the monolithic ``_responses.py``
into focused modules:
- ``_history_provider.py`` — new ``FoundryHostedAgentHistoryProvider``
that talks to the SDK's ``FoundryStorageProvider``, threads
``response_id`` / ``previous_response_id`` through ``ContextVar``s via
``bind_request_context``, and lifts host-bound isolation keys
(``x-agent-{user,chat}-isolation-key``) from the optional
``agent_framework_hosting`` package into a provider-local
``IsolationContext`` so the storage layer carries the correct
partition keys without channels having to know about them.
- ``_shared.py`` — extracts all SDK ``Item`` / ``OutputItem`` ↔
framework ``Message`` conversion helpers into one place so both
``_responses.py`` and the new history provider can share them.
Restores ``_convert_file_data`` for inline ``input_file`` payloads,
and the hosted-MCP routing for ``custom_tool_call_output`` items
whose ``call_id`` carries the ``mcp_*`` prefix.
- ``_ids.py`` — shared id helpers.
- ``_responses.py`` — shrinks ~700 lines, re-exports converters for
back-compat with existing tests.
- ``tests/test_history_provider.py`` — exercises the new provider
against a fake SDK backend; the host-isolation test is gated on the
optional ``agent_framework_hosting`` import.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* feat(foundry_hosting): add local_storage_root for file-based dev history
Adds an optional `local_storage_root: str | Path | None` parameter to
`FoundryHostedAgentHistoryProvider`. When set and the provider is
running outside a Foundry Hosted Agent container, conversations are
persisted to JSONL files via `agent_framework.FileHistoryProvider`
laid out as:
{root}/{user_key or '~none'}/{chat_key or '~none'}/{session_id}.jsonl
Hosted mode (FOUNDRY_HOSTING_ENVIRONMENT set) ignores the option with a
one-time INFO log so Foundry storage always wins on the platform. The
in-memory fallback is unchanged when the option is omitted.
Path safety: isolation segments are validated against the same character
allowlist FileHistoryProvider uses for session-id stems and
base64-url-encoded with a reserved "~iso-" prefix when unsafe. "~none"
sentinel for missing keys can never collide with a real isolation key
(real keys starting with "~" are encoded). The resolved target dir is
also re-checked to be inside the configured root.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry_hosting): address PR-1 review comments
- _shared.py:_capture_raw narrows `except Exception` to `except TypeError`
and emits a WARNING with traceback so the lossy fallback to a
synthesized round-trip is observable. Mirrors the reviewer suggestion.
- _history_provider.py:save_messages narrows `except Exception` to
`except FoundryStorageError` so only storage-validation failures
(4xx/5xx, opaque server errors) are swallowed. Network / TLS / auth
/ payload-builder bugs propagate so the caller can retry / alert.
Adds an instance-level `failed_writes` counter operators can poll
for silent-drop visibility.
- _history_provider.py id-stamping loop: drops the
`contextlib.suppress(AttributeError, TypeError)` around
`item.id = new_id` so SDK contract changes surface in the test
suite instead of silently corrupting the chain (the storage backend
rejects the entire `create_response` with HTTP 500 when synthetic
prefix-based ids leak through). `import contextlib` removed.
- tests:
* Unit-cover `foundry_response_id` / `foundry_response_id_factory` /
`foundry_item_id` so SDK `IdGenerator` contract changes are caught
locally.
* Cover the `save_messages` wire payload: required-by-storage fields
(`background`, `parallel_tool_calls`, `instructions`,
`agent_reference`), env-var-driven stamping (`FOUNDRY_AGENT_NAME` /
`FOUNDRY_AGENT_VERSION` / `FOUNDRY_AGENT_SESSION_ID` /
`MODEL_DEPLOYMENT_NAME` with `AZURE_AI_MODEL_DEPLOYMENT_NAME`
fallback), and the rule that `model` / `agent_session_id` /
`agent_reference.version` are omitted (not stamped to `None`) when
their env vars are unset.
* Cover the `FOUNDRY_AGENT_SESSION_ID` last-resort chain anchor on
both the get and save paths, including the prefix gate that blocks
non-`caresp_*`/`resp_*` values from reaching storage, and the
precedence rule that a host binding wins over the env.
* Replace the old `test_save_messages_swallows_backend_errors` with
two tests asserting the new contract: storage errors are swallowed
and bump `failed_writes`; everything else propagates and leaves the
counter at zero.
141 unit tests pass; mypy + pyright + ruff clean.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* fix(foundry_hosting): address PR-1 round-2 review comments
- Hosted detection now delegates to AgentConfig.from_env().is_hosted so
a future Foundry SDK rename of FOUNDRY_HOSTING_ENVIRONMENT propagates
automatically; drop the local _ENV_FOUNDRY_HOSTING_ENVIRONMENT
constant.
- Drop the FOUNDRY_AGENT_SESSION_ID fallback in both get_messages and
save_messages: per the SDK it identifies the *container instance*,
not the conversation, so chaining off it would silently merge
unrelated conversations across container restarts. The host-bound
previous_response_id (set by ResponsesChannel) is the only
authoritative anchor; the env value is still stamped into the
persisted envelope's agent_session_id for operator correlation.
- Update module docstring + replace TestFoundryAgentSessionIdAnchor
with assertions for the new contract (env var ignored as anchor,
still stamped onto persisted envelope, host binding wins).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
* refactor(foundry_hosting): reconcile with upstream main (#5851, #5666)
Brings the FoundryHostedAgentHistoryProvider refactor branch back into
sync with the foundry_hosting changes that have landed on upstream
main since PR-1 was opened:
* #5851 (path traversal in checkpoint storage, CWE-22).
The workflow-host code in ``_responses.py`` builds a
``FileCheckpointStorage`` from a caller-controlled ``context_id``
(``previous_response_id`` / ``conversation_id`` / ``response_id``).
Switch both call sites to route through
``_checkpoint_storage_for_context``, which rejects separators,
NUL bytes, drive letters, absolute paths, and all-dot segments,
and enforces ``is_relative_to(root)`` before any directory is
created.
* #5666 (function approval flow).
Make the SDK-Item → AF-Message conversion helpers in ``_shared.py``
async and accept an optional ``approval_storage`` keyword:
- ``_items_to_messages`` / ``_item_to_message`` /
``_item_to_message_inner``
- ``_output_items_to_messages`` / ``_output_item_to_message`` /
``_output_item_to_message_inner``
For ``mcp_approval_request`` / ``mcp_approval_response`` items the
helpers now load the original function-call Content from the
approval storage (via ``ApprovalStorage.load_approval_request``)
instead of synthesising a placeholder. This matches upstream
semantics and lets approval round-trips reconstruct the real
payload.
The ``ApprovalStorage`` Protocol moves to ``_shared.py`` so the
conversion helpers can reference it without pulling in
``_responses.py`` (which would create a circular import). The
concrete ``InMemoryFunctionApprovalStorage`` and
``FileBasedFunctionApprovalStorage`` stay in ``_responses.py``
next to the host that owns them, and re-export
``ApprovalStorage`` from ``_shared`` for compatibility.
The workflow-host streaming path passes its own
``self._approval_storage`` into ``_to_outputs`` so approval
requests are saved at emit time.
* Bump ``_history_provider.FoundryHostedAgentHistoryProvider.get_messages``
to ``await`` the now-async ``_output_items_to_messages`` call.
No public API change beyond the new keyword-only ``approval_storage``
parameter on the four conversion entry points.
Validation:
- uv run poe check-packages -P foundry_hosting (lint + pyright clean)
- uv run poe mypy -P foundry_hosting (clean)
- uv run poe test -P foundry_hosting (183 passed, 1 skipped)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
4c317eb7cf
·
2026-05-22 15:42:06 +02:00
History
Foundry Hosting
This package provides the integration of Agent Framework agents and workflows with the Foundry Agent Server, which can be hosted on Foundry infrastructure.