agentpool 2.2.3__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 +0 -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/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 +18 -49
- 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/task/supervisor.py +2 -2
- acp/utils.py +2 -2
- agentpool/__init__.py +2 -0
- agentpool/agents/acp_agent/acp_agent.py +278 -263
- agentpool/agents/acp_agent/acp_converters.py +150 -17
- agentpool/agents/acp_agent/client_handler.py +35 -24
- agentpool/agents/acp_agent/session_state.py +14 -6
- agentpool/agents/agent.py +471 -643
- agentpool/agents/agui_agent/agui_agent.py +104 -107
- agentpool/agents/agui_agent/helpers.py +3 -4
- agentpool/agents/base_agent.py +485 -32
- 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 +654 -334
- agentpool/agents/claude_code_agent/converters.py +4 -141
- 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/events/__init__.py +22 -0
- agentpool/agents/events/builtin_handlers.py +65 -0
- agentpool/agents/events/event_emitter.py +3 -0
- agentpool/agents/events/events.py +84 -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 +13 -0
- agentpool/agents/slashed_agent.py +5 -4
- agentpool/agents/tool_wrapping.py +18 -6
- agentpool/common_types.py +35 -21
- 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 +9 -8
- agentpool/config_resources/external_acp_agents.yml +2 -1
- agentpool/delegation/base_team.py +4 -30
- agentpool/delegation/pool.py +104 -265
- agentpool/delegation/team.py +57 -57
- agentpool/delegation/teamrun.py +50 -55
- agentpool/functional/run.py +10 -4
- agentpool/mcp_server/client.py +73 -38
- agentpool/mcp_server/conversions.py +54 -13
- agentpool/mcp_server/manager.py +9 -23
- agentpool/mcp_server/registries/official_registry_client.py +10 -1
- agentpool/mcp_server/tool_bridge.py +114 -79
- 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 +87 -8
- agentpool/messaging/messagenode.py +52 -14
- agentpool/messaging/messages.py +2 -26
- agentpool/messaging/processing.py +10 -22
- agentpool/models/__init__.py +1 -1
- agentpool/models/acp_agents/base.py +6 -2
- agentpool/models/acp_agents/mcp_capable.py +124 -15
- agentpool/models/acp_agents/non_mcp.py +0 -23
- agentpool/models/agents.py +66 -66
- agentpool/models/agui_agents.py +1 -1
- agentpool/models/claude_code_agents.py +111 -17
- agentpool/models/file_parsing.py +0 -1
- agentpool/models/manifest.py +70 -50
- agentpool/prompts/conversion_manager.py +1 -1
- agentpool/prompts/prompts.py +5 -2
- agentpool/resource_providers/__init__.py +2 -0
- agentpool/resource_providers/aggregating.py +4 -2
- agentpool/resource_providers/base.py +13 -3
- 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 +66 -12
- agentpool/resource_providers/plan_provider.py +111 -18
- agentpool/resource_providers/pool.py +5 -3
- agentpool/resource_providers/resource_info.py +111 -0
- agentpool/resource_providers/static.py +2 -2
- agentpool/sessions/__init__.py +2 -0
- agentpool/sessions/manager.py +2 -3
- agentpool/sessions/models.py +9 -6
- agentpool/sessions/protocol.py +28 -0
- agentpool/sessions/session.py +11 -55
- agentpool/storage/manager.py +361 -54
- agentpool/talk/registry.py +4 -4
- agentpool/talk/talk.py +9 -10
- agentpool/testing.py +1 -1
- 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/streams.py +21 -96
- agentpool/vfs_registry.py +7 -2
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/METADATA +16 -22
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/RECORD +242 -195
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/WHEEL +1 -1
- agentpool_cli/__main__.py +20 -0
- agentpool_cli/create.py +1 -1
- agentpool_cli/serve_acp.py +59 -1
- agentpool_cli/serve_opencode.py +1 -1
- agentpool_cli/ui.py +557 -0
- agentpool_commands/__init__.py +12 -5
- agentpool_commands/agents.py +1 -1
- 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/utils.py +31 -32
- agentpool_config/__init__.py +30 -2
- agentpool_config/agentpool_tools.py +498 -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 +1 -5
- agentpool_config/nodes.py +1 -1
- agentpool_config/observability.py +44 -0
- agentpool_config/session.py +0 -3
- agentpool_config/storage.py +38 -39
- agentpool_config/task.py +3 -3
- agentpool_config/tools.py +11 -28
- agentpool_config/toolsets.py +22 -90
- agentpool_server/a2a_server/agent_worker.py +307 -0
- agentpool_server/a2a_server/server.py +23 -18
- agentpool_server/acp_server/acp_agent.py +125 -56
- agentpool_server/acp_server/commands/acp_commands.py +46 -216
- agentpool_server/acp_server/commands/docs_commands/fetch_repo.py +8 -7
- agentpool_server/acp_server/event_converter.py +651 -0
- agentpool_server/acp_server/input_provider.py +53 -10
- agentpool_server/acp_server/server.py +1 -11
- agentpool_server/acp_server/session.py +90 -410
- 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/ENDPOINTS.md +53 -14
- agentpool_server/opencode_server/OPENCODE_UI_TOOLS_COMPLETE.md +202 -0
- agentpool_server/opencode_server/__init__.py +0 -8
- agentpool_server/opencode_server/converters.py +132 -26
- agentpool_server/opencode_server/input_provider.py +160 -8
- agentpool_server/opencode_server/models/__init__.py +42 -20
- agentpool_server/opencode_server/models/app.py +12 -0
- agentpool_server/opencode_server/models/events.py +203 -29
- agentpool_server/opencode_server/models/mcp.py +19 -0
- agentpool_server/opencode_server/models/message.py +18 -1
- agentpool_server/opencode_server/models/parts.py +134 -1
- agentpool_server/opencode_server/models/question.py +56 -0
- agentpool_server/opencode_server/models/session.py +13 -1
- agentpool_server/opencode_server/routes/__init__.py +4 -0
- agentpool_server/opencode_server/routes/agent_routes.py +33 -2
- agentpool_server/opencode_server/routes/app_routes.py +66 -3
- agentpool_server/opencode_server/routes/config_routes.py +66 -5
- agentpool_server/opencode_server/routes/file_routes.py +184 -5
- agentpool_server/opencode_server/routes/global_routes.py +1 -1
- agentpool_server/opencode_server/routes/lsp_routes.py +1 -1
- agentpool_server/opencode_server/routes/message_routes.py +122 -66
- agentpool_server/opencode_server/routes/permission_routes.py +63 -0
- agentpool_server/opencode_server/routes/pty_routes.py +23 -22
- agentpool_server/opencode_server/routes/question_routes.py +128 -0
- agentpool_server/opencode_server/routes/session_routes.py +139 -68
- agentpool_server/opencode_server/routes/tui_routes.py +1 -1
- agentpool_server/opencode_server/server.py +47 -2
- agentpool_server/opencode_server/state.py +30 -0
- agentpool_storage/__init__.py +0 -4
- agentpool_storage/base.py +81 -2
- agentpool_storage/claude_provider/ARCHITECTURE.md +433 -0
- agentpool_storage/claude_provider/__init__.py +42 -0
- agentpool_storage/{claude_provider.py → claude_provider/provider.py} +190 -8
- agentpool_storage/file_provider.py +149 -15
- agentpool_storage/memory_provider.py +132 -12
- 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/session_store.py +20 -6
- agentpool_storage/sql_provider/sql_provider.py +135 -2
- agentpool_storage/sql_provider/utils.py +2 -12
- 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 -4
- agentpool_toolsets/builtin/code.py +4 -4
- agentpool_toolsets/builtin/debug.py +115 -40
- agentpool_toolsets/builtin/execution_environment.py +54 -165
- agentpool_toolsets/builtin/skills.py +0 -77
- 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/grep.py +25 -5
- agentpool_toolsets/fsspec_toolset/helpers.py +3 -2
- agentpool_toolsets/fsspec_toolset/toolset.py +350 -66
- agentpool_toolsets/mcp_discovery/data/mcp_servers.parquet +0 -0
- agentpool_toolsets/mcp_discovery/toolset.py +74 -17
- agentpool_toolsets/mcp_run_toolset.py +8 -11
- agentpool_toolsets/notifications.py +33 -33
- agentpool_toolsets/openapi.py +3 -1
- agentpool_toolsets/search_toolset.py +3 -1
- 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/opencode_provider.py +0 -730
- agentpool_storage/text_log_provider.py +0 -276
- agentpool_toolsets/builtin/chain.py +0 -288
- agentpool_toolsets/builtin/user_interaction.py +0 -52
- agentpool_toolsets/semantic_memory_toolset.py +0 -536
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/entry_points.txt +0 -0
- {agentpool-2.2.3.dist-info → agentpool-2.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Zed IDE storage format models."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any, Literal
|
|
6
|
+
|
|
7
|
+
from pydantic import AliasChoices, BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ZedMentionUri(BaseModel):
|
|
11
|
+
"""Mention URI - can be File, Directory, Symbol, etc."""
|
|
12
|
+
|
|
13
|
+
File: dict[str, Any] | None = None
|
|
14
|
+
Directory: dict[str, Any] | None = None
|
|
15
|
+
Symbol: dict[str, Any] | None = None
|
|
16
|
+
Selection: dict[str, Any] | None = None
|
|
17
|
+
Thread: dict[str, Any] | None = None
|
|
18
|
+
TextThread: dict[str, Any] | None = None
|
|
19
|
+
Rule: dict[str, Any] | None = None
|
|
20
|
+
Fetch: dict[str, Any] | None = None
|
|
21
|
+
PastedImage: bool | None = None
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class ZedMention(BaseModel):
|
|
25
|
+
"""A file/symbol mention in Zed."""
|
|
26
|
+
|
|
27
|
+
uri: ZedMentionUri
|
|
28
|
+
content: str
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ZedImage(BaseModel):
|
|
32
|
+
"""An image in Zed (base64 encoded)."""
|
|
33
|
+
|
|
34
|
+
source: str # base64 encoded
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ZedThinking(BaseModel):
|
|
38
|
+
"""Thinking block from model."""
|
|
39
|
+
|
|
40
|
+
text: str
|
|
41
|
+
signature: str | None = None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class ZedToolUse(BaseModel):
|
|
45
|
+
"""Tool use block."""
|
|
46
|
+
|
|
47
|
+
id: str
|
|
48
|
+
name: str
|
|
49
|
+
raw_input: str
|
|
50
|
+
input: dict[str, Any]
|
|
51
|
+
is_input_complete: bool = True
|
|
52
|
+
thought_signature: str | None = None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class ZedToolResult(BaseModel):
|
|
56
|
+
"""Tool result."""
|
|
57
|
+
|
|
58
|
+
tool_use_id: str
|
|
59
|
+
tool_name: str
|
|
60
|
+
is_error: bool = False
|
|
61
|
+
content: dict[str, Any] | str | None = None
|
|
62
|
+
output: dict[str, Any] | str | None = None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class ZedUserMessage(BaseModel):
|
|
66
|
+
"""User message in Zed thread."""
|
|
67
|
+
|
|
68
|
+
id: str
|
|
69
|
+
content: list[dict[str, Any]] # Can contain Text, Image, Mention
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class ZedAgentMessage(BaseModel):
|
|
73
|
+
"""Agent message in Zed thread."""
|
|
74
|
+
|
|
75
|
+
content: list[dict[str, Any]] # Can contain Text, Thinking, ToolUse
|
|
76
|
+
tool_results: dict[str, ZedToolResult] = Field(default_factory=dict)
|
|
77
|
+
reasoning_details: Any | None = None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class ZedMessage(BaseModel):
|
|
81
|
+
"""A message in Zed thread - either User or Agent."""
|
|
82
|
+
|
|
83
|
+
User: ZedUserMessage | None = None
|
|
84
|
+
Agent: ZedAgentMessage | None = None
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class ZedLanguageModel(BaseModel):
|
|
88
|
+
"""Model configuration."""
|
|
89
|
+
|
|
90
|
+
provider: str
|
|
91
|
+
model: str
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class ZedWorktreeSnapshot(BaseModel):
|
|
95
|
+
"""Git worktree snapshot."""
|
|
96
|
+
|
|
97
|
+
worktree_path: str
|
|
98
|
+
git_state: dict[str, Any] | None = None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
class ZedProjectSnapshot(BaseModel):
|
|
102
|
+
"""Project snapshot with git state."""
|
|
103
|
+
|
|
104
|
+
worktree_snapshots: list[ZedWorktreeSnapshot] = Field(default_factory=list)
|
|
105
|
+
unsaved_buffer_paths: list[str] = Field(default_factory=list)
|
|
106
|
+
timestamp: str | None = None
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class ZedThread(BaseModel):
|
|
110
|
+
"""A Zed conversation thread."""
|
|
111
|
+
|
|
112
|
+
model_config = {"populate_by_name": True}
|
|
113
|
+
|
|
114
|
+
# v0.3.0 uses "title", v0.2.0 uses "summary"
|
|
115
|
+
title: str = Field(alias="title", validation_alias=AliasChoices("title", "summary"))
|
|
116
|
+
messages: list[ZedMessage | Literal["Resume"]] # Control messages
|
|
117
|
+
updated_at: str
|
|
118
|
+
version: str | None = None
|
|
119
|
+
detailed_summary: str | None = None # v0.3.0 field
|
|
120
|
+
detailed_summary_state: str | dict[str, Any] | None = None # v0.2.0 field
|
|
121
|
+
initial_project_snapshot: ZedProjectSnapshot | None = None
|
|
122
|
+
cumulative_token_usage: dict[str, int] = Field(default_factory=dict)
|
|
123
|
+
request_token_usage: list[dict[str, int]] | dict[str, dict[str, int]] = Field(
|
|
124
|
+
default_factory=list
|
|
125
|
+
) # Can be list or dict depending on version
|
|
126
|
+
model: ZedLanguageModel | None = None
|
|
127
|
+
completion_mode: str | None = None
|
|
128
|
+
profile: str | None = None
|
|
129
|
+
exceeded_window_error: Any | None = None
|
|
130
|
+
tool_use_limit_reached: bool = False
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
"""Zed IDE storage provider - reads from ~/.local/share/zed/threads format."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections import defaultdict
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
import sqlite3
|
|
9
|
+
from typing import TYPE_CHECKING, Any
|
|
10
|
+
|
|
11
|
+
from agentpool.log import get_logger
|
|
12
|
+
from agentpool.utils.now import get_now
|
|
13
|
+
from agentpool_storage.base import StorageProvider
|
|
14
|
+
from agentpool_storage.zed_provider import helpers
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
if TYPE_CHECKING:
|
|
18
|
+
from collections.abc import Sequence
|
|
19
|
+
|
|
20
|
+
from pydantic_ai import FinishReason
|
|
21
|
+
|
|
22
|
+
from agentpool.messaging import ChatMessage, TokenCost
|
|
23
|
+
from agentpool_config.session import SessionQuery
|
|
24
|
+
from agentpool_config.storage import ZedStorageConfig
|
|
25
|
+
from agentpool_storage.models import (
|
|
26
|
+
ConversationData,
|
|
27
|
+
MessageData,
|
|
28
|
+
QueryFilters,
|
|
29
|
+
StatsFilters,
|
|
30
|
+
TokenUsage,
|
|
31
|
+
)
|
|
32
|
+
from agentpool_storage.zed_provider.models import ZedThread
|
|
33
|
+
|
|
34
|
+
logger = get_logger(__name__)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class ZedStorageProvider(StorageProvider):
|
|
38
|
+
"""Storage provider that reads Zed IDE's native thread format.
|
|
39
|
+
|
|
40
|
+
Zed stores conversations as zstd-compressed JSON in:
|
|
41
|
+
- ~/.local/share/zed/threads/threads.db (SQLite)
|
|
42
|
+
|
|
43
|
+
This is a READ-ONLY provider - it cannot write back to Zed's format.
|
|
44
|
+
|
|
45
|
+
## Supported Zed content types:
|
|
46
|
+
- Text → str / TextPart
|
|
47
|
+
- Image → BinaryContent
|
|
48
|
+
- Mention (File, Directory, Symbol, Selection) → formatted str
|
|
49
|
+
- Thinking → ThinkingPart
|
|
50
|
+
- ToolUse → ToolCallPart
|
|
51
|
+
- tool_results → ToolReturnPart
|
|
52
|
+
|
|
53
|
+
## Fields NOT available in Zed format:
|
|
54
|
+
- Per-message token costs (only cumulative per thread)
|
|
55
|
+
- Response time
|
|
56
|
+
- Parent message ID (flat structure)
|
|
57
|
+
- Forwarded from chain
|
|
58
|
+
- Finish reason
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
can_load_history = True
|
|
62
|
+
|
|
63
|
+
def __init__(self, config: ZedStorageConfig) -> None:
|
|
64
|
+
"""Initialize Zed storage provider.
|
|
65
|
+
|
|
66
|
+
Args:
|
|
67
|
+
config: Configuration for the provider
|
|
68
|
+
"""
|
|
69
|
+
super().__init__(config)
|
|
70
|
+
self.db_path = Path(config.path).expanduser()
|
|
71
|
+
if not self.db_path.name.endswith(".db"):
|
|
72
|
+
# If path is directory, add default db location
|
|
73
|
+
self.db_path = self.db_path / "threads" / "threads.db"
|
|
74
|
+
|
|
75
|
+
def _get_connection(self) -> sqlite3.Connection:
|
|
76
|
+
"""Get a SQLite connection."""
|
|
77
|
+
if not self.db_path.exists():
|
|
78
|
+
msg = f"Zed threads database not found: {self.db_path}"
|
|
79
|
+
raise FileNotFoundError(msg)
|
|
80
|
+
return sqlite3.connect(self.db_path)
|
|
81
|
+
|
|
82
|
+
def _list_threads(self) -> list[tuple[str, str, str]]:
|
|
83
|
+
"""List all threads.
|
|
84
|
+
|
|
85
|
+
Returns:
|
|
86
|
+
List of (id, summary, updated_at) tuples
|
|
87
|
+
"""
|
|
88
|
+
try:
|
|
89
|
+
conn = self._get_connection()
|
|
90
|
+
query = "SELECT id, summary, updated_at FROM threads ORDER BY updated_at DESC"
|
|
91
|
+
cursor = conn.execute(query)
|
|
92
|
+
threads = cursor.fetchall()
|
|
93
|
+
conn.close()
|
|
94
|
+
except FileNotFoundError:
|
|
95
|
+
return []
|
|
96
|
+
except sqlite3.Error as e:
|
|
97
|
+
logger.warning("Failed to list Zed threads", error=str(e))
|
|
98
|
+
return []
|
|
99
|
+
else:
|
|
100
|
+
return threads
|
|
101
|
+
|
|
102
|
+
def _load_thread(self, thread_id: str) -> ZedThread | None:
|
|
103
|
+
"""Load a single thread by ID."""
|
|
104
|
+
try:
|
|
105
|
+
conn = self._get_connection()
|
|
106
|
+
query = "SELECT data_type, data FROM threads WHERE id = ? LIMIT 1"
|
|
107
|
+
cursor = conn.execute(query, (thread_id,))
|
|
108
|
+
row = cursor.fetchone()
|
|
109
|
+
conn.close()
|
|
110
|
+
if row is None:
|
|
111
|
+
return None
|
|
112
|
+
|
|
113
|
+
data_type, data = row
|
|
114
|
+
return helpers.decompress_thread(data, data_type)
|
|
115
|
+
except FileNotFoundError:
|
|
116
|
+
return None
|
|
117
|
+
except (sqlite3.Error, Exception) as e: # noqa: BLE001
|
|
118
|
+
logger.warning("Failed to load Zed thread", thread_id=thread_id, error=str(e))
|
|
119
|
+
return None
|
|
120
|
+
|
|
121
|
+
async def filter_messages(self, query: SessionQuery) -> list[ChatMessage[str]]:
|
|
122
|
+
"""Filter messages based on query."""
|
|
123
|
+
messages: list[ChatMessage[str]] = []
|
|
124
|
+
for thread_id, summary, _updated_at in self._list_threads():
|
|
125
|
+
# Filter by conversation name if specified
|
|
126
|
+
if query.name and query.name not in (thread_id, summary):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
thread = self._load_thread(thread_id)
|
|
130
|
+
if thread is None:
|
|
131
|
+
continue
|
|
132
|
+
for msg in helpers.thread_to_chat_messages(thread, thread_id):
|
|
133
|
+
# Apply filters
|
|
134
|
+
if query.agents and msg.name not in query.agents:
|
|
135
|
+
continue
|
|
136
|
+
cutoff = query.get_time_cutoff()
|
|
137
|
+
if query.since and cutoff and msg.timestamp and msg.timestamp < cutoff:
|
|
138
|
+
continue
|
|
139
|
+
if query.until and msg.timestamp:
|
|
140
|
+
until_dt = datetime.fromisoformat(query.until)
|
|
141
|
+
if msg.timestamp > until_dt:
|
|
142
|
+
continue
|
|
143
|
+
if query.contains and query.contains not in msg.content:
|
|
144
|
+
continue
|
|
145
|
+
if query.roles and msg.role not in query.roles:
|
|
146
|
+
continue
|
|
147
|
+
messages.append(msg)
|
|
148
|
+
if query.limit and len(messages) >= query.limit:
|
|
149
|
+
return messages
|
|
150
|
+
|
|
151
|
+
return messages
|
|
152
|
+
|
|
153
|
+
async def log_message(
|
|
154
|
+
self,
|
|
155
|
+
*,
|
|
156
|
+
message_id: str,
|
|
157
|
+
conversation_id: str,
|
|
158
|
+
content: str,
|
|
159
|
+
role: str,
|
|
160
|
+
name: str | None = None,
|
|
161
|
+
parent_id: str | None = None,
|
|
162
|
+
cost_info: TokenCost | None = None,
|
|
163
|
+
model: str | None = None,
|
|
164
|
+
response_time: float | None = None,
|
|
165
|
+
provider_name: str | None = None,
|
|
166
|
+
provider_response_id: str | None = None,
|
|
167
|
+
messages: str | None = None,
|
|
168
|
+
finish_reason: FinishReason | None = None,
|
|
169
|
+
) -> None:
|
|
170
|
+
"""Log a message - NOT SUPPORTED (read-only provider)."""
|
|
171
|
+
logger.warning("ZedStorageProvider is read-only, cannot log messages")
|
|
172
|
+
|
|
173
|
+
async def log_conversation(
|
|
174
|
+
self,
|
|
175
|
+
*,
|
|
176
|
+
conversation_id: str,
|
|
177
|
+
node_name: str,
|
|
178
|
+
start_time: datetime | None = None,
|
|
179
|
+
) -> None:
|
|
180
|
+
"""Log a conversation - NOT SUPPORTED (read-only provider)."""
|
|
181
|
+
logger.warning("ZedStorageProvider is read-only, cannot log conversations")
|
|
182
|
+
|
|
183
|
+
async def get_conversations(
|
|
184
|
+
self,
|
|
185
|
+
filters: QueryFilters,
|
|
186
|
+
) -> list[tuple[ConversationData, Sequence[ChatMessage[str]]]]:
|
|
187
|
+
"""Get filtered conversations with their messages."""
|
|
188
|
+
from agentpool_storage.models import ConversationData as ConvData
|
|
189
|
+
|
|
190
|
+
result: list[tuple[ConvData, Sequence[ChatMessage[str]]]] = []
|
|
191
|
+
for thread_id, summary, updated_at_str in self._list_threads():
|
|
192
|
+
thread = self._load_thread(thread_id)
|
|
193
|
+
if thread is None:
|
|
194
|
+
continue
|
|
195
|
+
# Parse timestamp
|
|
196
|
+
try:
|
|
197
|
+
updated_at = datetime.fromisoformat(updated_at_str.replace("Z", "+00:00"))
|
|
198
|
+
except (ValueError, AttributeError):
|
|
199
|
+
updated_at = get_now()
|
|
200
|
+
# Apply filters
|
|
201
|
+
if filters.since and updated_at < filters.since:
|
|
202
|
+
continue
|
|
203
|
+
messages = helpers.thread_to_chat_messages(thread, thread_id)
|
|
204
|
+
if not messages:
|
|
205
|
+
continue
|
|
206
|
+
if filters.agent_name and not any(m.name == filters.agent_name for m in messages):
|
|
207
|
+
continue
|
|
208
|
+
if filters.query and not any(filters.query in m.content for m in messages):
|
|
209
|
+
continue
|
|
210
|
+
# Build MessageData list
|
|
211
|
+
msg_data_list: list[MessageData] = []
|
|
212
|
+
for msg in messages:
|
|
213
|
+
msg_data: MessageData = {
|
|
214
|
+
"role": msg.role,
|
|
215
|
+
"content": msg.content,
|
|
216
|
+
"timestamp": (msg.timestamp or get_now()).isoformat(),
|
|
217
|
+
"parent_id": msg.parent_id,
|
|
218
|
+
"model": msg.model_name,
|
|
219
|
+
"name": msg.name,
|
|
220
|
+
"token_usage": None,
|
|
221
|
+
"cost": None,
|
|
222
|
+
"response_time": None,
|
|
223
|
+
}
|
|
224
|
+
msg_data_list.append(msg_data)
|
|
225
|
+
|
|
226
|
+
# Get token usage from thread-level cumulative data
|
|
227
|
+
total_tokens = sum(thread.cumulative_token_usage.values())
|
|
228
|
+
token_usage_data: TokenUsage | None = (
|
|
229
|
+
{"total": total_tokens, "prompt": 0, "completion": 0} if total_tokens else None
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
conv_data = ConvData(
|
|
233
|
+
id=thread_id,
|
|
234
|
+
agent="zed",
|
|
235
|
+
title=summary or thread.title,
|
|
236
|
+
start_time=updated_at.isoformat(),
|
|
237
|
+
messages=msg_data_list,
|
|
238
|
+
token_usage=token_usage_data,
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
result.append((conv_data, messages))
|
|
242
|
+
if filters.limit and len(result) >= filters.limit:
|
|
243
|
+
break
|
|
244
|
+
|
|
245
|
+
return result
|
|
246
|
+
|
|
247
|
+
async def get_conversation_stats(self, filters: StatsFilters) -> dict[str, dict[str, Any]]:
|
|
248
|
+
"""Get conversation statistics."""
|
|
249
|
+
stats: dict[str, dict[str, Any]] = defaultdict(
|
|
250
|
+
lambda: {"total_tokens": 0, "messages": 0, "models": set()}
|
|
251
|
+
)
|
|
252
|
+
for thread_id, _summary, updated_at_str in self._list_threads():
|
|
253
|
+
try:
|
|
254
|
+
timestamp = datetime.fromisoformat(updated_at_str.replace("Z", "+00:00"))
|
|
255
|
+
except (ValueError, AttributeError):
|
|
256
|
+
timestamp = get_now()
|
|
257
|
+
# Apply time filter
|
|
258
|
+
if timestamp < filters.cutoff:
|
|
259
|
+
continue
|
|
260
|
+
thread = self._load_thread(thread_id)
|
|
261
|
+
if thread is None:
|
|
262
|
+
continue
|
|
263
|
+
model_name = "unknown"
|
|
264
|
+
if thread.model:
|
|
265
|
+
model_name = f"{thread.model.provider}:{thread.model.model}"
|
|
266
|
+
total_tokens = sum(thread.cumulative_token_usage.values())
|
|
267
|
+
message_count = len(thread.messages)
|
|
268
|
+
# Group by specified criterion
|
|
269
|
+
match filters.group_by:
|
|
270
|
+
case "model":
|
|
271
|
+
key = model_name
|
|
272
|
+
case "hour":
|
|
273
|
+
key = timestamp.strftime("%Y-%m-%d %H:00")
|
|
274
|
+
case "day":
|
|
275
|
+
key = timestamp.strftime("%Y-%m-%d")
|
|
276
|
+
case _:
|
|
277
|
+
key = "zed" # Default agent grouping
|
|
278
|
+
|
|
279
|
+
stats[key]["messages"] += message_count
|
|
280
|
+
stats[key]["total_tokens"] += total_tokens
|
|
281
|
+
stats[key]["models"].add(model_name)
|
|
282
|
+
|
|
283
|
+
# Convert sets to lists for JSON serialization
|
|
284
|
+
for value in stats.values():
|
|
285
|
+
value["models"] = list(value["models"])
|
|
286
|
+
|
|
287
|
+
return dict(stats)
|
|
288
|
+
|
|
289
|
+
async def reset(self, *, agent_name: str | None = None, hard: bool = False) -> tuple[int, int]:
|
|
290
|
+
"""Reset storage - NOT SUPPORTED (read-only provider)."""
|
|
291
|
+
logger.warning("ZedStorageProvider is read-only, cannot reset")
|
|
292
|
+
return 0, 0
|
|
293
|
+
|
|
294
|
+
async def get_conversation_counts(
|
|
295
|
+
self,
|
|
296
|
+
*,
|
|
297
|
+
agent_name: str | None = None,
|
|
298
|
+
) -> tuple[int, int]:
|
|
299
|
+
"""Get counts of conversations and messages."""
|
|
300
|
+
conv_count = 0
|
|
301
|
+
msg_count = 0
|
|
302
|
+
threads = self._list_threads()
|
|
303
|
+
for thread_id, _summary, _updated_at in threads:
|
|
304
|
+
thread = self._load_thread(thread_id)
|
|
305
|
+
if thread is None:
|
|
306
|
+
continue
|
|
307
|
+
|
|
308
|
+
conv_count += 1
|
|
309
|
+
msg_count += len(thread.messages)
|
|
310
|
+
return conv_count, msg_count
|
|
311
|
+
|
|
312
|
+
async def get_conversation_title(self, conversation_id: str) -> str | None:
|
|
313
|
+
"""Get the title of a conversation."""
|
|
314
|
+
thread = self._load_thread(conversation_id)
|
|
315
|
+
if thread is None:
|
|
316
|
+
return None
|
|
317
|
+
return thread.title
|
|
318
|
+
|
|
319
|
+
async def get_conversation_messages(
|
|
320
|
+
self,
|
|
321
|
+
conversation_id: str,
|
|
322
|
+
*,
|
|
323
|
+
include_ancestors: bool = False,
|
|
324
|
+
) -> list[ChatMessage[str]]:
|
|
325
|
+
"""Get all messages for a conversation.
|
|
326
|
+
|
|
327
|
+
Args:
|
|
328
|
+
conversation_id: Thread ID (conversation ID in Zed format)
|
|
329
|
+
include_ancestors: If True, traverse parent_id chain to include
|
|
330
|
+
messages from ancestor conversations (not supported in Zed format)
|
|
331
|
+
|
|
332
|
+
Returns:
|
|
333
|
+
List of messages ordered by timestamp
|
|
334
|
+
|
|
335
|
+
Note:
|
|
336
|
+
Zed threads don't have parent_id chain, so include_ancestors has no effect.
|
|
337
|
+
"""
|
|
338
|
+
thread = self._load_thread(conversation_id)
|
|
339
|
+
if thread is None:
|
|
340
|
+
return []
|
|
341
|
+
|
|
342
|
+
messages = helpers.thread_to_chat_messages(thread, conversation_id)
|
|
343
|
+
# Sort by timestamp (though they should already be in order)
|
|
344
|
+
messages.sort(key=lambda m: m.timestamp or get_now())
|
|
345
|
+
return messages
|
|
346
|
+
|
|
347
|
+
async def get_message(self, message_id: str) -> ChatMessage[str] | None:
|
|
348
|
+
"""Get a single message by ID.
|
|
349
|
+
|
|
350
|
+
Args:
|
|
351
|
+
message_id: ID of the message
|
|
352
|
+
|
|
353
|
+
Returns:
|
|
354
|
+
The message if found, None otherwise
|
|
355
|
+
|
|
356
|
+
Note:
|
|
357
|
+
Zed doesn't store individual message IDs, so this searches all threads.
|
|
358
|
+
This is inefficient for large datasets.
|
|
359
|
+
"""
|
|
360
|
+
for thread_id, _summary, _updated_at in self._list_threads():
|
|
361
|
+
thread = self._load_thread(thread_id)
|
|
362
|
+
if thread is None:
|
|
363
|
+
continue
|
|
364
|
+
messages = helpers.thread_to_chat_messages(thread, thread_id)
|
|
365
|
+
for msg in messages:
|
|
366
|
+
if msg.message_id == message_id:
|
|
367
|
+
return msg
|
|
368
|
+
return None
|
|
369
|
+
|
|
370
|
+
async def get_message_ancestry(self, message_id: str) -> list[ChatMessage[str]]:
|
|
371
|
+
"""Get the ancestry chain of a message.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
message_id: ID of the message
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
List of messages from oldest ancestor to the specified message
|
|
378
|
+
|
|
379
|
+
Note:
|
|
380
|
+
Zed threads don't support parent_id chains, so this only returns
|
|
381
|
+
the single message if found.
|
|
382
|
+
"""
|
|
383
|
+
msg = await self.get_message(message_id)
|
|
384
|
+
return [msg] if msg else []
|
|
385
|
+
|
|
386
|
+
async def fork_conversation(
|
|
387
|
+
self,
|
|
388
|
+
*,
|
|
389
|
+
source_conversation_id: str,
|
|
390
|
+
new_conversation_id: str,
|
|
391
|
+
fork_from_message_id: str | None = None,
|
|
392
|
+
new_agent_name: str | None = None,
|
|
393
|
+
) -> str | None:
|
|
394
|
+
"""Fork a conversation at a specific point.
|
|
395
|
+
|
|
396
|
+
Args:
|
|
397
|
+
source_conversation_id: Source thread ID
|
|
398
|
+
new_conversation_id: New thread ID
|
|
399
|
+
fork_from_message_id: Message ID to fork from (not used - Zed is read-only)
|
|
400
|
+
new_agent_name: Not used in Zed format
|
|
401
|
+
|
|
402
|
+
Returns:
|
|
403
|
+
None, as Zed storage is read-only
|
|
404
|
+
|
|
405
|
+
Note:
|
|
406
|
+
This is a READ-ONLY provider. Forking creates no persistent state.
|
|
407
|
+
Returns None to indicate no fork point is available.
|
|
408
|
+
"""
|
|
409
|
+
msg = "Fork conversation not supported for Zed storage (read-only)"
|
|
410
|
+
logger.warning(msg, source=source_conversation_id, new=new_conversation_id)
|
|
411
|
+
return None
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
if __name__ == "__main__":
|
|
415
|
+
import asyncio
|
|
416
|
+
import datetime as dt
|
|
417
|
+
|
|
418
|
+
from agentpool_config.storage import ZedStorageConfig
|
|
419
|
+
from agentpool_storage.models import QueryFilters, StatsFilters
|
|
420
|
+
|
|
421
|
+
async def main() -> None:
|
|
422
|
+
config = ZedStorageConfig()
|
|
423
|
+
provider = ZedStorageProvider(config)
|
|
424
|
+
print(f"Database: {provider.db_path}")
|
|
425
|
+
print(f"Exists: {provider.db_path.exists()}")
|
|
426
|
+
# List conversations
|
|
427
|
+
filters = QueryFilters(limit=10)
|
|
428
|
+
conversations = await provider.get_conversations(filters)
|
|
429
|
+
print(f"\nFound {len(conversations)} conversations")
|
|
430
|
+
for conv_data, messages in conversations[:5]:
|
|
431
|
+
print(f" - {conv_data['id'][:8]}... | {conv_data['title'] or 'Untitled'}")
|
|
432
|
+
print(f" Messages: {len(messages)}, Updated: {conv_data['start_time']}")
|
|
433
|
+
# Get counts
|
|
434
|
+
conv_count, msg_count = await provider.get_conversation_counts()
|
|
435
|
+
print(f"\nTotal: {conv_count} conversations, {msg_count} messages")
|
|
436
|
+
# Get stats
|
|
437
|
+
cutoff = dt.datetime.now(dt.UTC) - dt.timedelta(days=30)
|
|
438
|
+
stats_filters = StatsFilters(cutoff=cutoff, group_by="day")
|
|
439
|
+
stats = await provider.get_conversation_stats(stats_filters)
|
|
440
|
+
print(f"\nStats: {stats}")
|
|
441
|
+
|
|
442
|
+
asyncio.run(main())
|