xpander-sdk 2.0.332__tar.gz → 2.0.334__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.
- {xpander_sdk-2.0.332/src/xpander_sdk.egg-info → xpander_sdk-2.0.334}/PKG-INFO +1 -1
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/setup.py +1 -1
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/__init__.py +1 -1
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/context_optimizer.py +87 -2
- xpander_sdk-2.0.334/src/xpander_sdk/models/context_status.py +31 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/events.py +3 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/frameworks/agno.py +1 -1
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334/src/xpander_sdk.egg-info}/PKG-INFO +1 -1
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk.egg-info/SOURCES.txt +2 -0
- xpander_sdk-2.0.334/tests/test_context_optimizer_status_events.py +164 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/LICENSE +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/README.md +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/pyproject.toml +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/setup.cfg +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/consts/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/consts/api_routes.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/action_ledger.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/compact_retry_result.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/completion_evidence.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/constants.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/encryption.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/error_patterns.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/finalize_mode.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/helpers/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/helpers/chunking.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/helpers/recent_actions.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/helpers/secrets.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/helpers/tool_result.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/helpers/xml_safety.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/mixins/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/mixins/map_reduce.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/prompts.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/workspace_cache.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/module_base.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/state.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/xpander_api_client.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/exceptions/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/exceptions/module_exception.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/action_ledger.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/activity.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/compactization.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/configuration.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/deep_planning.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/frameworks.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/generic.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/notifications.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/orchestrations.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/shared.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/models/user.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/agents_module.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/agent.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/agent_list.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/knowledge_bases.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/sub_modules/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/sub_modules/agent.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/utils/generic.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/backend_module.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/decorators/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/decorators/on_auth_event.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/events_registry.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/frameworks/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/frameworks/dispatch.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/extra_headers.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/mcp_oauth.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/tool_call_events.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_boot.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_shutdown.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_task.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_tool.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/events_module.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/models/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/models/deployments.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/models/events.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/streaming_server.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/utils/generic.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/utils/git_init.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/knowledge_bases_module.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/models/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/models/knowledge_bases.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/sub_modules/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/sub_modules/knowledge_base.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/sub_modules/knowledge_base_document_item.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/models/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/models/task.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/models/tasks_list.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/sub_modules/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/sub_modules/task.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/tasks_module.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/utils/files.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/decorators/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/decorators/register_tool.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/models/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/models/mcp.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/models/tool_invocation_result.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/sub_modules/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/sub_modules/tool.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/tools_repository_module.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/utils/generic.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/utils/local_tools.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/utils/schemas.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/utils/workspace_payload.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/utils/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/utils/agents/__init__.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/utils/env.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/utils/event_loop.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/utils/generic.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/utils/tools.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk.egg-info/dependency_links.txt +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk.egg-info/requires.txt +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk.egg-info/top_level.txt +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_action_ledger_dedup.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_boot_shutdown_handlers.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_context_optimizer_chunked.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_context_optimizer_perf.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_context_optimizer_recent_actions.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_context_optimizer_unwrap.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_context_optimizer_window.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_context_window_detection.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_layer_1_cache_integration.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_reasoning_plan_task_id.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_tool_call_events.py +0 -0
- {xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/tests/test_workspace_cache.py +0 -0
|
@@ -259,6 +259,11 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
|
|
|
259
259
|
default_factory=set, init=False, repr=False
|
|
260
260
|
)
|
|
261
261
|
|
|
262
|
+
_last_emitted_status_pct: Optional[int] = field(
|
|
263
|
+
default=None, init=False, repr=False
|
|
264
|
+
)
|
|
265
|
+
_compacting: bool = field(default=False, init=False, repr=False)
|
|
266
|
+
|
|
262
267
|
# In-memory cache + write queue for Layer 1 offloaded blobs. The optimizer
|
|
263
268
|
# writes encrypted bytes here synchronously and the cache spawns the
|
|
264
269
|
# actual workspace POST in the background; the agno tool hook short-
|
|
@@ -544,6 +549,8 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
|
|
|
544
549
|
# Layer 1: always runs — offload large tool results to workspace
|
|
545
550
|
await self.layer_1_microcompact(messages)
|
|
546
551
|
|
|
552
|
+
await self._publish_context_status(messages)
|
|
553
|
+
|
|
547
554
|
# Layer 3: agent-requested manual compaction (takes priority)
|
|
548
555
|
if self.compact_requested:
|
|
549
556
|
focus = self.compact_focus
|
|
@@ -1046,6 +1053,78 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
|
|
|
1046
1053
|
),
|
|
1047
1054
|
)
|
|
1048
1055
|
|
|
1056
|
+
async def apublish_context_status(
|
|
1057
|
+
self, messages: List[Message]
|
|
1058
|
+
) -> "Optional[Any]":
|
|
1059
|
+
"""Force-emit a context_status snapshot and return the payload; for mid-loop hooks (tool-call completions, sub-execution boundaries) where the caller wants to refresh the indicator without changing the compacting flag."""
|
|
1060
|
+
if not messages:
|
|
1061
|
+
return None
|
|
1062
|
+
return await self._publish_context_status(messages, force=True)
|
|
1063
|
+
|
|
1064
|
+
async def apublish_final_context_status(
|
|
1065
|
+
self, messages: List[Message]
|
|
1066
|
+
) -> "Optional[Any]":
|
|
1067
|
+
"""Force-emit a final context_status with compacting cleared; called at agno run end (RunCompleted/Cancelled/Error) so the indicator settles on post-final-message state."""
|
|
1068
|
+
if not messages:
|
|
1069
|
+
return None
|
|
1070
|
+
self._compacting = False
|
|
1071
|
+
return await self._publish_context_status(messages, force=True)
|
|
1072
|
+
|
|
1073
|
+
async def _publish_context_status(
|
|
1074
|
+
self,
|
|
1075
|
+
messages: List[Message],
|
|
1076
|
+
*,
|
|
1077
|
+
force: bool = False,
|
|
1078
|
+
) -> "Optional[Any]":
|
|
1079
|
+
"""Emit a context_status snapshot for the chat UI indicator; returns the payload (or None if unconfigured / estimate failed)."""
|
|
1080
|
+
if not self.agent or not self.task:
|
|
1081
|
+
return None
|
|
1082
|
+
|
|
1083
|
+
from xpander_sdk.models.context_status import ContextStatus
|
|
1084
|
+
from xpander_sdk.models.events import TaskUpdateEventType
|
|
1085
|
+
|
|
1086
|
+
try:
|
|
1087
|
+
estimated = self._estimate_tokens(messages)
|
|
1088
|
+
except Exception as exc:
|
|
1089
|
+
logger.debug(f"[context-optimizer] context_status estimate failed: {exc}")
|
|
1090
|
+
return None
|
|
1091
|
+
|
|
1092
|
+
window = max(1, int(self.context_window))
|
|
1093
|
+
pct = max(0.0, min(100.0, (estimated / window) * 100.0))
|
|
1094
|
+
self._last_emitted_status_pct = int(pct)
|
|
1095
|
+
|
|
1096
|
+
payload = ContextStatus(
|
|
1097
|
+
estimated_tokens=estimated,
|
|
1098
|
+
context_window=window,
|
|
1099
|
+
percent=round(pct, 1),
|
|
1100
|
+
auto_compact_threshold=self._auto_compact_threshold,
|
|
1101
|
+
emergency_threshold=self._emergency_compact_threshold,
|
|
1102
|
+
compacting=self._compacting,
|
|
1103
|
+
)
|
|
1104
|
+
|
|
1105
|
+
coro = self._push_activity_event(
|
|
1106
|
+
event_type=TaskUpdateEventType.ContextStatus,
|
|
1107
|
+
data=payload,
|
|
1108
|
+
)
|
|
1109
|
+
try:
|
|
1110
|
+
task = asyncio.create_task(coro)
|
|
1111
|
+
except RuntimeError as exc:
|
|
1112
|
+
logger.debug(f"[context-optimizer] context_status schedule failed: {exc}")
|
|
1113
|
+
coro.close()
|
|
1114
|
+
return payload
|
|
1115
|
+
|
|
1116
|
+
def _log_status_task_exception(t: "asyncio.Task[Any]") -> None:
|
|
1117
|
+
if t.cancelled():
|
|
1118
|
+
return
|
|
1119
|
+
exc = t.exception()
|
|
1120
|
+
if exc is not None:
|
|
1121
|
+
logger.debug(
|
|
1122
|
+
f"[context-optimizer] context_status publish failed: {exc}"
|
|
1123
|
+
)
|
|
1124
|
+
|
|
1125
|
+
task.add_done_callback(_log_status_task_exception)
|
|
1126
|
+
return payload
|
|
1127
|
+
|
|
1049
1128
|
# Progress-emission rate-limiter state. Reset at the start of every
|
|
1050
1129
|
# ``layer_2_auto_compact`` call by ``_reset_progress_state``.
|
|
1051
1130
|
_progress_last_percent: float = field(default=-1.0, init=False, repr=False)
|
|
@@ -1379,6 +1458,9 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
|
|
|
1379
1458
|
f"threshold={self._auto_compact_threshold:,})"
|
|
1380
1459
|
)
|
|
1381
1460
|
|
|
1461
|
+
self._compacting = True
|
|
1462
|
+
await self._publish_context_status(messages, force=True)
|
|
1463
|
+
|
|
1382
1464
|
# Publish start event (fire-and-forget)
|
|
1383
1465
|
await self._publish_compaction_start(
|
|
1384
1466
|
trigger=trigger,
|
|
@@ -1574,6 +1656,9 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
|
|
|
1574
1656
|
reduce_phase_seconds=telemetry.get("reduce_phase_seconds"),
|
|
1575
1657
|
)
|
|
1576
1658
|
|
|
1659
|
+
self._compacting = False
|
|
1660
|
+
await self._publish_context_status(messages, force=True)
|
|
1661
|
+
|
|
1577
1662
|
ratio = (
|
|
1578
1663
|
f"{(1 - post_tokens / pre_tokens) * 100:.1f}%"
|
|
1579
1664
|
if pre_tokens > 0
|
|
@@ -1665,9 +1750,9 @@ class XPanderContextOptimizer(MapReduceMixin, CompressionManager):
|
|
|
1665
1750
|
f"(trigger={trigger}, consecutive_failures={self._auto_compact_consecutive_failures}/"
|
|
1666
1751
|
f"{MAX_CONSECUTIVE_COMPACT_FAILURES}): {exc}"
|
|
1667
1752
|
)
|
|
1668
|
-
# Publish error event (fire-and-forget)
|
|
1669
1753
|
await self._publish_compaction_error(trigger=trigger, error=str(exc))
|
|
1670
|
-
|
|
1754
|
+
self._compacting = False
|
|
1755
|
+
await self._publish_context_status(messages, force=True)
|
|
1671
1756
|
|
|
1672
1757
|
# ================================================================== #
|
|
1673
1758
|
# Pre-retry helpers
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
from pydantic import Field
|
|
2
|
+
|
|
3
|
+
from xpander_sdk.models.shared import XPanderSharedModel
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ContextStatus(XPanderSharedModel):
|
|
7
|
+
"""Per-turn snapshot of the LLM context window for the chat UI indicator."""
|
|
8
|
+
|
|
9
|
+
estimated_tokens: int = Field(
|
|
10
|
+
...,
|
|
11
|
+
description="Rough token count of the current message list (post-Layer-1 microcompaction).",
|
|
12
|
+
)
|
|
13
|
+
context_window: int = Field(
|
|
14
|
+
..., description="Model context window size in tokens."
|
|
15
|
+
)
|
|
16
|
+
percent: float = Field(
|
|
17
|
+
...,
|
|
18
|
+
description="estimated_tokens / context_window * 100, clamped to [0, 100].",
|
|
19
|
+
)
|
|
20
|
+
auto_compact_threshold: int = Field(
|
|
21
|
+
...,
|
|
22
|
+
description="Token level at which Layer 2 auto-compaction fires.",
|
|
23
|
+
)
|
|
24
|
+
emergency_threshold: int = Field(
|
|
25
|
+
...,
|
|
26
|
+
description="Token level at which the emergency safety net (88%) fires.",
|
|
27
|
+
)
|
|
28
|
+
compacting: bool = Field(
|
|
29
|
+
default=False,
|
|
30
|
+
description="True while a Layer 2 compaction is in flight on the current turn.",
|
|
31
|
+
)
|
|
@@ -91,6 +91,9 @@ class TaskUpdateEventType(str, Enum):
|
|
|
91
91
|
# task compaction
|
|
92
92
|
TaskCompactization = "task_compactization"
|
|
93
93
|
|
|
94
|
+
# context window status (per-turn snapshot, delta-gated)
|
|
95
|
+
ContextStatus = "context_status"
|
|
96
|
+
|
|
94
97
|
# agent gateway
|
|
95
98
|
AgentGatewayDecision = "agent_gateway_decision"
|
|
96
99
|
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/frameworks/agno.py
RENAMED
|
@@ -604,7 +604,7 @@ async def build_agent_args(
|
|
|
604
604
|
{
|
|
605
605
|
"name": xpander_agent.name,
|
|
606
606
|
"model": model,
|
|
607
|
-
"description": xpander_agent.instructions.description,
|
|
607
|
+
"description": xpander_agent.description if xpander_agent.description and xpander_agent.description is not None and len(xpander_agent.description) != 0 else xpander_agent.instructions.description,
|
|
608
608
|
"instructions": (
|
|
609
609
|
task.instructions_override
|
|
610
610
|
if task.instructions_override
|
|
@@ -40,6 +40,7 @@ src/xpander_sdk/models/action_ledger.py
|
|
|
40
40
|
src/xpander_sdk/models/activity.py
|
|
41
41
|
src/xpander_sdk/models/compactization.py
|
|
42
42
|
src/xpander_sdk/models/configuration.py
|
|
43
|
+
src/xpander_sdk/models/context_status.py
|
|
43
44
|
src/xpander_sdk/models/deep_planning.py
|
|
44
45
|
src/xpander_sdk/models/events.py
|
|
45
46
|
src/xpander_sdk/models/frameworks.py
|
|
@@ -127,6 +128,7 @@ tests/test_boot_shutdown_handlers.py
|
|
|
127
128
|
tests/test_context_optimizer_chunked.py
|
|
128
129
|
tests/test_context_optimizer_perf.py
|
|
129
130
|
tests/test_context_optimizer_recent_actions.py
|
|
131
|
+
tests/test_context_optimizer_status_events.py
|
|
130
132
|
tests/test_context_optimizer_unwrap.py
|
|
131
133
|
tests/test_context_optimizer_window.py
|
|
132
134
|
tests/test_context_window_detection.py
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
"""Unit tests for the per-turn context_status event stream."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import asyncio
|
|
6
|
+
from types import SimpleNamespace
|
|
7
|
+
from unittest.mock import AsyncMock, MagicMock, patch
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
10
|
+
|
|
11
|
+
from xpander_sdk.core.context_optimizer import context_optimizer as co
|
|
12
|
+
from xpander_sdk.core.context_optimizer.context_optimizer import (
|
|
13
|
+
XPanderContextOptimizer,
|
|
14
|
+
)
|
|
15
|
+
from xpander_sdk.models.events import TaskUpdateEventType
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def _mk_msg(role: str = "user", content: str = "x"):
|
|
19
|
+
return SimpleNamespace(
|
|
20
|
+
role=role,
|
|
21
|
+
content=content,
|
|
22
|
+
tool_name=None,
|
|
23
|
+
tool_call_id=None,
|
|
24
|
+
to_dict=lambda r=role, c=content: {"role": r, "content": c},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _make_optimizer(estimated: int = 50_000):
|
|
29
|
+
opt = XPanderContextOptimizer(
|
|
30
|
+
context_window=200_000,
|
|
31
|
+
reserved_for_output=20_000,
|
|
32
|
+
buffer_tokens=13_000,
|
|
33
|
+
chunked_compact_threshold=100_000,
|
|
34
|
+
)
|
|
35
|
+
opt.agent = SimpleNamespace(id="agent-1", configuration=MagicMock())
|
|
36
|
+
opt.task = SimpleNamespace(
|
|
37
|
+
id="task-1", organization_id="org-1", deep_planning=None
|
|
38
|
+
)
|
|
39
|
+
opt._push_activity_event = AsyncMock(return_value=None)
|
|
40
|
+
opt._estimate_tokens = MagicMock(return_value=estimated)
|
|
41
|
+
opt.layer_1_microcompact = AsyncMock(return_value=None)
|
|
42
|
+
return opt
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _status_calls(opt) -> list:
|
|
46
|
+
return [
|
|
47
|
+
call
|
|
48
|
+
for call in opt._push_activity_event.await_args_list
|
|
49
|
+
if call.kwargs.get("event_type") == TaskUpdateEventType.ContextStatus
|
|
50
|
+
]
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
async def _drain_pending_tasks() -> None:
|
|
54
|
+
"""Yield so detached create_task pushes run before asserting on AsyncMock awaits."""
|
|
55
|
+
for _ in range(5):
|
|
56
|
+
await asyncio.sleep(0)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@pytest.mark.asyncio
|
|
60
|
+
async def test_first_acompress_emits_one_status_event():
|
|
61
|
+
opt = _make_optimizer(estimated=50_000)
|
|
62
|
+
await opt.acompress(messages=[_mk_msg()])
|
|
63
|
+
await _drain_pending_tasks()
|
|
64
|
+
|
|
65
|
+
calls = _status_calls(opt)
|
|
66
|
+
assert len(calls) == 1
|
|
67
|
+
payload = calls[0].kwargs["data"]
|
|
68
|
+
assert payload.estimated_tokens == 50_000
|
|
69
|
+
assert payload.context_window == 200_000
|
|
70
|
+
assert payload.percent == 25.0
|
|
71
|
+
assert payload.compacting is False
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
@pytest.mark.asyncio
|
|
75
|
+
async def test_status_event_emits_every_acompress():
|
|
76
|
+
opt = _make_optimizer(estimated=50_000)
|
|
77
|
+
await opt.acompress(messages=[_mk_msg()])
|
|
78
|
+
opt._estimate_tokens.return_value = 50_500
|
|
79
|
+
await opt.acompress(messages=[_mk_msg()])
|
|
80
|
+
await _drain_pending_tasks()
|
|
81
|
+
|
|
82
|
+
assert len(_status_calls(opt)) == 2
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
@pytest.mark.asyncio
|
|
86
|
+
async def test_status_event_re_emitted_when_percent_bucket_changes():
|
|
87
|
+
opt = _make_optimizer(estimated=50_000)
|
|
88
|
+
await opt.acompress(messages=[_mk_msg()])
|
|
89
|
+
opt._estimate_tokens.return_value = 60_000
|
|
90
|
+
await opt.acompress(messages=[_mk_msg()])
|
|
91
|
+
await _drain_pending_tasks()
|
|
92
|
+
|
|
93
|
+
calls = _status_calls(opt)
|
|
94
|
+
assert len(calls) == 2
|
|
95
|
+
assert calls[1].kwargs["data"].percent == 30.0
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
@pytest.mark.asyncio
|
|
99
|
+
async def test_layer_2_brackets_with_compacting_true_then_false():
|
|
100
|
+
opt = _make_optimizer(estimated=200_000)
|
|
101
|
+
|
|
102
|
+
async def fake_call(
|
|
103
|
+
system_prompt,
|
|
104
|
+
user_prompt,
|
|
105
|
+
run_metrics=None,
|
|
106
|
+
progress_label="layer 2",
|
|
107
|
+
**kwargs,
|
|
108
|
+
):
|
|
109
|
+
return "digest", 5, 3
|
|
110
|
+
|
|
111
|
+
with (
|
|
112
|
+
patch.object(co, "get_model", side_effect=lambda m: m),
|
|
113
|
+
patch.object(opt, "_run_llm_compaction_call", side_effect=fake_call),
|
|
114
|
+
):
|
|
115
|
+
from agno.models.message import Message
|
|
116
|
+
|
|
117
|
+
opt.model = MagicMock(id="mock-model")
|
|
118
|
+
opt.model.get_provider = lambda: "mock"
|
|
119
|
+
messages = [
|
|
120
|
+
Message(role="system", content="sys"),
|
|
121
|
+
Message(role="user", content="u"),
|
|
122
|
+
]
|
|
123
|
+
await opt.layer_2_auto_compact(messages=messages, trigger="auto")
|
|
124
|
+
await _drain_pending_tasks()
|
|
125
|
+
|
|
126
|
+
calls = _status_calls(opt)
|
|
127
|
+
assert len(calls) >= 2
|
|
128
|
+
assert calls[0].kwargs["data"].compacting is True
|
|
129
|
+
assert calls[-1].kwargs["data"].compacting is False
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
@pytest.mark.asyncio
|
|
133
|
+
async def test_layer_2_error_path_still_drops_compacting_flag():
|
|
134
|
+
opt = _make_optimizer(estimated=200_000)
|
|
135
|
+
|
|
136
|
+
async def failing_call(*a, **kw):
|
|
137
|
+
raise RuntimeError("provider exploded")
|
|
138
|
+
|
|
139
|
+
with (
|
|
140
|
+
patch.object(co, "get_model", side_effect=lambda m: m),
|
|
141
|
+
patch.object(opt, "_run_llm_compaction_call", side_effect=failing_call),
|
|
142
|
+
):
|
|
143
|
+
from agno.models.message import Message
|
|
144
|
+
|
|
145
|
+
opt.model = MagicMock(id="mock-model")
|
|
146
|
+
opt.model.get_provider = lambda: "mock"
|
|
147
|
+
await opt.layer_2_auto_compact(
|
|
148
|
+
messages=[Message(role="system", content="sys")],
|
|
149
|
+
trigger="auto",
|
|
150
|
+
)
|
|
151
|
+
await _drain_pending_tasks()
|
|
152
|
+
|
|
153
|
+
calls = _status_calls(opt)
|
|
154
|
+
assert calls, "expected at least one context_status event"
|
|
155
|
+
assert calls[-1].kwargs["data"].compacting is False
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@pytest.mark.asyncio
|
|
159
|
+
async def test_acompress_swallows_status_publish_failure():
|
|
160
|
+
opt = _make_optimizer(estimated=50_000)
|
|
161
|
+
opt._push_activity_event.side_effect = RuntimeError("redis down")
|
|
162
|
+
|
|
163
|
+
await opt.acompress(messages=[_mk_msg()])
|
|
164
|
+
await _drain_pending_tasks()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/action_ledger.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/constants.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/encryption.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/error_patterns.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/finalize_mode.py
RENAMED
|
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
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/core/context_optimizer/prompts.py
RENAMED
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/agent_list.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/models/knowledge_bases.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/sub_modules/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/sub_modules/agent.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/agents/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/backend_module.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/decorators/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/events_registry.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/frameworks/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/frameworks/dispatch.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/extra_headers.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/backend/utils/mcp_oauth.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_boot.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_shutdown.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_task.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/decorators/on_tool.py
RENAMED
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/models/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/models/deployments.py
RENAMED
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/streaming_server.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/utils/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/events/utils/git_init.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/knowledge_bases/__init__.py
RENAMED
|
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
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/models/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/models/tasks_list.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/sub_modules/__init__.py
RENAMED
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tasks/sub_modules/task.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{xpander_sdk-2.0.332 → xpander_sdk-2.0.334}/src/xpander_sdk/modules/tools_repository/models/mcp.py
RENAMED
|
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
|
|
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
|