agent-framework-core 1.18.0__tar.gz → 1.19.0__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_core-1.18.0 → agent_framework_core-1.19.0}/PKG-INFO +18 -2
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/README.md +15 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/__init__.py +12 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/__init__.pyi +12 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_agents.py +23 -8
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_compaction.py +166 -17
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_mode.py +69 -17
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_tool_approval.py +65 -41
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_in_memory.py +2 -98
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_mcp.py +578 -72
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_middleware.py +52 -11
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_sessions.py +60 -6
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_skills.py +286 -238
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_telemetry.py +2 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_tools.py +1488 -202
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_types.py +104 -26
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_vectors.py +1244 -4
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_agent.py +12 -3
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_agent_executor.py +26 -36
- agent_framework_core-1.19.0/agent_framework/_workflows/_agent_utils.py +153 -0
- agent_framework_core-1.19.0/agent_framework/_workflows/_checkpoint.py +873 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_checkpoint_encoding.py +10 -3
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_const.py +8 -2
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_executor.py +8 -9
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_function_executor.py +7 -9
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_functional.py +34 -14
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_request_info_mixin.py +46 -7
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_runner.py +15 -16
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_typing_utils.py +24 -3
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow.py +178 -35
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow_executor.py +4 -1
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/azure/__init__.py +3 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/azure/__init__.pyi +4 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/exceptions.py +6 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/observability.py +115 -42
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/security.py +487 -114
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/pyproject.toml +4 -2
- agent_framework_core-1.18.0/agent_framework/_workflows/_agent_utils.py +0 -17
- agent_framework_core-1.18.0/agent_framework/_workflows/_checkpoint.py +0 -463
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/LICENSE +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_agent_hooks.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_clients.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_docstrings.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_evaluation.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_feature_stage.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_filesystem.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_agent.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_agent.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_background_agents.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_file_access.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_file_memory.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_loop.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_memory.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_todo.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_serialization.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_settings.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_vector_filters.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_conversation_history.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_edge.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_edge_runner.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_events.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_message_utils.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_model_utils.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_runner_context.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_state.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_validation.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_viz.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow_builder.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow_context.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/a2a/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/a2a/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ag_ui/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ag_ui/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/amazon/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/amazon/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/anthropic/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/anthropic/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/chatkit/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/chatkit/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/declarative/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/declarative/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/devui/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/devui/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/foundry/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/foundry/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/gemini/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/gemini/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/github/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/github/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/google/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/google/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/hyperlight/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/lab/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mem0/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mem0/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/microsoft/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/microsoft/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mistral/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mistral/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/monty/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/monty/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ollama/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ollama/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/openai/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/openai/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/orchestrations/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/orchestrations/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/py.typed +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/redis/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/redis/__init__.pyi +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/tools/__init__.py +0 -0
- {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/tools/__init__.pyi +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: agent-framework-core
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.19.0
|
|
4
4
|
Summary: Microsoft Agent Framework for building AI Agents with Python. This is the core package that has all the core abstractions and implementations.
|
|
5
5
|
Author-email: Microsoft <af-support@microsoft.com>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -21,6 +21,7 @@ Requires-Dist: typing-extensions>=4.15.0,<5
|
|
|
21
21
|
Requires-Dist: pydantic>=2,<3
|
|
22
22
|
Requires-Dist: python-dotenv>=1,<2
|
|
23
23
|
Requires-Dist: opentelemetry-api>=1.39.0,<2
|
|
24
|
+
Requires-Dist: pyyaml>=6.0,<7.0
|
|
24
25
|
Requires-Dist: mcp>=1.24.0,<2 ; extra == "all"
|
|
25
26
|
Requires-Dist: agent-framework-a2a ; extra == "all"
|
|
26
27
|
Requires-Dist: agent-framework-ag-ui ; extra == "all"
|
|
@@ -41,7 +42,7 @@ Requires-Dist: agent-framework-foundry-hosting ; extra == "all"
|
|
|
41
42
|
Requires-Dist: agent-framework-foundry-local ; extra == "all"
|
|
42
43
|
Requires-Dist: agent-framework-gemini ; extra == "all"
|
|
43
44
|
Requires-Dist: agent-framework-github-copilot ; extra == "all" and ( python_version >= '3.11')
|
|
44
|
-
Requires-Dist: agent-framework-hyperlight ; extra == "all" and ( (sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64'))
|
|
45
|
+
Requires-Dist: agent-framework-hyperlight ; extra == "all" and ( python_version < '3.15' and ((sys_platform == 'linux' and platform_machine == 'x86_64') or (sys_platform == 'win32' and platform_machine == 'AMD64')))
|
|
45
46
|
Requires-Dist: agent-framework-mem0 ; extra == "all"
|
|
46
47
|
Requires-Dist: agent-framework-mistral ; extra == "all"
|
|
47
48
|
Requires-Dist: agent-framework-monty ; extra == "all"
|
|
@@ -227,6 +228,21 @@ asyncio.run(main())
|
|
|
227
228
|
|
|
228
229
|
You can explore additional agent samples [here](https://github.com/microsoft/agent-framework/tree/main/python/samples/02-agents).
|
|
229
230
|
|
|
231
|
+
### MCP HTTP client ownership and cookies
|
|
232
|
+
|
|
233
|
+
**Breaking change:** HTTP clients created by `MCPStreamableHTTPTool` no longer persist
|
|
234
|
+
response cookies. This applies with or without a `header_provider`, including repeated
|
|
235
|
+
calls within the same MCP session. Connection reuse, timeout and redirect defaults,
|
|
236
|
+
and cleanup on close or reset are preserved. Explicit `Cookie` headers supplied through
|
|
237
|
+
`static_headers` or a `header_provider` are still supported.
|
|
238
|
+
|
|
239
|
+
Applications relying on cookie-based authentication, sessions, or load-balancer affinity
|
|
240
|
+
must supply an `httpx.AsyncClient` through `http_client=`. Supplied clients retain their
|
|
241
|
+
configuration and response-cookie handling and must be closed by the caller. Scope
|
|
242
|
+
cookie-bearing clients and MCP tool sessions to one authenticated principal; do not
|
|
243
|
+
share a user-specific cookie jar across users. Rejecting cookies does not partition
|
|
244
|
+
MCP protocol sessions or other server-side state between principals.
|
|
245
|
+
|
|
230
246
|
## 5. Multi-Agent Orchestration
|
|
231
247
|
|
|
232
248
|
Coordinate multiple agents to collaborate on complex tasks using orchestration patterns:
|
|
@@ -168,6 +168,21 @@ asyncio.run(main())
|
|
|
168
168
|
|
|
169
169
|
You can explore additional agent samples [here](https://github.com/microsoft/agent-framework/tree/main/python/samples/02-agents).
|
|
170
170
|
|
|
171
|
+
### MCP HTTP client ownership and cookies
|
|
172
|
+
|
|
173
|
+
**Breaking change:** HTTP clients created by `MCPStreamableHTTPTool` no longer persist
|
|
174
|
+
response cookies. This applies with or without a `header_provider`, including repeated
|
|
175
|
+
calls within the same MCP session. Connection reuse, timeout and redirect defaults,
|
|
176
|
+
and cleanup on close or reset are preserved. Explicit `Cookie` headers supplied through
|
|
177
|
+
`static_headers` or a `header_provider` are still supported.
|
|
178
|
+
|
|
179
|
+
Applications relying on cookie-based authentication, sessions, or load-balancer affinity
|
|
180
|
+
must supply an `httpx.AsyncClient` through `http_client=`. Supplied clients retain their
|
|
181
|
+
configuration and response-cookie handling and must be closed by the caller. Scope
|
|
182
|
+
cookie-bearing clients and MCP tool sessions to one authenticated principal; do not
|
|
183
|
+
share a user-specific cookie jar across users. Rejecting cookies does not partition
|
|
184
|
+
MCP protocol sessions or other server-side state between principals.
|
|
185
|
+
|
|
171
186
|
## 5. Multi-Agent Orchestration
|
|
172
187
|
|
|
173
188
|
Coordinate multiple agents to collaborate on complex tasks using orchestration patterns:
|
|
@@ -33,6 +33,7 @@ from ._telemetry import (
|
|
|
33
33
|
from .exceptions import (
|
|
34
34
|
AgentFrameworkException,
|
|
35
35
|
MiddlewareException,
|
|
36
|
+
ResponseInvalidatedException,
|
|
36
37
|
UserInputRequiredException,
|
|
37
38
|
WorkflowCheckpointException,
|
|
38
39
|
WorkflowConvergenceException,
|
|
@@ -309,8 +310,13 @@ _LAZY_MODULE_EXPORTS: Final[Mapping[str, tuple[str, ...]]] = {
|
|
|
309
310
|
"SearchType",
|
|
310
311
|
"SupportsVectorSearch",
|
|
311
312
|
"SupportsVectorUpsert",
|
|
313
|
+
"VectorCollectionContextProvider",
|
|
312
314
|
"VectorStoreCollectionDefinition",
|
|
313
315
|
"VectorStoreField",
|
|
316
|
+
"VectorStoreHistoryProvider",
|
|
317
|
+
"create_delete_tool",
|
|
318
|
+
"create_get_tool",
|
|
319
|
+
"create_upsert_tool",
|
|
314
320
|
"create_vector_search_tool",
|
|
315
321
|
"register_vectorstoremodel",
|
|
316
322
|
"vectorstoremodel",
|
|
@@ -565,6 +571,7 @@ __all__ = [
|
|
|
565
571
|
"Param",
|
|
566
572
|
"RawAgent",
|
|
567
573
|
"ReleaseCandidateFeature",
|
|
574
|
+
"ResponseInvalidatedException",
|
|
568
575
|
"ResponseStream",
|
|
569
576
|
"Role",
|
|
570
577
|
"RoleLiteral",
|
|
@@ -631,8 +638,10 @@ __all__ = [
|
|
|
631
638
|
"UsageDetails",
|
|
632
639
|
"UserInputRequiredException",
|
|
633
640
|
"ValidationTypeEnum",
|
|
641
|
+
"VectorCollectionContextProvider",
|
|
634
642
|
"VectorStoreCollectionDefinition",
|
|
635
643
|
"VectorStoreField",
|
|
644
|
+
"VectorStoreHistoryProvider",
|
|
636
645
|
"Workflow",
|
|
637
646
|
"WorkflowAgent",
|
|
638
647
|
"WorkflowBuilder",
|
|
@@ -665,8 +674,11 @@ __all__ = [
|
|
|
665
674
|
"create_agent_hooks_middleware_from_emitter",
|
|
666
675
|
"create_always_approve_tool_response",
|
|
667
676
|
"create_always_approve_tool_with_arguments_response",
|
|
677
|
+
"create_delete_tool",
|
|
668
678
|
"create_edge_runner",
|
|
679
|
+
"create_get_tool",
|
|
669
680
|
"create_harness_agent",
|
|
681
|
+
"create_upsert_tool",
|
|
670
682
|
"create_vector_search_tool",
|
|
671
683
|
"detect_media_type_from_base64",
|
|
672
684
|
"enqueue_messages",
|
|
@@ -266,8 +266,13 @@ from ._vectors import (
|
|
|
266
266
|
SearchType,
|
|
267
267
|
SupportsVectorSearch,
|
|
268
268
|
SupportsVectorUpsert,
|
|
269
|
+
VectorCollectionContextProvider,
|
|
269
270
|
VectorStoreCollectionDefinition,
|
|
270
271
|
VectorStoreField,
|
|
272
|
+
VectorStoreHistoryProvider,
|
|
273
|
+
create_delete_tool,
|
|
274
|
+
create_get_tool,
|
|
275
|
+
create_upsert_tool,
|
|
271
276
|
create_vector_search_tool,
|
|
272
277
|
register_vectorstoremodel,
|
|
273
278
|
vectorstoremodel,
|
|
@@ -335,6 +340,7 @@ from ._workflows._workflow_executor import SubWorkflowRequestMessage, SubWorkflo
|
|
|
335
340
|
from .exceptions import (
|
|
336
341
|
AgentFrameworkException,
|
|
337
342
|
MiddlewareException,
|
|
343
|
+
ResponseInvalidatedException,
|
|
338
344
|
UserInputRequiredException,
|
|
339
345
|
WorkflowCheckpointException,
|
|
340
346
|
WorkflowConvergenceException,
|
|
@@ -520,6 +526,7 @@ __all__ = [
|
|
|
520
526
|
"Param",
|
|
521
527
|
"RawAgent",
|
|
522
528
|
"ReleaseCandidateFeature",
|
|
529
|
+
"ResponseInvalidatedException",
|
|
523
530
|
"ResponseStream",
|
|
524
531
|
"Role",
|
|
525
532
|
"RoleLiteral",
|
|
@@ -586,8 +593,10 @@ __all__ = [
|
|
|
586
593
|
"UsageDetails",
|
|
587
594
|
"UserInputRequiredException",
|
|
588
595
|
"ValidationTypeEnum",
|
|
596
|
+
"VectorCollectionContextProvider",
|
|
589
597
|
"VectorStoreCollectionDefinition",
|
|
590
598
|
"VectorStoreField",
|
|
599
|
+
"VectorStoreHistoryProvider",
|
|
591
600
|
"Workflow",
|
|
592
601
|
"WorkflowAgent",
|
|
593
602
|
"WorkflowBuilder",
|
|
@@ -620,8 +629,11 @@ __all__ = [
|
|
|
620
629
|
"create_agent_hooks_middleware_from_emitter",
|
|
621
630
|
"create_always_approve_tool_response",
|
|
622
631
|
"create_always_approve_tool_with_arguments_response",
|
|
632
|
+
"create_delete_tool",
|
|
623
633
|
"create_edge_runner",
|
|
634
|
+
"create_get_tool",
|
|
624
635
|
"create_harness_agent",
|
|
636
|
+
"create_upsert_tool",
|
|
625
637
|
"create_vector_search_tool",
|
|
626
638
|
"detect_media_type_from_base64",
|
|
627
639
|
"enqueue_messages",
|
|
@@ -1405,8 +1405,10 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1405
1405
|
self.context_providers.append(InMemoryHistoryProvider())
|
|
1406
1406
|
|
|
1407
1407
|
active_session = session
|
|
1408
|
+
framework_created_session = False
|
|
1408
1409
|
if active_session is None and self.context_providers:
|
|
1409
1410
|
active_session = AgentSession()
|
|
1411
|
+
framework_created_session = True
|
|
1410
1412
|
|
|
1411
1413
|
per_service_call_history_providers = self._resolve_per_service_call_history_providers(
|
|
1412
1414
|
session=active_session,
|
|
@@ -1455,12 +1457,22 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1455
1457
|
# Normalize tools
|
|
1456
1458
|
normalized_tools = _normalize_tools(tools_)
|
|
1457
1459
|
|
|
1460
|
+
# Extract additional function arguments
|
|
1461
|
+
effective_function_invocation_kwargs = (
|
|
1462
|
+
dict(function_invocation_kwargs) if function_invocation_kwargs is not None else {}
|
|
1463
|
+
)
|
|
1464
|
+
additional_function_arguments = {**effective_function_invocation_kwargs, **existing_additional_args}
|
|
1465
|
+
|
|
1458
1466
|
# Resolve final tool list (configured tools + runtime provided tools + local MCP server tools)
|
|
1459
1467
|
final_tools = list(base_tools)
|
|
1460
1468
|
for tool in normalized_tools:
|
|
1461
1469
|
if isinstance(tool, MCPTool):
|
|
1470
|
+
await tool._prepare_for_run(additional_function_arguments) # pyright: ignore[reportPrivateUsage]
|
|
1462
1471
|
if not tool.is_connected:
|
|
1472
|
+
# The handshake and discovery requests are issued before any tool call, so the run's
|
|
1473
|
+
# kwargs must reach header_provider here or those requests go out unauthenticated.
|
|
1463
1474
|
await self._async_exit_stack.enter_async_context(tool)
|
|
1475
|
+
await tool._prepare_for_run(additional_function_arguments) # pyright: ignore[reportPrivateUsage]
|
|
1464
1476
|
_append_unique_tools(
|
|
1465
1477
|
final_tools,
|
|
1466
1478
|
tool.functions,
|
|
@@ -1470,19 +1482,18 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1470
1482
|
_append_unique_tools(final_tools, [tool])
|
|
1471
1483
|
|
|
1472
1484
|
for mcp_server in self.mcp_tools:
|
|
1485
|
+
await mcp_server._prepare_for_run(additional_function_arguments) # pyright: ignore[reportPrivateUsage]
|
|
1473
1486
|
if not mcp_server.is_connected:
|
|
1474
1487
|
await self._async_exit_stack.enter_async_context(mcp_server)
|
|
1488
|
+
await mcp_server._prepare_for_run( # pyright: ignore[reportPrivateUsage]
|
|
1489
|
+
additional_function_arguments
|
|
1490
|
+
)
|
|
1475
1491
|
_append_unique_tools(
|
|
1476
1492
|
final_tools,
|
|
1477
1493
|
mcp_server.functions,
|
|
1478
1494
|
duplicate_error_message=mcp_duplicate_message,
|
|
1479
1495
|
)
|
|
1480
1496
|
|
|
1481
|
-
effective_function_invocation_kwargs = (
|
|
1482
|
-
dict(function_invocation_kwargs) if function_invocation_kwargs is not None else {}
|
|
1483
|
-
)
|
|
1484
|
-
additional_function_arguments = {**effective_function_invocation_kwargs, **existing_additional_args}
|
|
1485
|
-
|
|
1486
1497
|
model = opts.pop("model", None)
|
|
1487
1498
|
|
|
1488
1499
|
# Build options dict from run() options merged with provided options
|
|
@@ -1520,8 +1531,12 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1520
1531
|
session_messages: list[Message] = session_context.get_messages(include_input=True)
|
|
1521
1532
|
|
|
1522
1533
|
effective_client_kwargs = dict(client_kwargs) if client_kwargs is not None else {}
|
|
1534
|
+
from ._tools import _APPROVAL_SESSION_IS_AUTHORITATIVE_KEY # pyright: ignore[reportPrivateUsage]
|
|
1535
|
+
|
|
1536
|
+
effective_client_kwargs.pop(_APPROVAL_SESSION_IS_AUTHORITATIVE_KEY, None)
|
|
1523
1537
|
if active_session is not None:
|
|
1524
1538
|
effective_client_kwargs["session"] = active_session
|
|
1539
|
+
effective_client_kwargs[_APPROVAL_SESSION_IS_AUTHORITATIVE_KEY] = not framework_created_session
|
|
1525
1540
|
per_service_call_history_middleware: PerServiceCallHistoryPersistingMiddleware | None = None
|
|
1526
1541
|
if per_service_call_history_providers and active_session is not None:
|
|
1527
1542
|
per_service_call_history_middleware = PerServiceCallHistoryPersistingMiddleware(
|
|
@@ -1712,7 +1727,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1712
1727
|
logger.error("Failed to send log message to server: %s", e)
|
|
1713
1728
|
|
|
1714
1729
|
@server.list_tools()
|
|
1715
|
-
async def _list_tools() -> list[types.Tool]:
|
|
1730
|
+
async def _list_tools() -> list[types.Tool]:
|
|
1716
1731
|
"""List all tools in the agent."""
|
|
1717
1732
|
schema = agent_tool.parameters()
|
|
1718
1733
|
|
|
@@ -1726,7 +1741,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1726
1741
|
return [tool]
|
|
1727
1742
|
|
|
1728
1743
|
@server.call_tool()
|
|
1729
|
-
async def _call_tool(
|
|
1744
|
+
async def _call_tool(
|
|
1730
1745
|
name: str, arguments: dict[str, Any]
|
|
1731
1746
|
) -> Sequence[types.TextContent | types.ImageContent | types.AudioContent | types.EmbeddedResource]:
|
|
1732
1747
|
"""Call a tool in the agent."""
|
|
@@ -1769,7 +1784,7 @@ class RawAgent(BaseAgent, Generic[OptionsCoT]):
|
|
|
1769
1784
|
return mcp_content or [types.TextContent(type="text", text="")]
|
|
1770
1785
|
|
|
1771
1786
|
@server.set_logging_level()
|
|
1772
|
-
async def _set_logging_level(level: types.LoggingLevel) -> None:
|
|
1787
|
+
async def _set_logging_level(level: types.LoggingLevel) -> None:
|
|
1773
1788
|
"""Set the logging level for the server."""
|
|
1774
1789
|
logger.setLevel(LOG_LEVEL_MAPPING[level])
|
|
1775
1790
|
# emit this log with the new minimum level
|
|
@@ -30,6 +30,12 @@ GROUP_KIND_KEY = "kind"
|
|
|
30
30
|
GROUP_INDEX_KEY = "index"
|
|
31
31
|
GROUP_HAS_REASONING_KEY = "has_reasoning"
|
|
32
32
|
GROUP_TOKEN_COUNT_KEY = "token_count" # ruff:ignore[hardcoded-password-string] # nosec B105 - compaction metadata key, not a credential
|
|
33
|
+
GROUP_TOKEN_COUNT_BASIS_KEY = "token_count_basis" # ruff:ignore[hardcoded-password-string] # nosec B105 - compaction metadata key, not a credential
|
|
34
|
+
# Bumped whenever the token-estimation serialization changes. Cached counts
|
|
35
|
+
# stamped with an older basis are recomputed instead of reused: annotations
|
|
36
|
+
# survive Message.to_dict()/from_dict() round-trips, so without the basis a
|
|
37
|
+
# transcript annotated before a serialization change keeps its stale counts.
|
|
38
|
+
TOKEN_COUNT_BASIS_VERSION = 2
|
|
33
39
|
EXCLUDED_KEY = "_excluded"
|
|
34
40
|
EXCLUDE_REASON_KEY = "_exclude_reason"
|
|
35
41
|
SUMMARY_OF_MESSAGE_IDS_KEY = "_summary_of_message_ids"
|
|
@@ -48,6 +54,44 @@ _TOOL_CALL_CONTENT_TYPES: Final[set[str]] = {
|
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
|
|
57
|
+
def _deduplicate_origin_session_ids(origin_session_ids: Iterable[str]) -> list[str]:
|
|
58
|
+
"""Return origin session IDs in first-seen order without duplicates."""
|
|
59
|
+
unique_origin_session_ids: list[str] = []
|
|
60
|
+
seen_origin_session_ids: set[str] = set()
|
|
61
|
+
for origin_session_id in origin_session_ids:
|
|
62
|
+
if origin_session_id not in seen_origin_session_ids:
|
|
63
|
+
seen_origin_session_ids.add(origin_session_id)
|
|
64
|
+
unique_origin_session_ids.append(origin_session_id)
|
|
65
|
+
return unique_origin_session_ids
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _aggregate_origin_session_ids(messages: Sequence[Message]) -> list[str]:
|
|
69
|
+
"""Aggregate origin_session_ids from a sequence of Message objects.
|
|
70
|
+
|
|
71
|
+
Extracts origin_session_ids from each message's _attribution and returns
|
|
72
|
+
a deduplicated list preserving first-seen order. Messages without attribution
|
|
73
|
+
or without origin_session_ids are silently skipped.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
messages: The Message objects to aggregate provenance from.
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Deduplicated origin_session_ids in first-seen order.
|
|
80
|
+
"""
|
|
81
|
+
origin_session_ids: list[str] = []
|
|
82
|
+
for message in messages:
|
|
83
|
+
attribution = message.additional_properties.get("_attribution")
|
|
84
|
+
if not isinstance(attribution, Mapping):
|
|
85
|
+
continue
|
|
86
|
+
origins = attribution.get("origin_session_ids") # pyright: ignore[reportUnknownVariableType, reportUnknownMemberType]
|
|
87
|
+
if not isinstance(origins, Sequence) or isinstance(origins, str):
|
|
88
|
+
continue
|
|
89
|
+
for origin in cast("Sequence[Any]", origins):
|
|
90
|
+
if isinstance(origin, str):
|
|
91
|
+
origin_session_ids.append(origin)
|
|
92
|
+
return _deduplicate_origin_session_ids(origin_session_ids)
|
|
93
|
+
|
|
94
|
+
|
|
51
95
|
@runtime_checkable
|
|
52
96
|
class TokenizerProtocol(Protocol):
|
|
53
97
|
"""Protocol for token counters used by token-aware compaction strategies."""
|
|
@@ -378,13 +422,13 @@ def _set_group_summarized_by_summary_id(message: Message, summary_id: str) -> No
|
|
|
378
422
|
def _reconcile_compaction_summaries( # pyright: ignore[reportUnusedFunction]
|
|
379
423
|
source_messages: list[Message],
|
|
380
424
|
working_messages: Sequence[Message],
|
|
381
|
-
|
|
425
|
+
source_message_identities: set[int],
|
|
382
426
|
) -> None:
|
|
383
|
-
"""Reconcile summaries
|
|
427
|
+
"""Reconcile summaries supported by source-owned messages without persisting unrelated rewrites."""
|
|
384
428
|
source_message_ids = {message.message_id for message in source_messages if message.message_id}
|
|
385
429
|
candidates: list[tuple[Message, set[str]]] = []
|
|
386
430
|
for message in working_messages:
|
|
387
|
-
if id(message) in
|
|
431
|
+
if id(message) in source_message_identities:
|
|
388
432
|
continue
|
|
389
433
|
|
|
390
434
|
annotation = _read_group_annotation_raw(message)
|
|
@@ -458,7 +502,13 @@ def _write_group_annotation(
|
|
|
458
502
|
token_count: int | None = None
|
|
459
503
|
if existing_raw_annotation is not None:
|
|
460
504
|
raw_token_count = existing_raw_annotation.get(GROUP_TOKEN_COUNT_KEY)
|
|
461
|
-
|
|
505
|
+
raw_token_count_basis = existing_raw_annotation.get(GROUP_TOKEN_COUNT_BASIS_KEY)
|
|
506
|
+
# Preserve a cached count only when it was computed with the current
|
|
507
|
+
# serialization basis; counts from older bases are stale and get
|
|
508
|
+
# recomputed (see TOKEN_COUNT_BASIS_VERSION).
|
|
509
|
+
if raw_token_count_basis == TOKEN_COUNT_BASIS_VERSION and (
|
|
510
|
+
isinstance(raw_token_count, int) or raw_token_count is None
|
|
511
|
+
):
|
|
462
512
|
token_count = raw_token_count
|
|
463
513
|
unknown_fields = {
|
|
464
514
|
key: value
|
|
@@ -470,6 +520,7 @@ def _write_group_annotation(
|
|
|
470
520
|
GROUP_INDEX_KEY,
|
|
471
521
|
GROUP_HAS_REASONING_KEY,
|
|
472
522
|
GROUP_TOKEN_COUNT_KEY,
|
|
523
|
+
GROUP_TOKEN_COUNT_BASIS_KEY,
|
|
473
524
|
}
|
|
474
525
|
}
|
|
475
526
|
|
|
@@ -479,6 +530,7 @@ def _write_group_annotation(
|
|
|
479
530
|
GROUP_INDEX_KEY: index,
|
|
480
531
|
GROUP_HAS_REASONING_KEY: has_reasoning,
|
|
481
532
|
GROUP_TOKEN_COUNT_KEY: token_count,
|
|
533
|
+
GROUP_TOKEN_COUNT_BASIS_KEY: TOKEN_COUNT_BASIS_VERSION if token_count is not None else None,
|
|
482
534
|
}
|
|
483
535
|
annotation.update(unknown_fields)
|
|
484
536
|
message.additional_properties[GROUP_ANNOTATION_KEY] = annotation
|
|
@@ -512,6 +564,10 @@ def _token_count(message: Message) -> int | None:
|
|
|
512
564
|
if annotation is None:
|
|
513
565
|
return None
|
|
514
566
|
token_count = annotation.get(GROUP_TOKEN_COUNT_KEY)
|
|
567
|
+
# Counts stamped with an older serialization basis are stale: they were
|
|
568
|
+
# computed from a different serialized form and must be recomputed.
|
|
569
|
+
if annotation.get(GROUP_TOKEN_COUNT_BASIS_KEY) != TOKEN_COUNT_BASIS_VERSION:
|
|
570
|
+
return None
|
|
515
571
|
return token_count if isinstance(token_count, int) else None
|
|
516
572
|
|
|
517
573
|
|
|
@@ -520,6 +576,7 @@ def _write_token_count(message: Message, token_count: int) -> None:
|
|
|
520
576
|
if annotation is None:
|
|
521
577
|
return
|
|
522
578
|
annotation[GROUP_TOKEN_COUNT_KEY] = token_count
|
|
579
|
+
annotation[GROUP_TOKEN_COUNT_BASIS_KEY] = TOKEN_COUNT_BASIS_VERSION
|
|
523
580
|
message.additional_properties[GROUP_ANNOTATION_KEY] = annotation
|
|
524
581
|
|
|
525
582
|
|
|
@@ -694,12 +751,68 @@ def annotate_message_groups(
|
|
|
694
751
|
return _ordered_group_ids_from_annotations(messages)
|
|
695
752
|
|
|
696
753
|
|
|
754
|
+
_OPAQUE_REASONING_KEYS: Final[frozenset[str]] = frozenset({"encrypted_content"})
|
|
755
|
+
|
|
756
|
+
|
|
757
|
+
def _strip_opaque_reasoning_payload(value: Any) -> Any:
|
|
758
|
+
"""Recursively drop opaque reasoning members, keeping clear-text payloads."""
|
|
759
|
+
if isinstance(value, dict):
|
|
760
|
+
entries = cast("dict[Any, Any]", value)
|
|
761
|
+
filtered: dict[Any, Any] = {}
|
|
762
|
+
for key, item in entries.items():
|
|
763
|
+
if key in _OPAQUE_REASONING_KEYS:
|
|
764
|
+
continue
|
|
765
|
+
stripped = _strip_opaque_reasoning_payload(item)
|
|
766
|
+
if stripped is not None:
|
|
767
|
+
filtered[key] = stripped
|
|
768
|
+
return filtered or None
|
|
769
|
+
if isinstance(value, list):
|
|
770
|
+
entries = cast("list[Any]", value)
|
|
771
|
+
kept = [
|
|
772
|
+
stripped
|
|
773
|
+
for stripped in (_strip_opaque_reasoning_payload(entry) for entry in entries)
|
|
774
|
+
if stripped is not None
|
|
775
|
+
]
|
|
776
|
+
return kept or None
|
|
777
|
+
return value
|
|
778
|
+
|
|
779
|
+
|
|
697
780
|
def _serialize_content(content: Content) -> dict[str, Any]:
|
|
698
781
|
payload = content.to_dict(exclude_none=True)
|
|
699
782
|
payload.pop("raw_representation", None)
|
|
700
783
|
# ``items`` mirrors ``result`` for function_result content; exclude it
|
|
701
784
|
# to avoid double-counting tokens during estimation.
|
|
702
785
|
payload.pop("items", None)
|
|
786
|
+
# ``protected_data`` carries provider reasoning payloads. JSON-serialised
|
|
787
|
+
# reasoning_details (Chat Completions) are replayed to the provider as
|
|
788
|
+
# clear text -- ``summary``, ``reasoning_text`` and nested ``reasoning.text``
|
|
789
|
+
# are part of the context the provider receives, so only their opaque
|
|
790
|
+
# members (``encrypted_content``) are excluded and the clear text stays
|
|
791
|
+
# counted. Anything that is not such a JSON structure (Anthropic thinking
|
|
792
|
+
# ``signature``, Responses API ``encrypted_content`` blobs) is replayed
|
|
793
|
+
# opaquely and never tokenised; exclude it so estimation measures the text
|
|
794
|
+
# the model actually sees.
|
|
795
|
+
protected_data = payload.get("protected_data")
|
|
796
|
+
if isinstance(protected_data, str) and protected_data:
|
|
797
|
+
try:
|
|
798
|
+
reasoning_payload = json.loads(protected_data)
|
|
799
|
+
except ValueError:
|
|
800
|
+
reasoning_payload = None
|
|
801
|
+
filtered = (
|
|
802
|
+
_strip_opaque_reasoning_payload(reasoning_payload) if isinstance(reasoning_payload, (dict, list)) else None
|
|
803
|
+
)
|
|
804
|
+
if filtered is None:
|
|
805
|
+
payload.pop("protected_data", None)
|
|
806
|
+
else:
|
|
807
|
+
payload["protected_data"] = json.dumps(filtered, ensure_ascii=False)
|
|
808
|
+
else:
|
|
809
|
+
payload.pop("protected_data", None)
|
|
810
|
+
additional_properties = payload.get("additional_properties")
|
|
811
|
+
if isinstance(additional_properties, dict) and "encrypted_content" in additional_properties:
|
|
812
|
+
typed_properties = cast("dict[str, Any]", additional_properties)
|
|
813
|
+
payload["additional_properties"] = {
|
|
814
|
+
key: value for key, value in typed_properties.items() if key != "encrypted_content"
|
|
815
|
+
}
|
|
703
816
|
return payload
|
|
704
817
|
|
|
705
818
|
|
|
@@ -1142,19 +1255,28 @@ class ToolResultCompactionStrategy:
|
|
|
1142
1255
|
_set_group_summarized_by_summary_id(msg, summary_id)
|
|
1143
1256
|
changed = set_excluded(msg, excluded=True, reason="tool_result_compaction") or changed
|
|
1144
1257
|
|
|
1258
|
+
# Aggregate provenance directly from the actual Message objects being summarized
|
|
1259
|
+
# This ensures origin_session_ids are preserved even when message_id is None
|
|
1260
|
+
aggregated_origins = _aggregate_origin_session_ids(group_msgs)
|
|
1261
|
+
|
|
1145
1262
|
# Insert summary with forward links to the originals.
|
|
1146
1263
|
summary_annotation = {
|
|
1147
1264
|
SUMMARY_OF_MESSAGE_IDS_KEY: original_message_ids,
|
|
1148
1265
|
SUMMARY_OF_GROUP_IDS_KEY: [group_id],
|
|
1149
1266
|
}
|
|
1150
1267
|
insertion_index = starts.get(group_id, 0)
|
|
1268
|
+
|
|
1269
|
+
summary_additional_properties: dict[str, Any] = {
|
|
1270
|
+
GROUP_ANNOTATION_KEY: summary_annotation,
|
|
1271
|
+
}
|
|
1272
|
+
if aggregated_origins:
|
|
1273
|
+
summary_additional_properties["_attribution"] = {"origin_session_ids": aggregated_origins}
|
|
1274
|
+
|
|
1151
1275
|
summary_message = Message(
|
|
1152
1276
|
role="assistant",
|
|
1153
1277
|
contents=[summary_text],
|
|
1154
1278
|
message_id=summary_id,
|
|
1155
|
-
additional_properties=
|
|
1156
|
-
GROUP_ANNOTATION_KEY: summary_annotation,
|
|
1157
|
-
},
|
|
1279
|
+
additional_properties=summary_additional_properties,
|
|
1158
1280
|
)
|
|
1159
1281
|
messages.insert(insertion_index, summary_message)
|
|
1160
1282
|
annotate_message_groups(messages, from_index=insertion_index, force_reannotate=False)
|
|
@@ -1273,8 +1395,8 @@ def _format_summary_content(content: Content) -> str:
|
|
|
1273
1395
|
result_text = _format_summary_result_items(content.items) if content.items else ""
|
|
1274
1396
|
if not result_text:
|
|
1275
1397
|
result_text = _tool_result_text(content.result) if content.result is not None else "no result"
|
|
1276
|
-
if content.exception:
|
|
1277
|
-
result_text = f"error
|
|
1398
|
+
if content.exception is not None:
|
|
1399
|
+
result_text = f"error: {result_text}"
|
|
1278
1400
|
call_id_suffix = f" [call_id={content.call_id}]" if content.call_id else ""
|
|
1279
1401
|
return f"function_result: {result_text}{call_id_suffix}"
|
|
1280
1402
|
if content.type == "mcp_server_tool_call":
|
|
@@ -1285,8 +1407,8 @@ def _format_summary_content(content: Content) -> str:
|
|
|
1285
1407
|
return call
|
|
1286
1408
|
if content.type == "mcp_server_tool_result":
|
|
1287
1409
|
result_text = _tool_result_text(content.output)
|
|
1288
|
-
if content.exception:
|
|
1289
|
-
result_text = f"error
|
|
1410
|
+
if content.exception is not None:
|
|
1411
|
+
result_text = f"error: {result_text}"
|
|
1290
1412
|
call_id_suffix = f" [call_id={content.call_id}]" if content.call_id else ""
|
|
1291
1413
|
return f"mcp_tool_result: {result_text}{call_id_suffix}"
|
|
1292
1414
|
if content.type in ("function_approval_request", "function_approval_response"):
|
|
@@ -1570,13 +1692,21 @@ class SummarizationStrategy:
|
|
|
1570
1692
|
SUMMARY_OF_GROUP_IDS_KEY: summary_of_group_ids,
|
|
1571
1693
|
}
|
|
1572
1694
|
|
|
1695
|
+
# Aggregate provenance directly from the actual Message objects being summarized
|
|
1696
|
+
# This ensures origin_session_ids are preserved even when message_id is None
|
|
1697
|
+
aggregated_origins = _aggregate_origin_session_ids(messages_to_summarize)
|
|
1698
|
+
|
|
1699
|
+
summary_additional_properties: dict[str, Any] = {
|
|
1700
|
+
GROUP_ANNOTATION_KEY: summary_annotation,
|
|
1701
|
+
}
|
|
1702
|
+
if aggregated_origins:
|
|
1703
|
+
summary_additional_properties["_attribution"] = {"origin_session_ids": aggregated_origins}
|
|
1704
|
+
|
|
1573
1705
|
summary_message = Message(
|
|
1574
1706
|
role="assistant",
|
|
1575
1707
|
contents=[summary_text],
|
|
1576
1708
|
message_id=summary_id,
|
|
1577
|
-
additional_properties=
|
|
1578
|
-
GROUP_ANNOTATION_KEY: summary_annotation,
|
|
1579
|
-
},
|
|
1709
|
+
additional_properties=summary_additional_properties,
|
|
1580
1710
|
)
|
|
1581
1711
|
|
|
1582
1712
|
for message in messages_to_summarize:
|
|
@@ -1817,6 +1947,11 @@ class CompactionProvider(ContextProvider):
|
|
|
1817
1947
|
if not all_messages:
|
|
1818
1948
|
return
|
|
1819
1949
|
|
|
1950
|
+
# Track each original message's source before compaction
|
|
1951
|
+
source_by_id: dict[int, str] = {
|
|
1952
|
+
id(message): sid for sid, msgs in context.context_messages.items() for message in msgs
|
|
1953
|
+
}
|
|
1954
|
+
|
|
1820
1955
|
await _run_compaction_strategy(
|
|
1821
1956
|
all_messages,
|
|
1822
1957
|
strategy=self.before_strategy,
|
|
@@ -1825,9 +1960,23 @@ class CompactionProvider(ContextProvider):
|
|
|
1825
1960
|
)
|
|
1826
1961
|
|
|
1827
1962
|
projected = project_included_messages(all_messages)
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1963
|
+
|
|
1964
|
+
# Rebuild provider message lists from the projected list, preserving source attribution
|
|
1965
|
+
# and including new synthetic messages created by compaction strategies
|
|
1966
|
+
rebuilt: dict[str, list[Message]] = {sid: [] for sid in context.context_messages}
|
|
1967
|
+
fallback_sid = next(iter(rebuilt), self.source_id)
|
|
1968
|
+
last_sid = fallback_sid
|
|
1969
|
+
for message in projected:
|
|
1970
|
+
# For new synthetic messages, use the last known source; for original messages, use their tracked source
|
|
1971
|
+
sid = source_by_id.get(id(message), last_sid)
|
|
1972
|
+
if sid not in rebuilt:
|
|
1973
|
+
# If the source was somehow removed during compaction, fall back to the last known source
|
|
1974
|
+
sid = last_sid
|
|
1975
|
+
rebuilt[sid].append(message)
|
|
1976
|
+
last_sid = sid
|
|
1977
|
+
|
|
1978
|
+
context.context_messages.clear()
|
|
1979
|
+
context.context_messages.update(rebuilt)
|
|
1831
1980
|
|
|
1832
1981
|
async def after_run(
|
|
1833
1982
|
self,
|