58da0cc2534b0e5350bd1a83d75f363a08c3103d
1404 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
58da0cc253 |
Python: add MiddlewareFailure, a first-class fatal signal for function middleware (#7562)
* feat(core): first-class fatal signal (MiddlewareFailure) for function middleware The function-invocation loop converts every exception raised by function middleware into a tool-error result and keeps looping, so middleware that needs fail-closed semantics (enforcement layers, guardrails) had no loud escape: the agent-hooks feature simulated one by mutating shared run state, raising MiddlewareTermination, and re-raising the real failure two hops away at the run boundary. Introduce MiddlewareFailure (a MiddlewareException sibling of MiddlewareTermination) as the loop's explicit fail-closed escape: - _auto_invoke_function re-raises it (both the direct and the pipeline path) instead of absorbing it into a tool-error result; ordinary exceptions keep the absorb-and-continue contract. - A failing call fails the whole parallel batch: in-flight sibling tool tasks are cancelled and awaited before the failure propagates. - Every existing MiddlewareTermination absorb site (agent/chat pipelines, _execute_single_function_call, harness loop, purview) passes it through untouched by construction, and agent/chat middleware exceptions already propagate, so one exception type gives uniform fail-loud semantics across all three categories. Migrate the agent-hooks feature to the new signal: delete the _RunState.halted back-channel and its three run-boundary re-raise checks, drop the halted arm of the termination special case in the function middleware (the approval-request pass-through moves to the single approval check on the normal path), and fail partial installs loudly. Tool-seam host_error blocks keep surfacing as InterceptionBlocked at the run boundary via the exception cause chain (one deny surface at every seam, pinned by tests). Spec 004 gains the middleware-failure invariants and matrix rows. Closes #7522 Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(core): harden tool-seam unwrap and pin review findings Review round follow-ups for the MiddlewareFailure feature: - Only agent-hooks' own tagged tool-seam halts (_ToolSeamBlockFailure) authorize re-raising the chained InterceptionBlocked at the run boundary; a third-party MiddlewareFailure with a crafted InterceptionBlocked cause now propagates as raised instead of laundering an attacker-shaped interception record into the feature's deny surface (regression test added, verified by mutation). - Document that middleware must not catch MiddlewareFailure (docstring and spec 004): swallowing it converts a fail-closed abort back into a running, possibly unguarded loop. - Pin the trailing termination re-raise in the agent-hooks function middleware: an inner short-circuit is bracketed and still propagates, skipping outer middleware post-code (test fails with the re-raise removed). Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(core): acyclic tool-seam unwrap chain; document cooperative batch cancellation Address two automated-review findings on the MiddlewareFailure PR, both confirmed empirically: - _reraise_tool_seam_block created a two-object exception-chain cycle (block.__cause__ -> wrapper -> block) by re-raising the chained InterceptionBlocked `from` its transport wrapper. Detach the wrapper's back-links and re-raise bare, recording the wrapper as the block's __context__ — acyclic, both exceptions still visible in tracebacks. Regression test walks the chain and pins finiteness (verified to fail against the cyclic re-raise). - Batch cancellation is cooperative: a synchronous tool body already running in a worker thread (asyncio.to_thread) cannot be interrupted by task cancellation and may complete its side effects after the failure reached the caller; its result is discarded either way and propagation is not delayed behind it. Narrow the stated contract (MiddlewareFailure docstring, loop comment, spec 004) and pin it with a blocking-sync-sibling regression test. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(core): settle dangling calls on service-managed conversations on abort Address maintainer review on the MiddlewareFailure PR: - A MiddlewareFailure escaping a tool batch on a service-managed conversation left the hosted thread ending in unresolved function_call items: _update_continuation_state persists session.service_session_id when the model turn completes (before tool execution), and probe-verified the next run sends only the new user message against that conversation — OpenAI-style continuations reject such a request, so a routine policy abort left the session permanently stuck. Both loops now settle the thread before propagating: one error function_result per dangling call, submitted with tool_choice="none" in a single extra request whose response is discarded; a settlement failure never masks the abort, and runs without a service-managed conversation make no extra request. Pinned by three regression tests (non-streaming, streaming, and the no-conversation no-cost case); spec 004 and the MiddlewareFailure docstring updated. - Make the three tool-bracket escape tuples in the agent-hooks function middleware identical (MiddlewareTermination, MiddlewareFailure, CancelledError): a MiddlewareFailure raised inside the post/error-bracket emit bodies is unreachable today, but the uniform tuples remove the need to reason about why they would differ, and preserve the exact exception (including the private tool-seam tag) if the emitter ever surfaces one. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(core): advance settled continuation; settle approved-replay aborts Address maintainer review on the MiddlewareFailure settlement path, both probe-verified (branch rebased onto current main first): - Advance the persisted continuation to the settlement response. For response-ID continuations (OpenAI Responses store=True, where the response id is the continuation handle) the settlement response is the first endpoint whose chain includes the synthetic tool outputs; leaving session.service_session_id on the pre-settlement response made the settlement ineffective — the next run would continue from the still-unresolved turn. The settlement response now runs through _update_function_invocation_continuation_state (a no-op for stable conversation-object ids). Pinned by a regression test that fails with the advance removed. - Cover the approval-resolution phase: a MiddlewareFailure raised while an approved tool is replayed escapes loudly (probe-verified, already the case) but executed before the loops' settlement seams, leaving the original — already service-persisted — call unresolved. _resolve_approval_responses now takes a settle_dangling_calls callback invoked with the approved batch on abort; the settlement helper became a layer method taking explicit calls (approval-response wrappers unwrap to their underlying calls, hosted-tool approvals are left to their provider protocol) and carries its own best-effort containment. Pinned by deny-during- replay regression tests in both response modes, mutation-verified. Spec 004 invariants and matrix rows updated accordingly. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> --------- Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> |
||
|
|
00d7102c54 |
Python: fix(workflows): preserve all trace contexts in FanInEdgeRunner aggregation (#7557)
* fix(workflows): preserve all trace contexts in FanInEdgeRunner aggregation FanInEdgeRunner collected trace contexts and source span IDs using the singular backward-compat properties (msg.trace_context / msg.source_span_id), which return only the first element of the plural lists. When a message arriving at a fan-in already carries multiple trace contexts (e.g. from a prior fan-in aggregation), all but the first were silently dropped. Iterate over the plural fields (trace_contexts / source_span_ids) and extend the aggregated lists so every trace context and source span ID from every source message is preserved. This keeps distributed tracing links intact for nested fan-in topologies. Added test_fan_in_preserves_multiple_trace_contexts_per_message that sends a message with two trace contexts through a fan-in and asserts all three contexts (2 + 1) reach the target executor. * fix: address Copilot review comments on trace context aggregation 1. Pair trace_contexts and source_span_ids per-message (via zip) instead of flattening independently. This prevents misalignment when a message has mismatched counts — orphans are dropped per-message rather than shifting all subsequent pairs out of alignment. 2. Remove TraceCapturingAggregator's override of Executor.execute() (documented as "do not override"). Capture trace data from the WorkflowContext passed to the handler instead. --------- Co-authored-by: weed33834 <weed33834@users.noreply.github.com> Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com> |
||
|
|
d80c340a06 | Clarify function-loop spec update guidance (#7706) | ||
|
|
af4347a61d |
Python: Restrict workflow type deserialization (#7500)
Resolve request-info type names only from exact caller-provided mappings or already-loaded module namespaces. Remove payload-selected imports and add focused regression coverage for both request and response type fields. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a53fe20b-c3f0-4583-badc-d5deac7c1049 |
||
|
|
a445e4815d |
Bump ty from 0.0.64 to 0.0.70 in /python (#7644)
Bumps [ty](https://github.com/astral-sh/ty) from 0.0.64 to 0.0.70. - [Release notes](https://github.com/astral-sh/ty/releases) - [Changelog](https://github.com/astral-sh/ty/blob/main/CHANGELOG.md) - [Commits](https://github.com/astral-sh/ty/compare/0.0.64...0.0.70) --- updated-dependencies: - dependency-name: ty dependency-version: 0.0.69 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
8b8fbbba03 |
Bump flit from 3.12.0 to 4.0.2 in /python (#7645)
Bumps [flit](https://github.com/pypa/flit) from 3.12.0 to 4.0.2. - [Changelog](https://github.com/pypa/flit/blob/main/doc/history.rst) - [Commits](https://github.com/pypa/flit/compare/3.12.0...4.0.2) --- updated-dependencies: - dependency-name: flit dependency-version: 4.0.2 dependency-type: direct:development update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
925d722acf |
Python: clarify skill script argument guidance (#7695)
* Python: clarify skill script argument guidance * test: harden skill argument guidance coverage |
||
|
|
6a3633e54a |
Python: Add a global workflow checkpoint type registry (#7636)
* Add a glocal checkpoint type registry * Update samples * Revert uv.lock * Address comments * Revert uv.lock * Revert uv.lock |
||
|
|
648a31ade6 |
Python: Surface A2A preview consent URLs (#7606)
* fix(foundry-hosting): surface A2A consent URLs * Use non-hashing membership Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Tao Chen <williamchan444307762@hotmail.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Tao Chen <taochen@microsoft.com> |
||
|
|
11592495db | docs: fix Agent Lightning installation link (#7693) | ||
|
|
9c3a1a4af7 |
Python: Enhance _OutputItemTracker to prevent duplicate function call streaming (#7486)
* Python: Enhance _OutputItemTracker to prevent duplicate function call streaming * Handle empty function call metadata arguments Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 9b232bff-6a8c-4b02-addd-89de57a82f6a * Python: Refactor _OutputItemTracker to manage outstanding function calls and update tests for call ID reuse --------- Copilot-Session: 9b232bff-6a8c-4b02-addd-89de57a82f6a |
||
|
|
228754d7fa |
Python: Fix AG-UI url source dropping attachments when the URL is in source.value (#7655)
The ag-ui-protocol `InputContentUrlSource` carries the URL in `source.value`, but `_extract_multimodal_source_fields` only read `source.url`/`source.uri` for url-typed sources, so attachments sent in the spec shape were dropped during the AG-UI to MAF conversion. The base64 branch already read `source.value` correctly. Read `source.value` first, keeping `url`/`uri` as fallbacks for the non-spec shape. Adds tests for both. Fixes #7653 |
||
|
|
8461667fe4 |
fix: deduplicate streamed DevUI tool calls (#7652)
Refs #7651 🐛 - Generated by Copilot |
||
|
|
e289320027 |
Python: Add approval storage and improve truth checks (#7631)
* Add approval storage and improve truth checks * Address PR comments * Update spec * Revert changes to agui since it is already handled in another pr * Add missed change |
||
|
|
ae7fa3389c |
Python: Bump Python package versions for 1.14.0 release (#7661)
* Bump Python package versions for 1.14.0 release Bump the CHANGELOG-selected packages for the 1.14.0 release: minor versions for root/core, AG-UI, Foundry, OpenAI, and orchestrations due to additive public APIs; patch versions for declarative and GitHub Copilot fixes; and Pacific-date prerelease stamps only for changed alpha/beta packages. No beta cohort bump was applied. Core dependency floors follow the strict policy and remain unchanged because no dependent package requires a new 1.14 API. Release validation also identified and corrected missing AG-UI and Copilot Studio runtime dependencies and aligned GitHub Copilot metadata with its Python 3.11 SDK requirement. Lab is intentionally skipped because its changes are development-only, and the moved Azure Functions and Durable Task packages are documented but no longer versioned here. * Raise AG-UI core dependency floor |
||
|
|
4aa737eee5 |
Python: [BREAKING] Require building functional workflow instances (#7521)
* Harden functional workflow continuation authority Use a versioned opaque single-use token on WorkflowRunResult, validate it before request correlation, consume it immediately before replayed user code, and rotate it on each pause. Carry the same explicit authority through streaming and non-streaming FunctionalWorkflowAgent responses. Files changed: functional workflow/runtime result APIs, functional HITL regression tests, core agent guidance, and the functional HITL sample. Next iteration: enforce pending-state overlap and token-authorized abandonment, then document and test checkpoint authorization boundaries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Enforce one pending functional continuation Reject fresh messages and checkpoint restores while an in-memory continuation is pending. Add token-authorized abandonment on FunctionalWorkflow and FunctionalWorkflowAgent, and clear retained replay state atomically when authority is consumed while preserving the active message for token rotation and checkpoints. Files changed: functional workflow runtime and agent adapter, functional lifecycle regression tests, and core workflow guidance. Next iteration: preserve and document authorized checkpoint continuation boundaries. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Preserve authorized functional checkpoint continuation Treat checkpoint restore as a host- and storage-authorized path independent of process-local continuation tokens, and issue fresh authority whenever restored execution pauses again. Cover default and per-run storage, deterministic and custom request IDs, token rotation, and checkpoint-plus-response restore. Files changed: functional workflow and checkpoint interface guidance, functional checkpoint lifecycle tests, the functional HITL sample, and core workflow guidance. Next iteration: run the final repository-wide Python validation gates. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Validate Python continuation hardening Run the complete Python workspace checks, aggregate coverage suite, repository hooks, and core package build from the final combined worktree. Keep the validation iteration code-neutral because all gates pass without corrective changes. Files changed: none; this commit records the final validation gate. Blockers: none. Next iteration: no remaining AFK tasks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Handle functional checkpoint continuation failures Publish retained continuation state only after checkpoint persistence succeeds, and cover reuse after a transient save failure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 * Address functional continuation review findings Add owner recovery for lost tokens, harden malformed token validation, preserve consistent failure surfaces, and keep agent pending state aligned with resumable workflow state. Document process-local single-use continuation semantics and extend regression coverage across direct, streaming, checkpoint, and agent paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 * Handle functional continuation cancellation Release the workflow run guard when cancellation interrupts resumed user code while keeping the single-use continuation token consumed. Replace sample assertions with explicit runtime checks and add cancellation regression coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 * Simplify functional workflow instance isolation Remove continuation-token handling and align functional workflows with the graph workflow ownership model: one stateful instance per logical caller or session. Add create_instance() for independent callers, document the ownership contract, and cover pending-state isolation between instances. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 * Scope functional workflow checkpoint storage Do not inherit checkpoint storage when creating an independent workflow instance. Allow hosts to provide an explicitly caller-scoped storage adapter and document that shared checkpoint access requires host authorization and tenant isolation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 * Require building functional workflow instances Make @workflow return a stateless FunctionalWorkflowDefinition and require build() before run() or as_agent(). This aligns functional workflows with the graph definition/build lifecycle and prevents module-level decorated definitions from retaining caller state. Move checkpoint configuration to build(), export the definition type, migrate samples, and cover isolated built instances. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: a8f47743-1cdc-4924-8e1b-667d0d790b78 |
||
|
|
8c4da3c3b9 |
Python: Harden AG-UI approval lifecycle and resume semantics (#7594)
* Route local approvals through lifecycle owner Key decisions: - Add an internal typed approval lifecycle with pending, claimed, executing, and settled states. - Keep authorization separate from execution; only LocalPendingToolTransitionOwner invokes approved local calls. - Register server-owned occurrences before canonical ResumeDecision claims and retain one replayable result under the original call identity. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_approval_state.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_approval_result_event.py - packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py Verification: - 952 AG-UI tests passed. - Focused lifecycle/public tracer passed with warnings treated as errors. - Ruff format/check and AG-UI Pyright passed. - git diff --check passed. Notes for next iteration: - The function-calling-loop scenario mapping is inaccessible under the organization content-exclusion policy and could not be updated. - The workspace Poe package fan-out is blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks were run. * Make approval batches occurrence-safe Key decisions: - Give each local approval a scoped logical occurrence identity and share one occurrence across trusted thread aliases. - Validate complete Resume Decision batches before applying claims, then account for accepted, rejected, and cancelled occurrences independently. - Preserve sibling authority and original result identity across failures, mixed decisions, and reused raw call IDs. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_approval_state.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py Verification: - 959 AG-UI tests passed with 90% lifecycle branch coverage. - 30 focused lifecycle/public tracer tests passed with warnings treated as errors. - Ruff format/check and AG-UI Pyright passed. - git diff --check passed. Notes for next iteration: - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. - Workspace typing fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; package-local Pyright passed, while package-local MyPy retains three unrelated baseline errors. * Make approval resume retries idempotent Key decisions: - Retain terminal decisions and outcomes by scoped occurrence so identical accepted and rejected retries reproject results without granting execution authority again. - Reject conflicting names, arguments, decisions, wrong-scope lookups, and expired authority before an execution intent can reach the local transition owner. - Keep protocol normalization in the runner while using server-owned lifecycle context to canonicalize retries and preserve existing AG-UI wire aliases. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py - packages/ag-ui/tests/ag_ui/test_endpoint.py Verification: - 965 AG-UI tests passed with 90% approval lifecycle coverage. - 18 focused lifecycle, hostile-resume, wrong-thread, and endpoint retry tests passed with runtime and deprecation warnings treated as errors. - Ruff format/check and AG-UI package-local Pyright passed. - git diff --check passed. Notes for next iteration: - Terminal retention is process-local and unbounded until the later bounded-retention issue adds its explicit policy. - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. * Separate approval execution ownership Key decisions: - Carry explicit local, hosted, deferred in-run, or unavailable ownership on every approval occurrence and authorized intent. - Keep lifecycle authorization separate from execution; local calls execute only through the local adapter while hosted and setup-injected decisions forward through owner-specific adapters. - Leave declaration-only calls pending when no transition owner can act, and settle forwarded outcomes against the original occurrence without local fallback. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_approval_state.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_endpoint.py Verification: - 967 AG-UI tests passed with 92% package coverage and 89% approval lifecycle coverage. - 94 focused lifecycle, hosted, deferred-owner, hostile-resume, and approval tests passed. - Ruff format/check and package-local Pyright passed. - git diff --check passed. Notes for next iteration: - Executing-without-outcome recovery remains for the indeterminate execution-window issue. - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. - Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed. * Represent approval execution uncertainty Key decisions: - Distinguish reserved claims from execution windows that may have started an external side effect. - Recover non-idempotent execution failures as indeterminate and reject identical retries without another invocation. - Permit claim release only under an explicit safe policy and execution retry only with a predeclared idempotency key shared by local and forwarded owners. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_endpoint.py Verification: - 972 AG-UI tests passed with 92% line coverage and 89% package branch coverage. - 23 focused lifecycle, duplicate-resume, hosted-owner, and public settlement-window tests passed. - Package-local Ruff and Pyright passed; git diff --check passed. Notes for next iteration: - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. - Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed. * Reconcile approval snapshots with lifecycle state Key decisions: - Keep Approval State authoritative and emit typed snapshot reconciliation keyed by logical occurrence identity. - Retire settled, rejected, cancelled, expired, indeterminate, and missing controls while preserving nonterminal authority. - Reconcile stale snapshots before hydration or resume, and retain lifecycle deduplication when snapshot saves fail. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/agent_framework_ag_ui/_snapshot_session.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_endpoint.py Verification: - 975 AG-UI tests passed with 92% package coverage and 89% approval lifecycle coverage. - Package-local Ruff and Pyright passed; git diff --check passed. Notes for next iteration: - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. - Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed. * Bound process-local approval lifecycle state Key decisions: - Protect pending, claimed, executing, and indeterminate occurrences from eviction while retaining terminal outcomes for a configurable 15-minute process-local deduplication window. - Serialize complete approval batches by logical occurrence locks so aliases share atomic decisions and independent batches can progress concurrently. - Fail capacity, claim, and settlement conflicts explicitly, and emit redacted structured lifecycle telemetry without tool names, arguments, or approval payloads. - Remove legacy LRU eviction paths so active Approval State and middleware state are never silently discarded. Files changed: - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_approval_state.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_approval_state.py Verification: - 982 AG-UI tests passed with 92% package coverage and 91% approval lifecycle coverage. - 34 focused lifecycle and storage tests passed with RuntimeWarning and DeprecationWarning treated as errors. - Package-local Ruff and Pyright passed; git diff --check passed. Notes for next iteration: - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. - Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed. * Complete approval lifecycle cutover Key decisions: - Make ApprovalLifecycle the sole owner of trusted aliases, occurrence metadata, authority transitions, and retained outcomes. - Remove the parallel mutable pending-approval registry and route local, hosted, deferred, cancellation, replay, and snapshot reconciliation through lifecycle occurrences. - Encapsulate middleware Approval State behind copy-isolated store methods while keeping AG-UI protocol normalization and event projection in the runner. Files changed: - packages/ag-ui/AGENTS.md - packages/ag-ui/agent_framework_ag_ui/_agent.py - packages/ag-ui/agent_framework_ag_ui/_agent_run.py - packages/ag-ui/agent_framework_ag_ui/_approval_lifecycle.py - packages/ag-ui/agent_framework_ag_ui/_approval_state.py - packages/ag-ui/tests/ag_ui/test_agent_wrapper_comprehensive.py - packages/ag-ui/tests/ag_ui/test_approval_lifecycle.py - packages/ag-ui/tests/ag_ui/test_approval_result_event.py - packages/ag-ui/tests/ag_ui/test_approval_state.py - packages/ag-ui/tests/ag_ui/test_endpoint.py - packages/ag-ui/tests/ag_ui/test_run.py Verification: - 964 package-local AG-UI tests passed with 92% coverage and 90% approval lifecycle coverage. - 85 warning-strict focused approval tests passed. - Package-local Ruff and Pyright passed; git diff --check passed. Notes for next iteration: - The function-calling-loop scenario mapping remains inaccessible under the organization content-exclusion policy. - Workspace Poe fan-out remains blocked by the pre-existing missing packages/durabletask/pyproject.toml; equivalent package-local checks passed. * Align AG-UI approval resumes with protocol * Align workflow approvals with AG-UI resumes * Address AG-UI approval review findings * fix AG-UI test typing checks * fix AG-UI approval retention and cancellation retries |
||
|
|
5fafa18569 | Python: track agent-hooks feature usage (#7558) | ||
|
|
ee27065359 |
Python: Update agentserver to x.1.0b1 (#7621)
* Update agentserver to 2.1.0 * Update agentserver responses and invocations to x.1.0b1 * Pass platform context to state store provider * Pass user id * Correct requirements.txt * Fix unit tests * Fix unit tests |
||
|
|
9645d33cde |
Python: Fix Cosmos memory provider calling renamed add_cosmos toolkit API (#7635)
* Python: Fix Cosmos memory provider calling renamed add_cosmos toolkit API The Agent Memory Toolkit renamed AsyncCosmosMemoryClient.add_cosmos to upsert_memory with an identical signature. The provider declares azure-cosmos-agent-memory>=0.2.0b3 with no upper bound, so a resolved install can expose either name. after_run swallows write errors and only logs a warning, so on a post-rename toolkit the agent turn still looks successful while long-term memory silently stops receiving turns. Resolve the write method once per after_run, preferring upsert_memory and falling back to add_cosmos, so both ends of the declared range keep working. Same treatment for the emulator test's direct seed call. Fixes #7633 * Ponytail comment erased Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> * Clarify TODO comment regarding memory method rename Updated TODO comment to include author and clarify context , to resolve linting error --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
9a06fa3f42 |
Python: fix(python): add release_session API to prevent BackgroundAgentsProvider memory leaks (#7450)
* fix: add release_session API to prevent BackgroundAgentsProvider memory leaks * fix: address Copilot review comments on release_session * fix(harness): make background agent session release race-safe and bounded * fix (harness): address release_session and review feedback |
||
|
|
e926ad2859 |
Python: fix streaming transcript duplication with message injection and per-service-call persistence (#7605)
* Fix ordering issue when streaming with content injection and per-service-call persistence * Update spec * Address PR comment * revert uv.lock changes |
||
|
|
7cfa905486 |
Python: scope under-specified approve-for-session permission decisions (#7607)
* Python: scope under-specified approve-for-session permission decisions
PermissionDecisionApproveForSession carries an optional `approval` (tool
prompts) and an optional `domain` (URL prompts), so it can be constructed
with neither. A bare PermissionDecisionApproveForSession() serializes to
{"kind": "approve-for-session"}, which the Copilot CLI cannot interpret: it
dereferences the absent approval and crashes the CLI process with "Cannot
read properties of undefined (reading 'commandIdentifiers')", taking the
whole run down rather than failing a single tool call.
Wrap the resolved permission handler so such decisions are scoped using the
request that triggered them: shell prompts become an approval for that
prompt's command identifiers, MCP prompts an approval for that server and
tool, URL prompts an approval for that URL's domain, and so on.
The decision is only ever narrowed, never widened. When the prompt reports
can_offer_session_approval=False, or the request kind has no session-scoped
approval (such as a hook prompt), the decision is downgraded to a single-use
approval and a warning is logged. Decisions that already specify a scope are
forwarded unchanged, and handler exceptions still propagate so the SDK's
deny-on-error behavior is preserved.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e
* Fix test-suite type-checker errors for permission-decision normalizer
The permission-handler wrapper returned PermissionHandlerType (the sync-or-async
union), so awaiting its result in tests was rejected by the stricter CI type
checkers (pyrefly, ty, zuban). Give the wrapper a dedicated
AsyncPermissionHandlerType return type, and narrow the awaited result with an
isinstance assert before accessing its scope in the async-handler test.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e
* Add regression tests for extension permission approval normalization
Cover the two previously-untested branches of _derive_session_approval:
extension-management preserves the request operation, and
extension-permission-access preserves the extension name. Both assert the
serialized approval payload as well.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e
* Scope URL session approvals only for parser-unambiguous URLs
The URL branch derived the persisted domain with Python's urlparse, but the
Copilot CLI parses URLs with WHATWG semantics. The two disagree on crafted
authorities -- e.g. a backslash before the '@' in
'https://example.com<backslash>@evil.com' resolves to example.com under the CLI
but evil.com under urlparse -- so trusting urlparse could persist a session-wide
approval for an unrelated, attacker-chosen domain, widening authorization.
Add _derive_url_session_domain, which returns a domain only when the URL
contains none of the characters WHATWG and urlparse handle differently
(backslash, tab, newline, carriage return); any ambiguity (or a URL with no
host) narrows the decision to a single-use PermissionDecisionApproveOnce.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot-Session: 1b45752e-b602-4117-8304-3c8a8b877e3e
|
||
|
|
3221011427 | fix(core): warn when advertised MCP archives are rejected (#7622) | ||
|
|
3aadac53c8 |
Python: fix(redis): honour a max_messages retention limit of zero (#7470)
* Python: fix(redis): honour a max_messages retention limit of zero RedisHistoryProvider documents None as the sentinel for unlimited storage, so max_messages=0 must retain nothing. It retained everything: trimming to -max_messages emits LTRIM key 0 -1, which is Redis's "keep the whole list", and the count > max_messages guard is true for any non-empty list, so the trim ran on every save and did nothing. Negative values were worse than a no-op. max_messages=-5 emitted LTRIM key 5 -1, deleting the five oldest messages on every save while the list still grew without bound. Handle a limit of zero by deleting the key, which is what clear() in this class already does, and reject negative values in __init__ alongside the three ValueErrors it already raises for invalid configuration. None and positive limits are unchanged. * Python: never write the payload when Redis retention is disabled Addresses the automated review on #7470. With max_messages=0 the previous change still RPUSHed every message and deleted the key afterwards, so the payload reached Redis - and any AOF or replica stream - before being removed, and was briefly visible to other readers. Short-circuit instead: drop any existing history and return before serializing, so nothing is written at all. Also documents the new ValueError in the Raises: section, and asserts in the test that the pipeline is never used. * Python: leave stored history alone when Redis retention is disabled max_messages=0 deleted the session key. _redis_key omits source_id, so two providers with the default prefix share {key_prefix}:{session_id}, and the after-run pass persists in reverse provider order - a zero-retention provider listed first would drop a co-located provider's just-written history on every turn. Return before serializing instead: no payload reaches Redis, an AOF or a replica, and stored history is left as it is. Removing stored history is what clear() is for. --------- Co-authored-by: Chinmay V <203952148+chinmayv095@users.noreply.github.com> |
||
|
|
35c6b880f7 |
Python: Preserve Mistral prompt-cache usage details (#7597)
* Fix Mistral cached token usage Map prompt cache hits from Mistral chat usage into the standard usage details. Add regression coverage for regular and streaming responses. * Validate Mistral cached token usage * fix(mistral): satisfy strict cached token typing Narrow prompt token details before reading cached_tokens so the Mistral package passes strict Pyright without changing runtime validation.\n\nAddresses https://github.com/microsoft/agent-framework/pull/7597#discussion_r3750712320 |
||
|
|
3a5d00be54 |
Python: add checkpointing support to AgentFrameworkWorkflow.run() in agent-framework-ag-ui (#6646)
* Python: add checkpointing support to AgentFrameworkWorkflow.run() in ag-ui The ag-ui AgentFrameworkWorkflow.run() previously accepted only a RunAgentInput payload and exposed no way to use the core workflow's checkpointing/state-persistence, unlike the core agent-framework workflow implementations. This left ag-ui workflows without resumable execution. Add optional checkpoint_storage and checkpoint_id keyword arguments to run(), threaded through run_workflow_stream() into the core Workflow.run(). This delegates to the existing core capability instead of reinventing it and keeps the public surface consistent with Workflow.run(): - checkpoint_storage enables checkpoint creation at each superstep boundary. - checkpoint_id resumes a run from a persisted checkpoint; incoming messages are forwarded only as request-info responses (never as a new start-executor message) to honor the core's message/checkpoint_id mutual exclusivity, and responses + checkpoint_id performs a restore-then-send in one call. Both can also be supplied via the input_data keys __ag_ui_checkpoint_storage and __ag_ui_checkpoint_id so the FastAPI endpoint (which calls run(input_data) positionally) can opt in without changing its call site; explicit keyword arguments take precedence. Checkpoint resume bypasses the AG-UI thread snapshot hydration early-returns so it always reaches the core restore path. Backward compatible: run(input_data) keeps working unchanged, and the non-checkpoint path still calls run_workflow_stream(input_data, workflow) with its original two-argument convention. Adds focused tests covering checkpoint creation, resume-from-checkpoint, input-data-keyed params, and the unchanged default path. Fixes #6632. * Import Executor from the public agent_framework API in ag-ui workflow test * Fix ag-ui checkpoint resume: preserve thread snapshot, coerce resume responses; fix CI lint/typing A checkpoint-only resume no longer clobbers the stored AG-UI thread snapshot: the snapshot builder is seeded with the prior stored history so the saved snapshot keeps the earlier replayable transcript plus the newly produced output. Resume responses are now coerced against the post-restore pending requests on a checkpoint restore, so a JSON function_approval_response resumes through AG-UI after a cold restore instead of failing with a response-type mismatch. Also update the test-double workflow run() overrides to match the new keyword-only parent signature and re-sort the workflow test imports so ruff and the typing checkers pass. * Coerce ag-ui resume responses without a second checkpoint restore Reading pending request_info events for resume-response coercion previously restored the checkpoint into the live workflow, which invoked every executor's on_checkpoint_restore hook. workflow.run(checkpoint_id=...) then restored again, running those hooks a second time. Custom restore hooks are not required to be idempotent, so this could duplicate restoration work or break workflows that expect exactly one restore per resume. Load the persisted WorkflowCheckpoint directly from storage (runtime override or the workflow's build-time context storage) and read its pending_request_info_events instead. This exposes the same post-restore pending set for the resume contract and response coercion without mutating workflow state or running any restore hook, leaving workflow.run(checkpoint_id=...) as the single restore per resume. Add a regression test asserting on_checkpoint_restore runs exactly once on a checkpointed ag-ui resume. * Python: rework AG-UI workflow checkpointing onto public configuration surfaces Checkpoint storage is now configured on AgentFrameworkWorkflow (or the FastAPI endpoint) instead of being smuggled through input_data keys, and a run resumes by supplying its checkpoint id in the AG-UI forwarded props. With storage always in hand, resume-response coercion reads the pending request set straight from the persisted checkpoint via the public CheckpointStorage.load(), replacing the private runner-context fallback, and the core run call forwards checkpoint arguments directly, relying on core validation for conflicting parameters. Requesting a resume without configured storage now fails with a clear error. * Assign endpoint checkpoint storage in a single place The raw-workflow branch assigned checkpoint_storage at construction and the wiring block assigned it again. Construct the wrapper bare and let the wiring block own the assignment; the existing-storage guard keeps allowing a pre-wrapped runner without storage to adopt the endpoint's. --------- Co-authored-by: Evan Mattson <35585003+moonbox3@users.noreply.github.com> Co-authored-by: Evan Mattson <evan.mattson@microsoft.com> |
||
|
|
27d82b1567 |
Python: Ignore non-project workspace glob matches (#7509)
* Python: Ignore non-project workspace glob matches * test: collect workspace script tests * test: remove standalone script test --------- Co-authored-by: Luis Rodriguez <25299418+luisangelrod@users.noreply.github.com> Co-authored-by: Luis Rodriguez <luis.rodriguez@bcpos.com> |
||
|
|
5e52c6a718 |
Python: Fix ClaudeAgent reusing one SDK client across distinct fresh sessions (#7404)
* Python: Fix ClaudeAgent reusing one SDK client across distinct fresh sessions RawClaudeAgent kept a single mutable ClaudeSDKClient on the agent instance and reused it across distinct fresh AgentSession objects, because a fresh session passes session_id=None and the old reuse check treated that as "keep the current client". Two independent fresh sessions on one shared agent instance therefore shared a single provider conversation, so the second session continued the first session's conversation. Treat a fresh (None) continuation id as always requiring a new client, so an unbound session never inherits an existing provider conversation. Legitimate continuity is preserved: once a session runs, its service_session_id is written back, so later runs pass a real id and resume correctly. Guard client selection/creation with an asyncio.Lock so concurrent runs cannot race between the check and the client assignment. Add regression tests asserting two fresh sessions produce two clients and that an explicit continuation id still resumes the existing client. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af * Python: Bind Claude SDK client ownership to each run Replace the single mutable ClaudeSDKClient stored on the agent with a per-run client. Because a ClaudeSDKClient represents exactly one provider conversation, sharing one across distinct sessions collapsed them onto the same conversation and, for concurrent runs, let a fresh session disconnect a client another run was still streaming from. _acquire_client now returns a per-run client (owned) that resumes the framework session's provider conversation when one exists, and _get_stream releases it in a finally once the run completes. An injected client is reused verbatim and left to the caller. The streaming loop moves into _stream_run so the client is a local per-run value rather than shared agent state, which keeps distinct sessions isolated even under concurrency. Continuity is preserved: a session's service_session_id is written back after each run and forwarded as the resume id on subsequent runs. Replace the client-lifecycle tests with per-run ownership and end-to-end isolation tests (two fresh sessions get two separate clients, each disconnected). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af * Python: Close remaining Claude session-isolation gaps Address three shared-state gaps in the Claude adapter surfaced in review: - Run-scope structured output: carry the run's structured_output through a per-run state holder and a per-run finalizer instead of storing it on the agent, so a concurrent run cannot overwrite another run's value before its finalizer reads it. - Bind an injected client to one session: an injected ClaudeSDKClient is a single Claude conversation, so bind it to the first session that uses it and raise AgentInvalidRequestException if a different session tries to reuse it. A no-session run reuses the bound session so multi-turn continuity still works; multi-session callers must omit client= or use one agent per session. - Serialize the injected-client path with an asyncio.Lock so concurrent runs cannot race its connect or interleave queries on the one shared client. Owned per-run clients stay lock-free. Update and extend the tests accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af * Python: Bind injected Claude client on provider conversation identity Compare an injected client's binding on the session's service_session_id (the Claude conversation identity) rather than the framework-local session_id, falling back to session_id only when the incoming session has no provider id yet. A reconstructed session from get_session(service_session_id=...) carries a fresh session_id but the same provider conversation, so it now continues the bound conversation instead of raising. Sessions targeting a different conversation are still rejected. Add regression tests for reconstructed-same-conversation continuation and different-conversation rejection. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 598a9fe1-28c5-4db1-88fd-e14acd9340af |
||
|
|
30996433ac |
Python: Restore Gemini thought_signature on approval replays (#7546)
Gemini 3.x rejects a request whose functionCall parts lack a thought_signature. The signature was carried as base64 protected_data on a text_reasoning content and re-attached by adjacency, which requires the carrier to immediately precede its call. An approval round trip replays the call with no carrier at all, so the next turn failed with a 400. Track signatures in a bounded per-client call_id map populated at parse time from the resolved call_id, and backfill only when the emitted part has no signature. Also stop clearing the held signature on contents that emit no Part, so an approval response or an unsigned thought summary between the carrier and its call no longer drops it. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: dd0909cd-c7c3-42cb-aef1-1e9a3e64d917 |
||
|
|
e85b3c8ba8 |
Python: Fix FHA session ID translation (#7608)
* Fix FHA session ID traslation * Fix tests * Address comments and fix tests * Fix typing * Show how to use user created sessions * Update README |
||
|
|
db979b616a |
Python: Improve Json parsing for declarative workflow (#7550)
* Json parsing improvement * Fix PR comments * Address PR comments. |
||
|
|
d0a4165f17 |
[BREAKING] Python: Migrate FHA to responses==2.0.0b1 and add Foundry state store (#7533)
* Migrate FHA to responses==2.0.0b1 and add Foundry state store * Fix session id error * Fix tests * Improve tests * Fix copilot comments * Address comments * Revert sample changes * Address comments * Add ContextScopedStoreProvider * Fix type check * Fix type check * Export ContextScopedStoreProvider |
||
|
|
4357ff5742 |
Bump postcss (#7529)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.22 to 8.5.25. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.5.22...8.5.25) --- updated-dependencies: - dependency-name: postcss dependency-version: 8.5.25 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
221f4b6df1 |
Bump postcss from 8.5.15 to 8.5.25 in /python/packages/devui/frontend (#7493)
Bumps [postcss](https://github.com/postcss/postcss) from 8.5.15 to 8.5.25. - [Release notes](https://github.com/postcss/postcss/releases) - [Changelog](https://github.com/postcss/postcss/blob/main/CHANGELOG.md) - [Commits](https://github.com/postcss/postcss/compare/8.5.15...8.5.25) --- updated-dependencies: - dependency-name: postcss dependency-version: 8.5.25 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
a9f7b2b788 |
Bump pyrefly from 1.1.1 to 1.2.0 in /python (#7541)
Bumps [pyrefly](https://github.com/facebook/pyrefly) from 1.1.1 to 1.2.0. - [Release notes](https://github.com/facebook/pyrefly/releases) - [Commits](https://github.com/facebook/pyrefly/compare/1.1.1...1.2.0) --- updated-dependencies: - dependency-name: pyrefly dependency-version: 1.2.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
adcc3de654 |
Bump js-yaml from 4.3.0 to 4.3.1 in /python/packages/devui/frontend (#7554)
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 4.3.0 to 4.3.1. - [Changelog](https://github.com/nodeca/js-yaml/blob/4.3.1/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/4.3.0...4.3.1) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 4.3.1 dependency-type: indirect ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
034f5fa119 |
Bump zuban from 0.9.0 to 0.9.1 in /python (#7545)
Bumps [zuban](https://github.com/zubanls/zubanls-python) from 0.9.0 to 0.9.1. - [Release notes](https://github.com/zubanls/zubanls-python/releases) - [Commits](https://github.com/zubanls/zubanls-python/compare/v0.9.0...v0.9.1) --- updated-dependencies: - dependency-name: zuban dependency-version: 0.9.1 dependency-type: direct:development update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> |
||
|
|
48e547506b |
Python: Make encrypted reasoning opt-in for Foundry chat (#7536)
* Python: Make Foundry encrypted reasoning opt-in * Python: Opt hosted replay test into encrypted reasoning |
||
|
|
4b1afd9052 |
Python: surface Gemini thought summaries as reasoning content (#7488)
Gemini thought-summary parts (part.thought=True) were dropped in _parse_parts, so reasoning never reached ChatResponse.contents. Emit them as text_reasoning content instead, matching OpenAIResponsesClient. Round-trip is safe: _convert_message_contents never re-emits reasoning text as a Part. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: b8aa906e-1408-40c1-9a45-6deb40dc36f8 |
||
|
|
45c515b8a7 |
Python: fix CopilotStudioAgent LineTooLong on large activities (#7417)
* Python: fix CopilotStudioAgent LineTooLong on large activities Bump microsoft-agents-copilotstudio-client to >=1.2.0,<2 and forward a configurable read_bufsize (default 1 MiB) to the underlying aiohttp ClientSession via ConnectionSettings.client_session_settings. Copilot Studio streams each activity as a single SSE data line, so activities larger than aiohttp's 512 KB per-line limit previously raised aiohttp.http_exceptions.LineTooLong. Adds a client_session_settings parameter to CopilotStudioAgent and unit tests covering the default, override, and partial-settings cases. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2766dc09-ab5f-4adc-8627-98361d7ccef0 * Python: apply read_bufsize default to supplied CopilotStudio settings Address review feedback on the LineTooLong fix: when a user supplies their own ConnectionSettings but no client, inject the read_bufsize default so activities larger than aiohttp's 512 KB per-line limit still stream. Document configuring read_bufsize on the explicit pre-built-client path in the package and sample READMEs and the explicit-settings sample. Add unit tests covering the supplied-settings path. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2766dc09-ab5f-4adc-8627-98361d7ccef0 --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2766dc09-ab5f-4adc-8627-98361d7ccef0 |
||
|
|
b2a2fcbd87 |
Python: Add response/request customization hooks to OpenAIChatCompletionClient (#7028)
* Python: Fix reasoning content parsing in OpenAIChatCompletionClient Fix two issues with reasoning content handling in the Chat Completions client: 1. (#6979) reasoning_details plaintext buried as encrypted data: The client dumped the entire reasoning_details array into Content.protected_data without setting Content.text, causing AG-UI to emit ReasoningEncryptedValueEvent instead of visible ReasoningMessageContentEvent for plaintext reasoning providers (e.g. OpenRouter). Now extracts readable text from reasoning_details entries into Content.text while preserving protected_data for round-trip fidelity. 2. (#6978) Mistral list content causes crash: Mistral reasoning models return content as a list of typed chunks ([{"type": "thinking", ...}, {"type": "text", ...}]) instead of a plain string. _parse_text_from_openai assumed content was always a string, causing a Pydantic ValidationError downstream. Now detects list content and parses thinking chunks as Content.from_text_reasoning and text chunks as Content.from_text. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix pyright strict-mode type errors and handle content-as-string shape - Use cast() for proper type narrowing in _extract_reasoning_text and _parse_chunked_content to satisfy pyright strict mode - Handle {"content": "..."} string shape in _extract_reasoning_text (addresses review comment about missing format coverage) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix mypy errors: cast list content to Any in tests model_construct bypasses Pydantic runtime validation but mypy still checks declared types. Use cast(Any, ...) for the list content args. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review comments: summary field, reasoning field, and round-trip - Add 'summary' field extraction in _extract_reasoning_text for reasoning.summary entries from OpenRouter - Handle message.reasoning and message.reasoning_content top-level fields (plaintext reasoning without reasoning_details) in both streaming and non-streaming paths - reasoning_details takes priority when both fields are present - Preserve original Mistral chunk list in additional_properties ('_source_content_list') so _prepare_message_for_openai can reconstruct the structured list content for multi-turn reasoning - Add 5 new tests covering all new behaviors Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix ruff used-dummy-variable: rename _skip_structured_siblings Remove leading underscore from _skip_structured_siblings variable since it is accessed (not a dummy variable). Ruff's used-dummy-variable rule flags variables with leading underscores that are read. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Fix missing newline at end of test file Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Address review: type-agnostic chunk round-trip and reasoning field echo-back - Honor the _source_content_list marker regardless of the first emitted content's type by handling it before the type match, so a chunk list beginning with a text chunk still round-trips as one structured message (addresses github-actions review comment on results[0]). - Tag every chunked-content item with a shared _structured_content_group id and skip only exact group siblings during serialization, instead of suppressing all later text/reasoning content. - Record provenance of top-level reasoning/reasoning_content fields in _reasoning_source_field and echo the value back under the same key on the next request, which providers such as vLLM require (addresses Kimahriman review comment). Replaces the prior behavior that replayed surfaced reasoning as visible answer text. - Factor the duplicated reasoning parsing into _parse_reasoning_content. - Add tests for provenance capture, reasoning/reasoning_content round-trip, reasoning-only messages, text-first chunk round-trip, and unrelated sibling preservation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5 * Replace provider-specific reasoning logic with configurable parse/prepare hooks Following review feedback (#7028), keep OpenAIChatCompletionClient free of provider-specific quirks for 'almost OpenAI-compatible' endpoints. Instead of branching in core for OpenRouter/vLLM/Mistral, expose two optional callables so callers adapt the client themselves: - response_parser (OpenAIChatResponseContentsParser): post-processes the Content list parsed from each response choice/streaming delta, to surface non-standard fields (e.g. reasoning/reasoning_content/reasoning_details) for display. - message_preparer (OpenAIChatMessagePreparer): post-processes the outgoing request message dicts built from each framework Message, to echo provider-specific fields back on later turns (e.g. vLLM reasoning) for multi-turn continuity. Both default to None (no-op; byte-identical stock OpenAI behavior). This reverts the provider-specific reasoning/chunked-content parsing and round-trip markers previously added to core; Mistral chunked content is now handled by agent-framework-mistral. - Add the two callables to RawOpenAIChatCompletionClient / OpenAIChatCompletionClient constructors and invoke them at the parse and prepare seams. - Export the type aliases from the package and the core lazy openai namespace (+ .pyi). - Replace the removed-behavior tests with tests for the two hooks. - Document the hooks in packages/openai/AGENTS.md. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5 * Skip non-string content in default text parsing Structured list content (e.g. Mistral reasoning models returning content as a list of chunks) was wrapped verbatim into a text Content, producing a malformed Content whose text is a list that crashes downstream (issue #6978). Default text parsing now skips non-string content so a configured response_parser receives a clean slate to expand it. Applies to both streaming and non-streaming paths. Add tests for the skip and for a response_parser expanding chunked content. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5 * Address review: hook signature, per-role preparer, robust round-trip - response_parser now receives the already-selected ChatCompletionMessage / ChoiceDelta instead of Choice | ChunkChoice, so callers no longer duplicate the streaming dispatch (removes the Any/hasattr pattern from tests). The client owns the dispatch; parsers read provider fields directly. - message_preparer now runs once per Message for every role: the build logic moved to _build_openai_messages and the hook is applied at a single exit point in _prepare_message_for_openai, so system/developer messages no longer bypass it. - Round-trip example/test now correlates surfaced reasoning via an additional_properties marker on message.contents with bounded, order-aware, one-to-one dict removal, instead of fragile request-string matching. Adds a test proving an answer whose text equals the reasoning text is no longer dropped. - Update packages/openai/AGENTS.md for the new parser signature and guidance. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5 --------- Co-authored-by: Copilot <copilot@github.com> Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2f3c0308-51bf-4b66-8b53-87a8546743f5 |
||
|
|
7302d0bf23 |
Python: agent-hooks interception contract as a first-class experimental core feature (#7515)
* feat(python): add agent-hooks middleware as experimental core feature Implement the AGENT-HOOKS-0.1 interception contract as a first-class experimental feature in agent_framework core. - Single public factory agent_hooks_middleware() returning a private agent/chat/function middleware trio (one object per middleware category); partial or stacked installs fail closed with loud errors. - All eight interception points: input/output at the agent seam, pre/post_model_call at the chat seam, pre/post_tool_call at the function seam, agent_startup/agent_shutdown bracketing each run. - Fail-closed enforcement throughout: transforms write back into the native contexts (messages, arguments, results) or raise; content is preserved as Content objects; MiddlewareTermination short-circuits are guarded at every seam; enforcement-layer failures halt the run; interceptor crashes surface as host_error denies. - Streaming is fully buffered per spec buffered_output semantics: no update egresses before the post_model_call/output verdicts; a deny at pull time releases zero updates; run state stays active across lazy pulls with cleanup on every exit path. - Session scoping: per-run by default (startup/shutdown bracket each run) or host-owned via emitter/builder parameters for one session spanning multiple runs. - agent-hooks-sdk is an opt-in agent-hooks extra (not in all), lazy-imported per the _mcp.py pattern; core imports cleanly without it and the factory raises a clear ModuleNotFoundError. - ExperimentalFeature.AGENT_HOOKS + @experimental decorator, lazy root export, typing surface, PACKAGE_STATUS.md entry. - 55 tests built on real Agent/mock-client flows covering deny-before- execution, transform write-back, rich-content preservation, complete streaming ordering, error cleanup, concurrency isolation, nested agents, and importability without the optional SDK. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * style(python): unquote ResponseStream annotation per pyupgrade The pre-commit pyupgrade hook rewrites the quoted forward reference; ResponseStream is imported at runtime in this module, so the quotes were unnecessary. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * refactor(python): address agent-hooks review feedback Reworks the agent-hooks feature per PR review: - Verdicts now precede durability: a run-scoped persistence gate (_sessions.py) defers per-service-call history persistence and after-run provider work until the covering post_model_call/output verdict permits; denied content never persists, transforms persist post-write-back. Unhooked runs are unchanged (verified against an instrumented baseline). - ResponseStream.buffered_and_gated: a buffered-gate combinator that applies the run's pending stream hooks before the gate, then seals the stream, so no middleware can rewrite egress after the output verdict. Replaces the hand-rolled replay iterator. - MiddlewareBundle (public, _middleware.py): the factory returns an indivisible bundle categorize_middleware splits, making partial installs impossible by construction; members are validated at construction. Bare (non-sequence) middleware at agent construction is now normalized instead of silently dropped, and unrecognized middleware logs a warning instead of vanishing. - Factory split and rename: create_agent_hooks_middleware (per-run sessions) and create_agent_hooks_middleware_from_emitter (host-owned); the sentinel parameter-diffing is gone. - Wire conversions live in per-point codec classes owning to_wire and write_back. Fixes in that code: tool-call name transforms apply or raise; non-object args transforms raise; argument write-back merges only changed keys (original values, including bytes, preserved by identity); message-list write-back matches by identity, not index. - function_approval_request objects on the normal return path pass through un-emitted, preserving the human approval pause. - Hosted (service-executed) tool calls surface in the post_model_call content projection; the tool-seam limitation is documented. - Import probe covers the full SDK surface and re-raises as missing-extra only for the agent_hooks module; module logger added; _json_safe replaced by make_json_safe (which gained bytes support); tools_registered uses normalize_tools; dependency-pyright analyzes the module again via the test dependency-group. - Tests: 75 in the feature suite (persistence gating, stream-hook sealing, approval passthrough, codec units, bundle validation, bare-bundle installs), full core suite green. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * refactor(python): second review round for agent-hooks Addresses the second review round on the agent-hooks feature: - Nested-run persistence ownership: RawAgent.run stamps a run identity over the run's dynamic extent (including streaming pulls and result hooks); the persistence gate binds to its owning run via an offer/adopt handshake keyed to the agent instance and accepts only its owner's persists — nested runs persist inline regardless of how they were started (tool calls, middleware, custom run loops). The tool-seam suspension remains for custom-loop sub-agents invoked as tools; the one residual case (custom loop nested in a custom loop off the tool path) is fail-closed and documented. Fixes a latent pre-existing re-deferral: flush() now drains with the gate context suspended, so a nested hooked run's permitted after-run persistence no longer re-defers into an enclosing gate. - as_tool stream_callback consumes the released (verdicted) stream; observers cannot see denied or pre-transform content. Both directions are regression-tested. - categorize_middleware gained supported_categories: a bundle member landing in a category a call site cannot install raises; bare middleware warns like _add_middleware. Wired at the chat-client sites and the provider seam. - ResponseStream.buffered_and_gated owns the re-derivation rule via a rederive callable (gates cannot choose released updates) and is marked experimental. - Wire codecs compare with bool-aware equality (Python == equates 1 == True, which made bool/number transforms look untouched and get dropped) and _ToolResultCodec.write_back owns the untouched-wire rule via the before value. - middleware parameters accept a bare middleware or bundle everywhere the runtime does (constructors, run overloads, as_agent, telemetry and harness layers, foundry); the bare-source rule has a single owner in categorize_middleware; bare middleware assigned to the attribute now executes (documented behavior change). - MiddlewareBundle is experimental and validates members; approval passthrough, typing-check fixes (ty ignores mypy-coded ignore comments), logging, and documentation updates per review. Test count: 85 feature tests plus 12 new this round across sessions, middleware, agents; full core suite green; typing checked under mypy, pyrefly, ty, zuban, and pyright. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * docs(python): drop previous-behavior notes from middleware docstrings Per review: docstrings describe current behavior only. The bare-middleware behavior change stays recorded in the PR description and commit history. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(python): gate ownership survives retrying middleware A retry or fallback middleware issuing a second call_next() gave the new attempt a fresh run identity that the persistence gate's first-bind-wins ownership rejected, so the retried attempt's history persisted inline before the output verdict — a denied response became durable again. The gate now accumulates every identity adopted through its own offer ticket: all attempts' persistence stays behind the one final verdict (deny drops all of it, allow flushes all of it). Accumulation over rebind-replace is deliberate: rebinding would flip an earlier attempt's still-running background work from deferred to inline, which is the fail-open direction. A foreign agent still cannot bind: tickets are minted only by the covered pipeline's final handler and adoption is instance-keyed. Also consolidates the bare-middleware-source rule into a single _as_middleware_list owner used by every interpretation site (the harness merge, BaseAgent.__init__, categorize_middleware, both client-kwargs merges, get_response, SessionContext.extend_middleware), including the str/bytes exclusion the stray copies missed. The constructor now stores a copy of the caller's sequence; assign to the middleware attribute for post-construction changes. Retry regression tests cover denied and allowed retried runs in both stream modes and fail with first-bind-wins restored. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(python): streaming seam runs pipeline descent inside the gate The streaming agent seam ran call_next() outside the persistence gate (only _consume entered it later), so a retry middleware that drained a successful attempt with get_final_response() and discarded it persisted that attempt's exchange before any verdict existed; a later deny dropped only the retry attempt's deferred work. The descent is now wrapped in the gate exactly like the non-streaming seam: attempt identities adopted during descent are accepted owners, so in-pipeline draining defers, deny drops every attempt, and a middleware that raises after draining strands the pending persists unexecuted. The bind_owner docstring now states the actual soundness invariant covering both bind sites: every bind comes from a run inside the covered pipeline. New tests cover drained-and-discarded attempts (deny and allow, both stream modes) and a sub-agent tool inside a drained attempt; the streaming deny variant fails with the gate wrap reverted. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> * fix(python): flush deferred persistence on streaming no-result termination With the pipeline descent now running inside the persistence gate, a middleware that drains a successful attempt and then terminates without a result left that attempt's deferred persistence stranded: the streaming no-result termination path raised before any flush, so history of exchanges that really happened and passed their own verdicts quietly vanished (streaming only; non-streaming already flushes before its re-raise). The path now flushes before re-raising the termination, with a state.halted guard first so an enforcement failure during the drained attempt still strands pending fail-closed and surfaces the halt, mirroring the non-streaming ordering exactly. The regression test covers both seams; the streaming variant fails without the fix. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> --------- Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com> |
||
|
|
422160eabe |
Python: Add windows junction detection for skills (#7507)
* Add windows junction detection for skills * Address PR comment |
||
|
|
5a1d96df67 |
Python: Separate mem0 storage and search scopes (#7531)
* Separate mem0 storage and search scopes * Apply suggestions from code review Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> |
||
|
|
594954700a |
Python: Fix AG-UI conversation correlation across runs (#7430)
* Add single agent AGUI sample * Fix AG-UI conversation correlation across runs * Address PR review and code quality feedback * Correlate AG-UI chat spans across runs --------- Co-authored-by: Tao Chen <taochen@microsoft.com> |
||
|
|
5f3ca8f93c |
Python: Fix AG-UI approval resume at the protocol boundary (#7480)
* Python: Fix Ollama approval resume message handling * Python: Reject empty Ollama approval resume payload * Python: Keep AG-UI approval controls out of provider input * Python: Do not trust pending AG-UI tool results |
||
|
|
4d3c7844d6 |
Python: Bound tool result compaction summaries (#7396)
* Python: bound tool result compaction summaries Keep ToolResultCompactionStrategy from re-inserting oversized tool result payloads through the synthetic summary message by bounding the generated digest text. Add regression coverage proving a large tool result is not embedded verbatim, keeps a bounded prefix, and marks truncation. * Python: keep excluded tool results out of compaction digests Build ToolResultCompactionStrategy digest content from messages still included in the group so a summary cannot restore payloads that an earlier compaction already excluded. Use the strategy cap constant in the large-payload regression and add coverage for already-excluded tool results. Validation: uv run pytest packages/core/tests/core/test_compaction.py -q -k 'tool_result_compaction'; uv run ruff check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run ruff format --check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run poe test -P core; uv run poe build -P core; env HOME=/tmp/sds-home XDG_CACHE_HOME=/tmp/sds-cache uv run poe test -A. * Python: align compaction digest review cleanup Align ToolResultCompactionStrategy's included-message filter with the module's existing EXCLUDED_KEY boolean semantics. Make the large-payload regression size scale from _SUMMARY_MAX_CHARS so it continues to exercise truncation if the digest cap changes. Validation: uv run pytest packages/core/tests/core/test_compaction.py -q -k 'tool_result_compaction'; uv run ruff check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run ruff format --check packages/core/agent_framework/_compaction.py packages/core/tests/core/test_compaction.py; uv run poe test -P core; uv run poe build -P core. * Python: collapse tool result digest scan --------- Co-authored-by: Eduard van Valkenburg <eavanvalkenburg@users.noreply.github.com> |
||
|
|
07511b80c9 |
Python: Prevent orphaned local approval responses (#7462)
* Python: Prevent orphaned local approval responses Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0efaca91-a0a7-46f4-9b81-022385607fe4 * Python: Clarify approval serialization boundaries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 0efaca91-a0a7-46f4-9b81-022385607fe4 --------- Copilot-Session: 0efaca91-a0a7-46f4-9b81-022385607fe4 |
||
|
|
e84b5a07c1 |
Python: fix LocalEvaluator reporting zero-check items as passed (#7399)
LocalEvaluator.evaluate initialized item_passed to True and only ever cleared it inside the loop over check results. With no checks configured the loop never runs, so an item with zero scores was recorded as passed: result_counts reported one pass, all_passed was True, and raise_for_status() did not raise. Initialize item_passed from bool(check_results) so an item with no evaluated checks fails closed. This matches the .NET contract in this repository, where AgentEvaluationResults.ItemPassed ends with 'return result.Metrics.Count > 0' and is pinned by LocalEvaluator_WithZeroChecks_ItemsHaveZeroMetricsAndFailAsync. Add a focused regression covering the counts, all_passed, the empty score list, and raise_for_status(). Update the LocalEvaluator class and evaluate() docstrings, which previously described the pass rule without the zero-check case. Fixes #7397 |