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.
Files changed (114) hide show
  1. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/PKG-INFO +18 -2
  2. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/README.md +15 -0
  3. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/__init__.py +12 -0
  4. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/__init__.pyi +12 -0
  5. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_agents.py +23 -8
  6. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_compaction.py +166 -17
  7. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_mode.py +69 -17
  8. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_tool_approval.py +65 -41
  9. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_in_memory.py +2 -98
  10. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_mcp.py +578 -72
  11. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_middleware.py +52 -11
  12. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_sessions.py +60 -6
  13. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_skills.py +286 -238
  14. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_telemetry.py +2 -0
  15. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_tools.py +1488 -202
  16. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_types.py +104 -26
  17. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_vectors.py +1244 -4
  18. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_agent.py +12 -3
  19. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_agent_executor.py +26 -36
  20. agent_framework_core-1.19.0/agent_framework/_workflows/_agent_utils.py +153 -0
  21. agent_framework_core-1.19.0/agent_framework/_workflows/_checkpoint.py +873 -0
  22. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_checkpoint_encoding.py +10 -3
  23. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_const.py +8 -2
  24. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_executor.py +8 -9
  25. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_function_executor.py +7 -9
  26. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_functional.py +34 -14
  27. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_request_info_mixin.py +46 -7
  28. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_runner.py +15 -16
  29. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_typing_utils.py +24 -3
  30. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow.py +178 -35
  31. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow_executor.py +4 -1
  32. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/azure/__init__.py +3 -0
  33. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/azure/__init__.pyi +4 -0
  34. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/exceptions.py +6 -0
  35. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/observability.py +115 -42
  36. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/security.py +487 -114
  37. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/pyproject.toml +4 -2
  38. agent_framework_core-1.18.0/agent_framework/_workflows/_agent_utils.py +0 -17
  39. agent_framework_core-1.18.0/agent_framework/_workflows/_checkpoint.py +0 -463
  40. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/LICENSE +0 -0
  41. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_agent_hooks.py +0 -0
  42. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_clients.py +0 -0
  43. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_docstrings.py +0 -0
  44. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_evaluation.py +0 -0
  45. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_feature_stage.py +0 -0
  46. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_filesystem.py +0 -0
  47. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/__init__.py +0 -0
  48. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_agent.py +0 -0
  49. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_agent.pyi +0 -0
  50. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_background_agents.py +0 -0
  51. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_file_access.py +0 -0
  52. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_file_memory.py +0 -0
  53. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_loop.py +0 -0
  54. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_memory.py +0 -0
  55. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_harness/_todo.py +0 -0
  56. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_serialization.py +0 -0
  57. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_settings.py +0 -0
  58. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_vector_filters.py +0 -0
  59. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/__init__.py +0 -0
  60. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_conversation_history.py +0 -0
  61. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_edge.py +0 -0
  62. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_edge_runner.py +0 -0
  63. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_events.py +0 -0
  64. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_message_utils.py +0 -0
  65. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_model_utils.py +0 -0
  66. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_runner_context.py +0 -0
  67. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_state.py +0 -0
  68. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_validation.py +0 -0
  69. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_viz.py +0 -0
  70. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow_builder.py +0 -0
  71. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/_workflows/_workflow_context.py +0 -0
  72. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/a2a/__init__.py +0 -0
  73. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/a2a/__init__.pyi +0 -0
  74. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ag_ui/__init__.py +0 -0
  75. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ag_ui/__init__.pyi +0 -0
  76. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/amazon/__init__.py +0 -0
  77. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/amazon/__init__.pyi +0 -0
  78. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/anthropic/__init__.py +0 -0
  79. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/anthropic/__init__.pyi +0 -0
  80. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/chatkit/__init__.py +0 -0
  81. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/chatkit/__init__.pyi +0 -0
  82. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/declarative/__init__.py +0 -0
  83. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/declarative/__init__.pyi +0 -0
  84. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/devui/__init__.py +0 -0
  85. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/devui/__init__.pyi +0 -0
  86. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/foundry/__init__.py +0 -0
  87. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/foundry/__init__.pyi +0 -0
  88. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/gemini/__init__.py +0 -0
  89. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/gemini/__init__.pyi +0 -0
  90. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/github/__init__.py +0 -0
  91. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/github/__init__.pyi +0 -0
  92. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/google/__init__.py +0 -0
  93. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/google/__init__.pyi +0 -0
  94. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/hyperlight/__init__.py +0 -0
  95. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/lab/__init__.py +0 -0
  96. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mem0/__init__.py +0 -0
  97. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mem0/__init__.pyi +0 -0
  98. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/microsoft/__init__.py +0 -0
  99. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/microsoft/__init__.pyi +0 -0
  100. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mistral/__init__.py +0 -0
  101. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/mistral/__init__.pyi +0 -0
  102. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/monty/__init__.py +0 -0
  103. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/monty/__init__.pyi +0 -0
  104. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ollama/__init__.py +0 -0
  105. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/ollama/__init__.pyi +0 -0
  106. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/openai/__init__.py +0 -0
  107. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/openai/__init__.pyi +0 -0
  108. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/orchestrations/__init__.py +0 -0
  109. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/orchestrations/__init__.pyi +0 -0
  110. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/py.typed +0 -0
  111. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/redis/__init__.py +0 -0
  112. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/redis/__init__.pyi +0 -0
  113. {agent_framework_core-1.18.0 → agent_framework_core-1.19.0}/agent_framework/tools/__init__.py +0 -0
  114. {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.18.0
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]: # type: ignore
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( # type: ignore
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: # type: ignore
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
- previous_message_ids: set[int],
425
+ source_message_identities: set[int],
382
426
  ) -> None:
383
- """Reconcile summaries of source messages without persisting unrelated rewrites."""
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 previous_message_ids:
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
- if isinstance(raw_token_count, int) or raw_token_count is None:
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({content.exception}): {result_text}"
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({content.exception}): {result_text}"
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
- projected_set = {id(m) for m in projected}
1829
- for sid in list(context.context_messages):
1830
- context.context_messages[sid] = [m for m in context.context_messages[sid] if id(m) in projected_set]
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,