agentpool 2.1.9__py3-none-any.whl → 2.5.0__py3-none-any.whl
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.
- acp/__init__.py +13 -4
- acp/acp_requests.py +20 -77
- acp/agent/connection.py +8 -0
- acp/agent/implementations/debug_server/debug_server.py +6 -2
- acp/agent/protocol.py +6 -0
- acp/bridge/README.md +15 -2
- acp/bridge/__init__.py +3 -2
- acp/bridge/__main__.py +60 -19
- acp/bridge/ws_server.py +173 -0
- acp/bridge/ws_server_cli.py +89 -0
- acp/client/connection.py +38 -29
- acp/client/implementations/default_client.py +3 -2
- acp/client/implementations/headless_client.py +2 -2
- acp/connection.py +2 -2
- acp/notifications.py +20 -50
- acp/schema/__init__.py +2 -0
- acp/schema/agent_responses.py +21 -0
- acp/schema/client_requests.py +3 -3
- acp/schema/session_state.py +63 -29
- acp/stdio.py +39 -9
- acp/task/supervisor.py +2 -2
- acp/transports.py +362 -2
- acp/utils.py +17 -4
- agentpool/__init__.py +6 -1
- agentpool/agents/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +407 -277
- agentpool/agents/acp_agent/acp_converters.py +196 -38
- agentpool/agents/acp_agent/client_handler.py +191 -26
- agentpool/agents/acp_agent/session_state.py +17 -6
- agentpool/agents/agent.py +607 -572
- agentpool/agents/agui_agent/__init__.py +0 -2
- agentpool/agents/agui_agent/agui_agent.py +176 -110
- agentpool/agents/agui_agent/agui_converters.py +0 -131
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +632 -17
- agentpool/agents/claude_code_agent/FORKING.md +191 -0
- agentpool/agents/claude_code_agent/__init__.py +13 -1
- agentpool/agents/claude_code_agent/claude_code_agent.py +1058 -291
- agentpool/agents/claude_code_agent/converters.py +74 -143
- agentpool/agents/claude_code_agent/history.py +474 -0
- agentpool/agents/claude_code_agent/models.py +77 -0
- agentpool/agents/claude_code_agent/static_info.py +100 -0
- agentpool/agents/claude_code_agent/usage.py +242 -0
- agentpool/agents/context.py +40 -0
- agentpool/agents/events/__init__.py +24 -0
- agentpool/agents/events/builtin_handlers.py +67 -1
- agentpool/agents/events/event_emitter.py +32 -2
- agentpool/agents/events/events.py +104 -3
- agentpool/agents/events/infer_info.py +145 -0
- agentpool/agents/events/processors.py +254 -0
- agentpool/agents/interactions.py +41 -6
- agentpool/agents/modes.py +67 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_call_accumulator.py +213 -0
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +56 -21
- agentpool/config_resources/__init__.py +38 -1
- agentpool/config_resources/acp_assistant.yml +2 -2
- agentpool/config_resources/agents.yml +3 -0
- agentpool/config_resources/agents_template.yml +1 -0
- agentpool/config_resources/claude_code_agent.yml +10 -6
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +136 -289
- agentpool/delegation/team.py +58 -57
- agentpool/delegation/teamrun.py +51 -55
- agentpool/diagnostics/__init__.py +53 -0
- agentpool/diagnostics/lsp_manager.py +1593 -0
- agentpool/diagnostics/lsp_proxy.py +41 -0
- agentpool/diagnostics/lsp_proxy_script.py +229 -0
- agentpool/diagnostics/models.py +398 -0
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/__init__.py +0 -2
- agentpool/mcp_server/client.py +76 -32
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +34 -54
- agentpool/mcp_server/registries/official_registry_client.py +35 -1
- agentpool/mcp_server/tool_bridge.py +186 -139
- agentpool/messaging/__init__.py +0 -2
- agentpool/messaging/compaction.py +72 -197
- agentpool/messaging/connection_manager.py +11 -10
- agentpool/messaging/event_manager.py +5 -5
- agentpool/messaging/message_container.py +6 -30
- agentpool/messaging/message_history.py +99 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +54 -35
- agentpool/messaging/processing.py +12 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -24
- agentpool/models/acp_agents/mcp_capable.py +126 -157
- agentpool/models/acp_agents/non_mcp.py +129 -95
- agentpool/models/agents.py +98 -76
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +144 -19
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +113 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/repomap.py +1 -1
- agentpool/resource_providers/__init__.py +11 -1
- agentpool/resource_providers/aggregating.py +56 -5
- agentpool/resource_providers/base.py +70 -4
- agentpool/resource_providers/codemode/code_executor.py +72 -5
- agentpool/resource_providers/codemode/helpers.py +2 -2
- agentpool/resource_providers/codemode/provider.py +64 -12
- agentpool/resource_providers/codemode/remote_mcp_execution.py +2 -2
- agentpool/resource_providers/codemode/remote_provider.py +9 -12
- agentpool/resource_providers/filtering.py +3 -1
- agentpool/resource_providers/mcp_provider.py +89 -12
- agentpool/resource_providers/plan_provider.py +228 -46
- agentpool/resource_providers/pool.py +7 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +4 -2
- agentpool/sessions/__init__.py +4 -1
- agentpool/sessions/manager.py +33 -5
- agentpool/sessions/models.py +59 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/skills/registry.py +13 -8
- agentpool/storage/manager.py +572 -49
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +538 -20
- agentpool/tool_impls/__init__.py +6 -0
- agentpool/tool_impls/agent_cli/__init__.py +42 -0
- agentpool/tool_impls/agent_cli/tool.py +95 -0
- agentpool/tool_impls/bash/__init__.py +64 -0
- agentpool/tool_impls/bash/helpers.py +35 -0
- agentpool/tool_impls/bash/tool.py +171 -0
- agentpool/tool_impls/delete_path/__init__.py +70 -0
- agentpool/tool_impls/delete_path/tool.py +142 -0
- agentpool/tool_impls/download_file/__init__.py +80 -0
- agentpool/tool_impls/download_file/tool.py +183 -0
- agentpool/tool_impls/execute_code/__init__.py +55 -0
- agentpool/tool_impls/execute_code/tool.py +163 -0
- agentpool/tool_impls/grep/__init__.py +80 -0
- agentpool/tool_impls/grep/tool.py +200 -0
- agentpool/tool_impls/list_directory/__init__.py +73 -0
- agentpool/tool_impls/list_directory/tool.py +197 -0
- agentpool/tool_impls/question/__init__.py +42 -0
- agentpool/tool_impls/question/tool.py +127 -0
- agentpool/tool_impls/read/__init__.py +104 -0
- agentpool/tool_impls/read/tool.py +305 -0
- agentpool/tools/__init__.py +2 -1
- agentpool/tools/base.py +114 -34
- agentpool/tools/manager.py +57 -1
- agentpool/ui/base.py +2 -2
- agentpool/ui/mock_provider.py +2 -2
- agentpool/ui/stdlib_provider.py +2 -2
- agentpool/utils/file_watcher.py +269 -0
- agentpool/utils/identifiers.py +121 -0
- agentpool/utils/pydantic_ai_helpers.py +46 -0
- agentpool/utils/streams.py +616 -2
- agentpool/utils/subprocess_utils.py +155 -0
- agentpool/utils/token_breakdown.py +461 -0
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/METADATA +41 -27
- agentpool-2.5.0.dist-info/RECORD +579 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +24 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +100 -21
- agentpool_cli/serve_agui.py +87 -0
- agentpool_cli/serve_opencode.py +119 -0
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +42 -5
- agentpool_commands/agents.py +75 -2
- agentpool_commands/history.py +62 -0
- agentpool_commands/mcp.py +176 -0
- agentpool_commands/models.py +56 -3
- agentpool_commands/pool.py +260 -0
- agentpool_commands/session.py +1 -1
- agentpool_commands/text_sharing/__init__.py +119 -0
- agentpool_commands/text_sharing/base.py +123 -0
- agentpool_commands/text_sharing/github_gist.py +80 -0
- agentpool_commands/text_sharing/opencode.py +462 -0
- agentpool_commands/text_sharing/paste_rs.py +59 -0
- agentpool_commands/text_sharing/pastebin.py +116 -0
- agentpool_commands/text_sharing/shittycodingagent.py +112 -0
- agentpool_commands/tools.py +57 -0
- agentpool_commands/utils.py +80 -30
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -0
- agentpool_config/builtin_tools.py +77 -22
- agentpool_config/commands.py +24 -1
- agentpool_config/compaction.py +258 -0
- agentpool_config/converters.py +1 -1
- agentpool_config/event_handlers.py +42 -0
- agentpool_config/events.py +1 -1
- agentpool_config/forward_targets.py +1 -4
- agentpool_config/jinja.py +3 -3
- agentpool_config/mcp_server.py +132 -6
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +82 -38
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -22
- agentpool_config/toolsets.py +109 -233
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +234 -181
- agentpool_server/acp_server/commands/acp_commands.py +151 -156
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +18 -17
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +24 -90
- agentpool_server/acp_server/session.py +173 -331
- agentpool_server/acp_server/session_manager.py +8 -34
- agentpool_server/agui_server/server.py +3 -1
- agentpool_server/mcp_server/server.py +5 -2
- agentpool_server/opencode_server/.rules +95 -0
- agentpool_server/opencode_server/ENDPOINTS.md +401 -0
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +19 -0
- agentpool_server/opencode_server/command_validation.py +172 -0
- agentpool_server/opencode_server/converters.py +975 -0
- agentpool_server/opencode_server/dependencies.py +24 -0
- agentpool_server/opencode_server/input_provider.py +421 -0
- agentpool_server/opencode_server/models/__init__.py +250 -0
- agentpool_server/opencode_server/models/agent.py +53 -0
- agentpool_server/opencode_server/models/app.py +72 -0
- agentpool_server/opencode_server/models/base.py +26 -0
- agentpool_server/opencode_server/models/common.py +23 -0
- agentpool_server/opencode_server/models/config.py +37 -0
- agentpool_server/opencode_server/models/events.py +821 -0
- agentpool_server/opencode_server/models/file.py +88 -0
- agentpool_server/opencode_server/models/mcp.py +44 -0
- agentpool_server/opencode_server/models/message.py +179 -0
- agentpool_server/opencode_server/models/parts.py +323 -0
- agentpool_server/opencode_server/models/provider.py +81 -0
- agentpool_server/opencode_server/models/pty.py +43 -0
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +111 -0
- agentpool_server/opencode_server/routes/__init__.py +29 -0
- agentpool_server/opencode_server/routes/agent_routes.py +473 -0
- agentpool_server/opencode_server/routes/app_routes.py +202 -0
- agentpool_server/opencode_server/routes/config_routes.py +302 -0
- agentpool_server/opencode_server/routes/file_routes.py +571 -0
- agentpool_server/opencode_server/routes/global_routes.py +94 -0
- agentpool_server/opencode_server/routes/lsp_routes.py +319 -0
- agentpool_server/opencode_server/routes/message_routes.py +761 -0
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +300 -0
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +1276 -0
- agentpool_server/opencode_server/routes/tui_routes.py +139 -0
- agentpool_server/opencode_server/server.py +475 -0
- agentpool_server/opencode_server/state.py +151 -0
- agentpool_server/opencode_server/time_utils.py +8 -0
- agentpool_storage/__init__.py +12 -0
- agentpool_storage/base.py +184 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/claude_provider/provider.py +1089 -0
- agentpool_storage/file_provider.py +278 -15
- agentpool_storage/memory_provider.py +193 -12
- agentpool_storage/models.py +3 -0
- agentpool_storage/opencode_provider/ARCHITECTURE.md +386 -0
- agentpool_storage/opencode_provider/__init__.py +16 -0
- agentpool_storage/opencode_provider/helpers.py +414 -0
- agentpool_storage/opencode_provider/provider.py +895 -0
- agentpool_storage/project_store.py +325 -0
- agentpool_storage/session_store.py +26 -6
- agentpool_storage/sql_provider/__init__.py +4 -2
- agentpool_storage/sql_provider/models.py +48 -0
- agentpool_storage/sql_provider/sql_provider.py +269 -3
- agentpool_storage/sql_provider/utils.py +12 -13
- agentpool_storage/zed_provider/__init__.py +16 -0
- agentpool_storage/zed_provider/helpers.py +281 -0
- agentpool_storage/zed_provider/models.py +130 -0
- agentpool_storage/zed_provider/provider.py +442 -0
- agentpool_storage/zed_provider.py +803 -0
- agentpool_toolsets/__init__.py +0 -2
- agentpool_toolsets/builtin/__init__.py +2 -12
- agentpool_toolsets/builtin/code.py +96 -57
- agentpool_toolsets/builtin/debug.py +118 -48
- agentpool_toolsets/builtin/execution_environment.py +115 -230
- agentpool_toolsets/builtin/file_edit/file_edit.py +115 -7
- agentpool_toolsets/builtin/skills.py +9 -4
- agentpool_toolsets/builtin/subagent_tools.py +64 -51
- agentpool_toolsets/builtin/workers.py +4 -2
- agentpool_toolsets/composio_toolset.py +2 -2
- agentpool_toolsets/entry_points.py +3 -1
- agentpool_toolsets/fsspec_toolset/__init__.py +13 -1
- agentpool_toolsets/fsspec_toolset/diagnostics.py +860 -73
- agentpool_toolsets/fsspec_toolset/grep.py +99 -7
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/image_utils.py +161 -0
- agentpool_toolsets/fsspec_toolset/toolset.py +500 -95
- agentpool_toolsets/mcp_discovery/__init__.py +5 -0
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +511 -0
- agentpool_toolsets/mcp_run_toolset.py +87 -12
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- agentpool-2.1.9.dist-info/RECORD +0 -474
- agentpool_config/resources.py +0 -33
- agentpool_server/acp_server/acp_tools.py +0 -43
- agentpool_server/acp_server/commands/spawn.py +0 -210
- agentpool_storage/text_log_provider.py +0 -275
- agentpool_toolsets/builtin/agent_management.py +0 -239
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/history.py +0 -36
- agentpool_toolsets/builtin/integration.py +0 -85
- agentpool_toolsets/builtin/tool_management.py +0 -90
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.1.9.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -22,7 +22,7 @@ Example:
|
|
|
22
22
|
compacted = await pipeline.apply(messages)
|
|
23
23
|
|
|
24
24
|
# Or via config (for YAML)
|
|
25
|
-
config =
|
|
25
|
+
config = CompactionConfig(steps=[
|
|
26
26
|
FilterThinkingConfig(),
|
|
27
27
|
TruncateToolOutputsConfig(max_length=1000),
|
|
28
28
|
KeepLastMessagesConfig(count=10),
|
|
@@ -50,9 +50,8 @@ from __future__ import annotations
|
|
|
50
50
|
from abc import ABC, abstractmethod
|
|
51
51
|
from collections.abc import Sequence
|
|
52
52
|
from dataclasses import dataclass, field, replace
|
|
53
|
-
from typing import TYPE_CHECKING,
|
|
53
|
+
from typing import TYPE_CHECKING, Any, Self, cast
|
|
54
54
|
|
|
55
|
-
from pydantic import BaseModel, Field
|
|
56
55
|
from pydantic_ai import (
|
|
57
56
|
Agent,
|
|
58
57
|
BinaryContent,
|
|
@@ -73,6 +72,8 @@ if TYPE_CHECKING:
|
|
|
73
72
|
from pydantic_ai import ModelRequestPart, ModelResponsePart
|
|
74
73
|
from tokonomics.model_names import ModelId
|
|
75
74
|
|
|
75
|
+
from agentpool.messaging.message_history import MessageHistory
|
|
76
|
+
|
|
76
77
|
# Type aliases
|
|
77
78
|
ModelMessage = ModelRequest | ModelResponse
|
|
78
79
|
MessageSequence = Sequence[ModelMessage]
|
|
@@ -629,200 +630,6 @@ class WhenMessageCountExceeds(CompactionStep):
|
|
|
629
630
|
return list(messages)
|
|
630
631
|
|
|
631
632
|
|
|
632
|
-
# =============================================================================
|
|
633
|
-
# Configuration Models - For YAML/JSON configuration
|
|
634
|
-
# =============================================================================
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
class FilterThinkingConfig(BaseModel):
|
|
638
|
-
"""Configuration for FilterThinking step."""
|
|
639
|
-
|
|
640
|
-
type: Literal["filter_thinking"] = "filter_thinking"
|
|
641
|
-
|
|
642
|
-
def build(self) -> FilterThinking:
|
|
643
|
-
return FilterThinking()
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
class FilterRetryPromptsConfig(BaseModel):
|
|
647
|
-
"""Configuration for FilterRetryPrompts step."""
|
|
648
|
-
|
|
649
|
-
type: Literal["filter_retry_prompts"] = "filter_retry_prompts"
|
|
650
|
-
|
|
651
|
-
def build(self) -> FilterRetryPrompts:
|
|
652
|
-
return FilterRetryPrompts()
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
class FilterBinaryContentConfig(BaseModel):
|
|
656
|
-
"""Configuration for FilterBinaryContent step."""
|
|
657
|
-
|
|
658
|
-
type: Literal["filter_binary"] = "filter_binary"
|
|
659
|
-
keep_references: bool = False
|
|
660
|
-
|
|
661
|
-
def build(self) -> FilterBinaryContent:
|
|
662
|
-
return FilterBinaryContent(keep_references=self.keep_references)
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
class FilterToolCallsConfig(BaseModel):
|
|
666
|
-
"""Configuration for FilterToolCalls step."""
|
|
667
|
-
|
|
668
|
-
type: Literal["filter_tools"] = "filter_tools"
|
|
669
|
-
exclude_tools: list[str] = Field(default_factory=list)
|
|
670
|
-
include_only: list[str] | None = None
|
|
671
|
-
|
|
672
|
-
def build(self) -> FilterToolCalls:
|
|
673
|
-
return FilterToolCalls(exclude_tools=self.exclude_tools, include_only=self.include_only)
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
class FilterEmptyMessagesConfig(BaseModel):
|
|
677
|
-
"""Configuration for FilterEmptyMessages step."""
|
|
678
|
-
|
|
679
|
-
type: Literal["filter_empty"] = "filter_empty"
|
|
680
|
-
|
|
681
|
-
def build(self) -> FilterEmptyMessages:
|
|
682
|
-
return FilterEmptyMessages()
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
class TruncateToolOutputsConfig(BaseModel):
|
|
686
|
-
"""Configuration for TruncateToolOutputs step."""
|
|
687
|
-
|
|
688
|
-
type: Literal["truncate_tool_outputs"] = "truncate_tool_outputs"
|
|
689
|
-
max_length: int = 2000
|
|
690
|
-
suffix: str = "\n... [truncated]"
|
|
691
|
-
|
|
692
|
-
def build(self) -> TruncateToolOutputs:
|
|
693
|
-
return TruncateToolOutputs(max_length=self.max_length, suffix=self.suffix)
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
class TruncateTextPartsConfig(BaseModel):
|
|
697
|
-
"""Configuration for TruncateTextParts step."""
|
|
698
|
-
|
|
699
|
-
type: Literal["truncate_text"] = "truncate_text"
|
|
700
|
-
max_length: int = 5000
|
|
701
|
-
suffix: str = "\n... [truncated]"
|
|
702
|
-
|
|
703
|
-
def build(self) -> TruncateTextParts:
|
|
704
|
-
return TruncateTextParts(max_length=self.max_length, suffix=self.suffix)
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
class KeepLastMessagesConfig(BaseModel):
|
|
708
|
-
"""Configuration for KeepLastMessages step."""
|
|
709
|
-
|
|
710
|
-
type: Literal["keep_last"] = "keep_last"
|
|
711
|
-
count: int = 10
|
|
712
|
-
count_pairs: bool = True
|
|
713
|
-
|
|
714
|
-
def build(self) -> KeepLastMessages:
|
|
715
|
-
return KeepLastMessages(count=self.count, count_pairs=self.count_pairs)
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
class KeepFirstMessagesConfig(BaseModel):
|
|
719
|
-
"""Configuration for KeepFirstMessages step."""
|
|
720
|
-
|
|
721
|
-
type: Literal["keep_first"] = "keep_first"
|
|
722
|
-
count: int = 2
|
|
723
|
-
|
|
724
|
-
def build(self) -> KeepFirstMessages:
|
|
725
|
-
return KeepFirstMessages(count=self.count)
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
class KeepFirstAndLastConfig(BaseModel):
|
|
729
|
-
"""Configuration for KeepFirstAndLast step."""
|
|
730
|
-
|
|
731
|
-
type: Literal["keep_first_last"] = "keep_first_last"
|
|
732
|
-
first_count: int = 2
|
|
733
|
-
last_count: int = 5
|
|
734
|
-
|
|
735
|
-
def build(self) -> KeepFirstAndLast:
|
|
736
|
-
return KeepFirstAndLast(first_count=self.first_count, last_count=self.last_count)
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
class TokenBudgetConfig(BaseModel):
|
|
740
|
-
"""Configuration for TokenBudget step."""
|
|
741
|
-
|
|
742
|
-
type: Literal["token_budget"] = "token_budget"
|
|
743
|
-
max_tokens: int = 4000
|
|
744
|
-
model: str = "gpt-4o"
|
|
745
|
-
|
|
746
|
-
def build(self) -> TokenBudget:
|
|
747
|
-
return TokenBudget(max_tokens=self.max_tokens, model=self.model)
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
class SummarizeConfig(BaseModel):
|
|
751
|
-
"""Configuration for Summarize step."""
|
|
752
|
-
|
|
753
|
-
type: Literal["summarize"] = "summarize"
|
|
754
|
-
model: str = "openai:gpt-4o-mini"
|
|
755
|
-
threshold: int = 15
|
|
756
|
-
keep_recent: int = 5
|
|
757
|
-
summary_prompt: str | None = None
|
|
758
|
-
|
|
759
|
-
def build(self) -> Summarize:
|
|
760
|
-
kwargs: dict[str, Any] = {
|
|
761
|
-
"model": self.model,
|
|
762
|
-
"threshold": self.threshold,
|
|
763
|
-
"keep_recent": self.keep_recent,
|
|
764
|
-
}
|
|
765
|
-
if self.summary_prompt:
|
|
766
|
-
kwargs["summary_prompt"] = self.summary_prompt
|
|
767
|
-
return Summarize(**kwargs)
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
class WhenMessageCountExceedsConfig(BaseModel):
|
|
771
|
-
"""Configuration for WhenMessageCountExceeds wrapper."""
|
|
772
|
-
|
|
773
|
-
type: Literal["when_count_exceeds"] = "when_count_exceeds"
|
|
774
|
-
threshold: int = 20
|
|
775
|
-
step: "CompactionStepConfig"
|
|
776
|
-
|
|
777
|
-
def build(self) -> WhenMessageCountExceeds:
|
|
778
|
-
return WhenMessageCountExceeds(step=self.step.build(), threshold=self.threshold)
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
# Union of all config types with discriminator
|
|
782
|
-
CompactionStepConfig = Annotated[
|
|
783
|
-
FilterThinkingConfig
|
|
784
|
-
| FilterRetryPromptsConfig
|
|
785
|
-
| FilterBinaryContentConfig
|
|
786
|
-
| FilterToolCallsConfig
|
|
787
|
-
| FilterEmptyMessagesConfig
|
|
788
|
-
| TruncateToolOutputsConfig
|
|
789
|
-
| TruncateTextPartsConfig
|
|
790
|
-
| KeepLastMessagesConfig
|
|
791
|
-
| KeepFirstMessagesConfig
|
|
792
|
-
| KeepFirstAndLastConfig
|
|
793
|
-
| TokenBudgetConfig
|
|
794
|
-
| SummarizeConfig
|
|
795
|
-
| WhenMessageCountExceedsConfig,
|
|
796
|
-
Field(discriminator="type"),
|
|
797
|
-
]
|
|
798
|
-
|
|
799
|
-
# Update forward reference
|
|
800
|
-
WhenMessageCountExceedsConfig.model_rebuild()
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
class CompactionPipelineConfig(BaseModel):
|
|
804
|
-
"""Configuration for a complete compaction pipeline.
|
|
805
|
-
|
|
806
|
-
Example YAML:
|
|
807
|
-
```yaml
|
|
808
|
-
compaction:
|
|
809
|
-
steps:
|
|
810
|
-
- type: filter_thinking
|
|
811
|
-
- type: truncate_tool_outputs
|
|
812
|
-
max_length: 1000
|
|
813
|
-
- type: keep_last
|
|
814
|
-
count: 10
|
|
815
|
-
```
|
|
816
|
-
"""
|
|
817
|
-
|
|
818
|
-
steps: list[CompactionStepConfig] = Field(default_factory=list)
|
|
819
|
-
"""Ordered list of compaction steps to apply."""
|
|
820
|
-
|
|
821
|
-
def build(self) -> CompactionPipeline:
|
|
822
|
-
"""Build a CompactionPipeline from this configuration."""
|
|
823
|
-
return CompactionPipeline(steps=[step.build() for step in self.steps])
|
|
824
|
-
|
|
825
|
-
|
|
826
633
|
# =============================================================================
|
|
827
634
|
# Preset Pipelines - Common configurations
|
|
828
635
|
# =============================================================================
|
|
@@ -877,6 +684,74 @@ def summarizing_context(model: ModelId | str = "openai:gpt-4o-mini") -> Compacti
|
|
|
877
684
|
# =============================================================================
|
|
878
685
|
|
|
879
686
|
|
|
687
|
+
async def compact_conversation(
|
|
688
|
+
pipeline: CompactionPipeline,
|
|
689
|
+
conversation: MessageHistory,
|
|
690
|
+
) -> tuple[int, int]:
|
|
691
|
+
"""Apply a compaction pipeline to a conversation's message history.
|
|
692
|
+
|
|
693
|
+
Extracts model messages from ChatMessages, applies the pipeline,
|
|
694
|
+
and rebuilds the conversation history with compacted messages.
|
|
695
|
+
|
|
696
|
+
Args:
|
|
697
|
+
pipeline: The compaction pipeline to apply
|
|
698
|
+
conversation: The MessageHistory to compact
|
|
699
|
+
|
|
700
|
+
Returns:
|
|
701
|
+
Tuple of (original_count, compacted_count) of model messages
|
|
702
|
+
"""
|
|
703
|
+
from agentpool.messaging.messages import ChatMessage
|
|
704
|
+
|
|
705
|
+
chat_messages = conversation.get_history()
|
|
706
|
+
if not chat_messages:
|
|
707
|
+
return 0, 0
|
|
708
|
+
|
|
709
|
+
# Extract ModelRequest/ModelResponse from ChatMessage.messages
|
|
710
|
+
model_messages: list[ModelMessage] = []
|
|
711
|
+
for chat_msg in chat_messages:
|
|
712
|
+
if chat_msg.messages:
|
|
713
|
+
model_messages.extend(chat_msg.messages)
|
|
714
|
+
|
|
715
|
+
if not model_messages:
|
|
716
|
+
return 0, 0
|
|
717
|
+
|
|
718
|
+
original_count = len(model_messages)
|
|
719
|
+
|
|
720
|
+
# Apply the compaction pipeline
|
|
721
|
+
compacted = await pipeline.apply(model_messages)
|
|
722
|
+
|
|
723
|
+
# Rebuild ChatMessages from compacted model messages
|
|
724
|
+
new_chat_messages: list[ChatMessage[Any]] = []
|
|
725
|
+
current_msgs: list[ModelMessage] = []
|
|
726
|
+
|
|
727
|
+
for msg in compacted:
|
|
728
|
+
current_msgs.append(msg)
|
|
729
|
+
if isinstance(msg, ModelResponse):
|
|
730
|
+
new_chat_messages.append(
|
|
731
|
+
ChatMessage(
|
|
732
|
+
content="[compacted]",
|
|
733
|
+
role="assistant",
|
|
734
|
+
messages=list(current_msgs),
|
|
735
|
+
)
|
|
736
|
+
)
|
|
737
|
+
current_msgs = []
|
|
738
|
+
|
|
739
|
+
# Handle any remaining messages (incomplete pair)
|
|
740
|
+
if current_msgs:
|
|
741
|
+
new_chat_messages.append(
|
|
742
|
+
ChatMessage(
|
|
743
|
+
content="[compacted]",
|
|
744
|
+
role="user",
|
|
745
|
+
messages=list(current_msgs),
|
|
746
|
+
)
|
|
747
|
+
)
|
|
748
|
+
|
|
749
|
+
# Update the conversation history
|
|
750
|
+
conversation.set_history(new_chat_messages)
|
|
751
|
+
|
|
752
|
+
return original_count, len(compacted)
|
|
753
|
+
|
|
754
|
+
|
|
880
755
|
def _extract_text_content(msg: ModelMessage) -> str:
|
|
881
756
|
"""Extract text content from a message for token counting."""
|
|
882
757
|
parts_text: list[str] = []
|
|
@@ -6,7 +6,8 @@ from collections.abc import Sequence
|
|
|
6
6
|
from contextlib import asynccontextmanager
|
|
7
7
|
from typing import TYPE_CHECKING, Any, Self
|
|
8
8
|
|
|
9
|
-
from
|
|
9
|
+
from anyenv.signals import Signal
|
|
10
|
+
from psygnal import Signal as Psygnal
|
|
10
11
|
from psygnal.containers import EventedList
|
|
11
12
|
|
|
12
13
|
from agentpool.log import get_logger
|
|
@@ -27,10 +28,10 @@ logger = get_logger(__name__)
|
|
|
27
28
|
class ConnectionManager:
|
|
28
29
|
"""Manages connections for both Agents and Teams."""
|
|
29
30
|
|
|
30
|
-
connection_processed = Signal
|
|
31
|
+
connection_processed = Signal[Talk.ConnectionProcessed]()
|
|
31
32
|
|
|
32
|
-
node_connected =
|
|
33
|
-
connection_added =
|
|
33
|
+
node_connected = Psygnal(object) # Node
|
|
34
|
+
connection_added = Psygnal(Talk) # Agent
|
|
34
35
|
|
|
35
36
|
def __init__(self, owner: MessageNode[Any, Any]) -> None:
|
|
36
37
|
self.owner = owner
|
|
@@ -54,9 +55,9 @@ class ConnectionManager:
|
|
|
54
55
|
old.connection_processed.disconnect(self._handle_message_flow)
|
|
55
56
|
new.connection_processed.connect(self._handle_message_flow)
|
|
56
57
|
|
|
57
|
-
def _handle_message_flow(self, event: Talk.ConnectionProcessed) -> None:
|
|
58
|
+
async def _handle_message_flow(self, event: Talk.ConnectionProcessed) -> None:
|
|
58
59
|
"""Forward message flow to our aggregated signal."""
|
|
59
|
-
self.connection_processed.emit(event)
|
|
60
|
+
await self.connection_processed.emit(event)
|
|
60
61
|
|
|
61
62
|
def set_wait_state(self, target: MessageNode[Any, Any] | AgentName, wait: bool = True) -> None:
|
|
62
63
|
"""Set waiting behavior for target."""
|
|
@@ -308,10 +309,10 @@ class ConnectionManager:
|
|
|
308
309
|
if __name__ == "__main__":
|
|
309
310
|
from agentpool.agents import Agent
|
|
310
311
|
|
|
311
|
-
agent = Agent("test_agent")
|
|
312
|
-
agent_2 = Agent("test_agent_2")
|
|
313
|
-
agent_3 = Agent("test_agent_3")
|
|
314
|
-
agent_4 = Agent("test_agent_4")
|
|
312
|
+
agent = Agent("test_agent", model="openai:gpt-5-nano")
|
|
313
|
+
agent_2 = Agent("test_agent_2", model="openai:gpt-5-nano")
|
|
314
|
+
agent_3 = Agent("test_agent_3", model="openai:gpt-5-nano")
|
|
315
|
+
agent_4 = Agent("test_agent_4", model="openai:gpt-5-nano")
|
|
315
316
|
_conn_1 = agent >> agent_2
|
|
316
317
|
_conn_2 = agent >> agent_3
|
|
317
318
|
_conn_3 = agent_2 >> agent_4
|
|
@@ -10,9 +10,9 @@ from functools import wraps
|
|
|
10
10
|
import inspect
|
|
11
11
|
from typing import TYPE_CHECKING, Any, Self
|
|
12
12
|
|
|
13
|
-
from
|
|
13
|
+
from anyenv.signals import Signal
|
|
14
14
|
from evented.event_data import EventData, FunctionResultEventData
|
|
15
|
-
from
|
|
15
|
+
from evented_config import EmailConfig, FileWatchConfig, TimeEventConfig, WebhookConfig
|
|
16
16
|
from pydantic import SecretStr
|
|
17
17
|
|
|
18
18
|
from agentpool.log import get_logger
|
|
@@ -27,8 +27,8 @@ if TYPE_CHECKING:
|
|
|
27
27
|
from types import TracebackType
|
|
28
28
|
|
|
29
29
|
from evented.base import EventSource
|
|
30
|
-
from evented.configs import EventConfig
|
|
31
30
|
from evented.timed_watcher import TimeEventSource
|
|
31
|
+
from evented_config import EventConfig
|
|
32
32
|
|
|
33
33
|
|
|
34
34
|
logger = get_logger(__name__)
|
|
@@ -40,7 +40,7 @@ type EventCallback = Callable[[EventData], None | Awaitable[None]]
|
|
|
40
40
|
class EventManager:
|
|
41
41
|
"""Manages multiple event sources and their lifecycles."""
|
|
42
42
|
|
|
43
|
-
event_processed = Signal(
|
|
43
|
+
event_processed = Signal[EventData]()
|
|
44
44
|
|
|
45
45
|
def __init__(
|
|
46
46
|
self,
|
|
@@ -83,7 +83,7 @@ class EventManager:
|
|
|
83
83
|
except Exception:
|
|
84
84
|
logger.exception("Error in event callback", name=get_fn_name(callback))
|
|
85
85
|
|
|
86
|
-
self.event_processed.emit(event)
|
|
86
|
+
await self.event_processed.emit(event)
|
|
87
87
|
|
|
88
88
|
async def add_file_watch(
|
|
89
89
|
self,
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
import itertools
|
|
6
5
|
from typing import TYPE_CHECKING, Any, Literal
|
|
7
6
|
|
|
8
7
|
from psygnal.containers import EventedList
|
|
@@ -145,36 +144,13 @@ class ChatMessageList(EventedList[ChatMessage[Any]]):
|
|
|
145
144
|
message: Message to build flow DAG for
|
|
146
145
|
|
|
147
146
|
Returns:
|
|
148
|
-
Root DAGNode of the graph
|
|
147
|
+
Root DAGNode of the graph, or None
|
|
148
|
+
|
|
149
|
+
Note:
|
|
150
|
+
forwarded_from has been removed. This method now returns None.
|
|
151
|
+
Flow tracking can be reconstructed from parent_id chain or pool history.
|
|
149
152
|
"""
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
# Get messages from this conversation
|
|
153
|
-
conv_messages = [m for m in self if m.conversation_id == message.conversation_id]
|
|
154
|
-
nodes: dict[str, DAGNode] = {}
|
|
155
|
-
for msg in conv_messages: # First create all nodes
|
|
156
|
-
if msg.forwarded_from:
|
|
157
|
-
chain = [*msg.forwarded_from, msg.name or "unknown"]
|
|
158
|
-
for name in chain:
|
|
159
|
-
if name not in nodes:
|
|
160
|
-
nodes[name] = DAGNode(name)
|
|
161
|
-
|
|
162
|
-
# Then set up parent relationships
|
|
163
|
-
for msg in conv_messages:
|
|
164
|
-
if msg.forwarded_from:
|
|
165
|
-
chain = [*msg.forwarded_from, msg.name or "unknown"]
|
|
166
|
-
# Connect consecutive nodes
|
|
167
|
-
for parent_name, child_name in itertools.pairwise(chain):
|
|
168
|
-
parent = nodes[parent_name]
|
|
169
|
-
child = nodes[child_name]
|
|
170
|
-
if parent not in child.parents:
|
|
171
|
-
child.add_parent(parent)
|
|
172
|
-
|
|
173
|
-
# Find root nodes (those without parents)
|
|
174
|
-
roots = [node for node in nodes.values() if node.is_root]
|
|
175
|
-
if not roots:
|
|
176
|
-
return None
|
|
177
|
-
return roots[0] # Return first root for now
|
|
153
|
+
return None
|
|
178
154
|
|
|
179
155
|
def to_mermaid_graph(
|
|
180
156
|
self,
|
|
@@ -6,10 +6,11 @@ import asyncio
|
|
|
6
6
|
from collections import deque
|
|
7
7
|
from contextlib import asynccontextmanager
|
|
8
8
|
from dataclasses import dataclass, field
|
|
9
|
+
import json
|
|
9
10
|
from typing import TYPE_CHECKING, Any, Self, assert_never
|
|
10
11
|
from uuid import UUID, uuid4
|
|
11
12
|
|
|
12
|
-
from
|
|
13
|
+
from anyenv.signals import Signal
|
|
13
14
|
from upathtools import read_path, to_upath
|
|
14
15
|
|
|
15
16
|
from agentpool.log import get_logger
|
|
@@ -24,6 +25,7 @@ if TYPE_CHECKING:
|
|
|
24
25
|
from datetime import datetime
|
|
25
26
|
from types import TracebackType
|
|
26
27
|
|
|
28
|
+
from fsspec.asyn import AsyncFileSystem
|
|
27
29
|
from pydantic_ai import UserContent
|
|
28
30
|
from toprompt import AnyPromptType
|
|
29
31
|
from upathtools import JoinablePathLike
|
|
@@ -48,7 +50,7 @@ class MessageHistory:
|
|
|
48
50
|
session_id: str
|
|
49
51
|
timestamp: datetime = field(default_factory=get_now)
|
|
50
52
|
|
|
51
|
-
history_cleared = Signal(
|
|
53
|
+
history_cleared = Signal[HistoryCleared]()
|
|
52
54
|
|
|
53
55
|
def __init__(
|
|
54
56
|
self,
|
|
@@ -94,6 +96,14 @@ class MessageHistory:
|
|
|
94
96
|
# Note: max_messages and max_tokens will be handled in add_message/get_history
|
|
95
97
|
# to maintain the rolling window during conversation
|
|
96
98
|
|
|
99
|
+
# Filesystem for message history
|
|
100
|
+
from fsspec.implementations.asyn_wrapper import AsyncFileSystemWrapper
|
|
101
|
+
from fsspec.implementations.memory import MemoryFileSystem
|
|
102
|
+
|
|
103
|
+
self._memory_fs = MemoryFileSystem()
|
|
104
|
+
self._fs = AsyncFileSystemWrapper(self._memory_fs)
|
|
105
|
+
self._fs_initialized = False
|
|
106
|
+
|
|
97
107
|
@property
|
|
98
108
|
def storage(self) -> StorageManager:
|
|
99
109
|
return self._storage
|
|
@@ -150,17 +160,20 @@ class MessageHistory:
|
|
|
150
160
|
self,
|
|
151
161
|
*,
|
|
152
162
|
max_tokens: int | None = None,
|
|
153
|
-
include_system: bool = False,
|
|
154
163
|
format_template: str | None = None,
|
|
155
|
-
num_messages: int | None = None,
|
|
164
|
+
num_messages: int | None = None,
|
|
156
165
|
) -> str:
|
|
157
166
|
"""Format conversation history as a single context message.
|
|
158
167
|
|
|
159
168
|
Args:
|
|
160
169
|
max_tokens: Optional limit to include only last N tokens
|
|
161
|
-
include_system: Whether to include system messages
|
|
162
170
|
format_template: Optional custom format (defaults to agent/message pairs)
|
|
163
171
|
num_messages: Optional limit to include only last N messages
|
|
172
|
+
|
|
173
|
+
Note:
|
|
174
|
+
System prompts are stored as metadata (ModelRequest.instructions),
|
|
175
|
+
not as separate messages with role="system". ChatMessage.role only
|
|
176
|
+
supports "user" and "assistant".
|
|
164
177
|
"""
|
|
165
178
|
template = format_template or "Agent {agent}: {content}\n"
|
|
166
179
|
messages: list[str] = []
|
|
@@ -323,14 +336,14 @@ class MessageHistory:
|
|
|
323
336
|
self.chat_messages.clear()
|
|
324
337
|
self.chat_messages.extend(history)
|
|
325
338
|
|
|
326
|
-
def clear(self) -> None:
|
|
339
|
+
async def clear(self) -> None:
|
|
327
340
|
"""Clear conversation history and prompts."""
|
|
328
341
|
from agentpool.messaging import ChatMessageList
|
|
329
342
|
|
|
330
343
|
self.chat_messages = ChatMessageList()
|
|
331
344
|
self._last_messages = []
|
|
332
345
|
event = self.HistoryCleared(session_id=str(self.id))
|
|
333
|
-
self.history_cleared.emit(event)
|
|
346
|
+
await self.history_cleared.emit(event)
|
|
334
347
|
|
|
335
348
|
@asynccontextmanager
|
|
336
349
|
async def temporary_state(
|
|
@@ -477,12 +490,90 @@ class MessageHistory:
|
|
|
477
490
|
# Use cost_info if available
|
|
478
491
|
return self.chat_messages.get_history_tokens()
|
|
479
492
|
|
|
493
|
+
def get_last_message_id(self) -> str | None:
|
|
494
|
+
"""Get the message_id of the last message in history.
|
|
495
|
+
|
|
496
|
+
Used for setting parent_id on new messages to build the message tree.
|
|
497
|
+
|
|
498
|
+
Returns:
|
|
499
|
+
The message_id of the last message, or None if history is empty.
|
|
500
|
+
"""
|
|
501
|
+
if not self.chat_messages:
|
|
502
|
+
return None
|
|
503
|
+
return self.chat_messages[-1].message_id
|
|
504
|
+
|
|
505
|
+
def _update_filesystem(self) -> None:
|
|
506
|
+
"""Update filesystem with current message history."""
|
|
507
|
+
# Clear existing files
|
|
508
|
+
self._memory_fs.store.clear()
|
|
509
|
+
self._memory_fs.pseudo_dirs.clear()
|
|
510
|
+
|
|
511
|
+
# Create directory structure
|
|
512
|
+
self._memory_fs.makedirs("messages", exist_ok=True)
|
|
513
|
+
self._memory_fs.makedirs("by_role", exist_ok=True)
|
|
514
|
+
|
|
515
|
+
for msg in self.chat_messages:
|
|
516
|
+
# Format: {timestamp}_{role}_{message_id}
|
|
517
|
+
timestamp = msg.timestamp.strftime("%Y%m%d_%H%M%S_%f")
|
|
518
|
+
base_name = f"{timestamp}_{msg.role}_{msg.message_id}"
|
|
519
|
+
|
|
520
|
+
# Write message content
|
|
521
|
+
content_path = f"messages/{base_name}.txt"
|
|
522
|
+
content = str(msg.content)
|
|
523
|
+
self._memory_fs.pipe(content_path, content.encode("utf-8"))
|
|
524
|
+
|
|
525
|
+
# Write metadata
|
|
526
|
+
metadata = {
|
|
527
|
+
"message_id": msg.message_id,
|
|
528
|
+
"role": msg.role,
|
|
529
|
+
"timestamp": msg.timestamp.isoformat(),
|
|
530
|
+
"parent_id": msg.parent_id,
|
|
531
|
+
"model_name": msg.model_name,
|
|
532
|
+
"tokens": msg.usage.total_tokens if msg.usage else None,
|
|
533
|
+
"cost": float(msg.cost_info.total_cost) if msg.cost_info else None,
|
|
534
|
+
}
|
|
535
|
+
metadata_path = f"messages/{base_name}.json"
|
|
536
|
+
self._memory_fs.pipe(metadata_path, json.dumps(metadata, indent=2).encode("utf-8"))
|
|
537
|
+
|
|
538
|
+
# Create role-based directory symlinks (by storing paths)
|
|
539
|
+
role_dir = f"by_role/{msg.role}"
|
|
540
|
+
self._memory_fs.makedirs(role_dir, exist_ok=True)
|
|
541
|
+
|
|
542
|
+
# Write summary
|
|
543
|
+
summary = {
|
|
544
|
+
"session_id": self.id,
|
|
545
|
+
"total_messages": len(self.chat_messages),
|
|
546
|
+
"total_tokens": self.get_history_tokens(),
|
|
547
|
+
"total_cost": self.chat_messages.get_total_cost(),
|
|
548
|
+
"roles": {
|
|
549
|
+
"user": len([m for m in self.chat_messages if m.role == "user"]),
|
|
550
|
+
"assistant": len([m for m in self.chat_messages if m.role == "assistant"]),
|
|
551
|
+
},
|
|
552
|
+
}
|
|
553
|
+
self._memory_fs.pipe("summary.json", json.dumps(summary, indent=2).encode("utf-8"))
|
|
554
|
+
|
|
555
|
+
self._fs_initialized = True
|
|
556
|
+
|
|
557
|
+
def get_fs(self) -> AsyncFileSystem:
|
|
558
|
+
"""Get filesystem view of message history.
|
|
559
|
+
|
|
560
|
+
Returns:
|
|
561
|
+
AsyncFileSystem containing:
|
|
562
|
+
- messages/{timestamp}_{role}_{message_id}.txt - Message content
|
|
563
|
+
- messages/{timestamp}_{role}_{message_id}.json - Message metadata
|
|
564
|
+
- by_role/{role}/ - Messages organized by role
|
|
565
|
+
- summary.json - Conversation statistics
|
|
566
|
+
"""
|
|
567
|
+
# Update filesystem on access
|
|
568
|
+
self._update_filesystem()
|
|
569
|
+
return self._fs
|
|
570
|
+
|
|
480
571
|
|
|
481
572
|
if __name__ == "__main__":
|
|
482
573
|
from agentpool import Agent
|
|
483
574
|
|
|
484
575
|
async def main() -> None:
|
|
485
|
-
async with Agent() as agent:
|
|
576
|
+
async with Agent(model="openai:gpt-5-nano") as agent:
|
|
486
577
|
await agent.conversation.add_context_from_path("E:/mcp_zed.yml")
|
|
487
578
|
print(agent.conversation.get_history())
|
|
488
579
|
|