agent-framework-declarative 1.0.2__tar.gz → 1.0.4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/PKG-INFO +2 -2
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_declarative_base.py +14 -20
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_control_flow.py +1 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_mcp.py +1 -1
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_tools.py +6 -1
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_http_handler.py +63 -5
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/pyproject.toml +3 -3
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/LICENSE +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/README.md +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/__init__.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_feature_usage.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_loader.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_models.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/__init__.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_declarative_builder.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_errors.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_agents.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_basic.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_external_input.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_executors_http.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_factory.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_mcp_handler.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_powerfx_functions.py +0 -0
- {agent_framework_declarative-1.0.2 → agent_framework_declarative-1.0.4}/agent_framework_declarative/_workflows/_state.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-declarative
|
|
3
|
-
Version: 1.0.
|
|
3
|
+
Version: 1.0.4
|
|
4
4
|
Summary: Declarative specification support for Microsoft Agent Framework.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -15,7 +15,7 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
15
15
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
16
|
Classifier: Typing :: Typed
|
|
17
17
|
License-File: LICENSE
|
|
18
|
-
Requires-Dist: agent-framework-core>=1.
|
|
18
|
+
Requires-Dist: agent-framework-core>=1.15.0,<2
|
|
19
19
|
Requires-Dist: httpx>=0.27,<1
|
|
20
20
|
Requires-Dist: powerfx>=0.0.32,<0.0.35; python_version < '3.14'
|
|
21
21
|
Requires-Dist: pyyaml>=6.0,<7.0
|
|
@@ -1084,6 +1084,8 @@ class DeclarativeActionExecutor(Executor):
|
|
|
1084
1084
|
Follows .NET's DefaultTransform pattern - accepts any input type:
|
|
1085
1085
|
- dict/Mapping: Used directly as workflow.inputs
|
|
1086
1086
|
- str: Converted to {"input": value}
|
|
1087
|
+
- Message: Starts a fresh run (e.g. from DevUI), with text and ID
|
|
1088
|
+
surfaced through Inputs.input and System.LastMessage*.
|
|
1087
1089
|
- list[Message]: Treated as the agent-facing message contract
|
|
1088
1090
|
(e.g. from WorkflowAgent / as_agent()). The prior conversation
|
|
1089
1091
|
history is stored in ``Conversation.messages``/
|
|
@@ -1113,26 +1115,18 @@ class DeclarativeActionExecutor(Executor):
|
|
|
1113
1115
|
if isinstance(trigger, dict):
|
|
1114
1116
|
# Structured inputs - use directly
|
|
1115
1117
|
state.initialize(trigger) # type: ignore
|
|
1116
|
-
elif isinstance(trigger,
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
#
|
|
1123
|
-
#
|
|
1124
|
-
#
|
|
1125
|
-
#
|
|
1126
|
-
#
|
|
1127
|
-
|
|
1128
|
-
#
|
|
1129
|
-
# Continuation = declarative state already exists in the workflow's
|
|
1130
|
-
# shared state (either left over in-memory from a prior turn on
|
|
1131
|
-
# the same instance, or restored from a checkpoint just before
|
|
1132
|
-
# this run). In that case state.initialize() would wipe Local.*,
|
|
1133
|
-
# System.*, Conversation.* etc., destroying the cross-turn
|
|
1134
|
-
# context we're trying to preserve.
|
|
1135
|
-
is_continuation = state.is_initialized()
|
|
1118
|
+
elif isinstance(trigger, Message) or (
|
|
1119
|
+
isinstance(trigger, list) and all(isinstance(m, Message) for m in trigger) # pyright: ignore[reportUnknownVariableType]
|
|
1120
|
+
):
|
|
1121
|
+
# Message (e.g. from DevUI) or list[Message] (e.g. from WorkflowAgent / as_agent()).
|
|
1122
|
+
messages_list = [trigger] if isinstance(trigger, Message) else cast(list[Message], trigger)
|
|
1123
|
+
|
|
1124
|
+
# Preserve the existing list[Message] continuation contract used
|
|
1125
|
+
# by WorkflowAgent. A bare Message starts a fresh run: DevUI can
|
|
1126
|
+
# reuse this workflow instance across unrelated conversations.
|
|
1127
|
+
# State left on the instance is not evidence of session ownership.
|
|
1128
|
+
# Explicit checkpoint/HIL restores use the runner's resume path.
|
|
1129
|
+
is_continuation = not isinstance(trigger, Message) and state.is_initialized()
|
|
1136
1130
|
|
|
1137
1131
|
# Locate the trailing user message in the trigger.
|
|
1138
1132
|
last_user_index = -1
|
|
@@ -284,7 +284,7 @@ class InvokeMcpToolActionExecutor(DeclarativeActionExecutor):
|
|
|
284
284
|
output_messages_path = _get_output_path(self._action_def, "messages")
|
|
285
285
|
output_result_path = _get_output_path(self._action_def, "result")
|
|
286
286
|
|
|
287
|
-
if
|
|
287
|
+
if response.approved is not True:
|
|
288
288
|
logger.info(
|
|
289
289
|
"%s: MCP tool '%s' rejected: %s",
|
|
290
290
|
self.__class__.__name__,
|
|
@@ -82,6 +82,11 @@ class ToolApprovalResponse:
|
|
|
82
82
|
approved: bool
|
|
83
83
|
reason: str | None = None
|
|
84
84
|
|
|
85
|
+
def __post_init__(self) -> None:
|
|
86
|
+
"""Reject non-boolean approval decisions."""
|
|
87
|
+
if not isinstance(self.approved, bool):
|
|
88
|
+
raise TypeError("approved must be a bool.")
|
|
89
|
+
|
|
85
90
|
|
|
86
91
|
# ============================================================================
|
|
87
92
|
# Result Types
|
|
@@ -524,7 +529,7 @@ class BaseToolExecutor(DeclarativeActionExecutor):
|
|
|
524
529
|
messages_var, result_var, auto_send = self._get_output_config()
|
|
525
530
|
|
|
526
531
|
# Check if approved
|
|
527
|
-
if
|
|
532
|
+
if response.approved is not True:
|
|
528
533
|
logger.info(f"{self.__class__.__name__}: tool invocation rejected: {response.reason}")
|
|
529
534
|
|
|
530
535
|
# Store rejection status (don't raise error)
|
|
@@ -24,6 +24,7 @@ import asyncio
|
|
|
24
24
|
from collections.abc import Awaitable, Callable, Mapping
|
|
25
25
|
from dataclasses import dataclass, field
|
|
26
26
|
from typing import Any, Protocol, runtime_checkable
|
|
27
|
+
from urllib.parse import parse_qsl, quote, urlencode, urlsplit, urlunsplit
|
|
27
28
|
|
|
28
29
|
import httpx
|
|
29
30
|
|
|
@@ -173,16 +174,56 @@ class DefaultHttpRequestHandler:
|
|
|
173
174
|
# callers (not just the YAML executor) get sensible defaults.
|
|
174
175
|
headers["Content-Type"] = info.body_content_type or "text/plain"
|
|
175
176
|
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
177
|
+
# Compose the query as raw bytes rather than routing it through ``params=``.
|
|
178
|
+
# Anything that reaches ``httpx.QueryParams`` -- ``params=`` or the client's own
|
|
179
|
+
# ``AsyncClient.params`` merge -- is decoded and re-encoded. Measured against
|
|
180
|
+
# httpx 0.28.1, that rewrites ``%20`` in a value as ``+``, expands a bare
|
|
181
|
+
# ``download`` into ``download=``, and reorders interleaved duplicates
|
|
182
|
+
# (``x=1&y=2&x=3`` becomes ``x=1&x=3&y=2``), all of it even when nothing needed
|
|
183
|
+
# merging. Base64 and ``%3A`` round-trip unchanged, so a signature is not
|
|
184
|
+
# rewritten by itself, but any scheme that signs a value holding an encoded
|
|
185
|
+
# space is, and a server that distinguishes ``download`` from ``download=`` or
|
|
186
|
+
# reads repeated keys positionally sees a different request either way.
|
|
187
|
+
# Building the query here and writing it back over the request's ``raw_path``
|
|
188
|
+
# keeps the caller's bytes exactly as given.
|
|
189
|
+
raw = urlsplit(info.url)
|
|
190
|
+
|
|
191
|
+
# ``query_parameters`` append rather than replace, so a URL carrying
|
|
192
|
+
# ``filter=region&filter=status`` plus ``{"filter": "tenant"}`` sends all three,
|
|
193
|
+
# matching the .NET handler. Client-level params are defaults only: they apply
|
|
194
|
+
# for a key absent from both request-level sources.
|
|
195
|
+
explicit_pairs = [(key, value) for key, value in info.query_parameters.items() if key]
|
|
196
|
+
request_keys = {key for key, _ in parse_qsl(raw.query, keep_blank_values=True)}
|
|
197
|
+
request_keys.update(key for key, _ in explicit_pairs)
|
|
198
|
+
client_defaults = [(key, value) for key, value in client.params.multi_items() if key not in request_keys]
|
|
199
|
+
|
|
200
|
+
query_segments: list[str] = []
|
|
201
|
+
if raw.query:
|
|
202
|
+
query_segments.append(raw.query)
|
|
203
|
+
# ``quote_via=quote`` keeps spaces as ``%20`` instead of ``+``, so appended
|
|
204
|
+
# parameters are encoded the same way the preserved URL query is.
|
|
205
|
+
for pairs in (explicit_pairs, client_defaults):
|
|
206
|
+
if pairs:
|
|
207
|
+
query_segments.append(urlencode(pairs, quote_via=quote))
|
|
208
|
+
query = "&".join(segment for segment in query_segments if segment)
|
|
209
|
+
|
|
210
|
+
# Build without the query so the client's params merge has nothing to clobber --
|
|
211
|
+
# it drops the URL's query outright -- then overwrite the query httpx composed
|
|
212
|
+
# with ours. The fragment stays on the built URL; httpx does not send it.
|
|
213
|
+
request = client.build_request(
|
|
179
214
|
method=info.method,
|
|
180
|
-
url=
|
|
181
|
-
params=params,
|
|
215
|
+
url=urlunsplit((raw.scheme, raw.netloc, raw.path, "", raw.fragment)),
|
|
182
216
|
headers=headers or None,
|
|
183
217
|
content=content,
|
|
184
218
|
timeout=timeout,
|
|
185
219
|
)
|
|
220
|
+
# Reuse httpx's own path encoding and replace only the query part of ``raw_path``.
|
|
221
|
+
raw_path = request.url.raw_path.split(b"?", 1)[0]
|
|
222
|
+
if query:
|
|
223
|
+
raw_path += b"?" + _encode_query(query)
|
|
224
|
+
request.url = request.url.copy_with(raw_path=raw_path)
|
|
225
|
+
|
|
226
|
+
response = await client.send(request)
|
|
186
227
|
|
|
187
228
|
# Preserve multi-value headers (e.g. multiple Set-Cookie) as list[str].
|
|
188
229
|
# Normalize names to lowercase so lookups are consistent and case
|
|
@@ -231,6 +272,23 @@ class DefaultHttpRequestHandler:
|
|
|
231
272
|
await self.aclose()
|
|
232
273
|
|
|
233
274
|
|
|
275
|
+
#: Characters a query string may carry literally: RFC 3986 sub-delims plus ``:@/?``, the
|
|
276
|
+
#: square brackets many APIs use in key names, and ``%`` so escapes already in the
|
|
277
|
+
#: caller's URL are left alone instead of being double-encoded. Everything legal stays
|
|
278
|
+
#: as authored and only bytes that cannot appear in a URL get escaped.
|
|
279
|
+
_QUERY_SAFE = "!$&'()*+,;=:@/?%[]~"
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
def _encode_query(query: str) -> bytes:
|
|
283
|
+
"""Return *query* as URL-safe bytes without disturbing what is already valid.
|
|
284
|
+
|
|
285
|
+
A caller can hand us a URL whose query holds non-ASCII text -- ``?q=café`` -- which
|
|
286
|
+
cannot go on the wire as-is. Percent-encoding only the characters that need it keeps
|
|
287
|
+
an already-valid query byte-identical, which is the point of composing it by hand.
|
|
288
|
+
"""
|
|
289
|
+
return quote(query, safe=_QUERY_SAFE).encode("ascii")
|
|
290
|
+
|
|
291
|
+
|
|
234
292
|
def _has_header(headers: Mapping[str, str], name: str) -> bool:
|
|
235
293
|
"""Case-insensitive header presence check."""
|
|
236
294
|
needle = name.lower()
|
|
@@ -4,7 +4,7 @@ description = "Declarative specification support for Microsoft Agent Framework."
|
|
|
4
4
|
authors = [{ name = "Microsoft", email = "af-support@microsoft.com"}]
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.10"
|
|
7
|
-
version = "1.0.
|
|
7
|
+
version = "1.0.4"
|
|
8
8
|
license-files = ["LICENSE"]
|
|
9
9
|
urls.homepage = "https://aka.ms/agent-framework"
|
|
10
10
|
urls.source = "https://github.com/microsoft/agent-framework/tree/main/python"
|
|
@@ -22,7 +22,7 @@ classifiers = [
|
|
|
22
22
|
"Typing :: Typed",
|
|
23
23
|
]
|
|
24
24
|
dependencies = [
|
|
25
|
-
"agent-framework-core>=1.
|
|
25
|
+
"agent-framework-core>=1.15.0,<2",
|
|
26
26
|
"httpx>=0.27,<1",
|
|
27
27
|
"powerfx>=0.0.32,<0.0.35; python_version < '3.14'",
|
|
28
28
|
"pyyaml>=6.0,<7.0",
|
|
@@ -33,7 +33,7 @@ dev = [
|
|
|
33
33
|
]
|
|
34
34
|
|
|
35
35
|
[tool.uv]
|
|
36
|
-
prerelease = "if-necessary
|
|
36
|
+
prerelease = "if-necessary"
|
|
37
37
|
environments = [
|
|
38
38
|
"sys_platform == 'darwin'",
|
|
39
39
|
"sys_platform == 'linux'",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|