meredith-agent 0.2.6__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.
- coding_agent/__init__.py +38 -0
- coding_agent/acp/__init__.py +12 -0
- coding_agent/acp/server.py +269 -0
- coding_agent/agent/__init__.py +20 -0
- coding_agent/agent/core.py +545 -0
- coding_agent/agent/planner.py +388 -0
- coding_agent/agent/verifier.py +273 -0
- coding_agent/config.py +362 -0
- coding_agent/context/__init__.py +17 -0
- coding_agent/context/budget.py +184 -0
- coding_agent/context/compressor.py +323 -0
- coding_agent/context/manager.py +327 -0
- coding_agent/llm/__init__.py +26 -0
- coding_agent/llm/base.py +216 -0
- coding_agent/llm/local.py +527 -0
- coding_agent/llm/remote.py +304 -0
- coding_agent/main.py +212 -0
- coding_agent/memory/__init__.py +15 -0
- coding_agent/memory/store.py +358 -0
- coding_agent/rag/__init__.py +18 -0
- coding_agent/rag/chunker.py +484 -0
- coding_agent/rag/indexer.py +515 -0
- coding_agent/rag/retriever.py +390 -0
- coding_agent/recovery/__init__.py +14 -0
- coding_agent/recovery/detector.py +303 -0
- coding_agent/recovery/strategies.py +288 -0
- coding_agent/tools/__init__.py +18 -0
- coding_agent/tools/base.py +395 -0
- coding_agent/tools/fs.py +419 -0
- coding_agent/tools/git.py +201 -0
- coding_agent/tools/router.py +248 -0
- coding_agent/tools/search.py +487 -0
- coding_agent/tools/web.py +419 -0
- coding_agent/types.py +430 -0
- meredith_agent-0.2.6.dist-info/METADATA +327 -0
- meredith_agent-0.2.6.dist-info/RECORD +39 -0
- meredith_agent-0.2.6.dist-info/WHEEL +4 -0
- meredith_agent-0.2.6.dist-info/entry_points.txt +2 -0
- meredith_agent-0.2.6.dist-info/licenses/LICENSE +182 -0
coding_agent/__init__.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""
|
|
2
|
+
meredith: A modern AI coding agent with RAG, ACP integration,
|
|
3
|
+
and smart context management.
|
|
4
|
+
|
|
5
|
+
Supports any OpenAI-compatible remote API (Claude, GPT, Opencode, etc.)
|
|
6
|
+
and local models via Ollama (Linux, macOS, Windows) or MLX (Apple Silicon).
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
__version__ = "0.2.6"
|
|
10
|
+
|
|
11
|
+
from coding_agent.types import (
|
|
12
|
+
AgentState,
|
|
13
|
+
LoopDetection,
|
|
14
|
+
LoopType,
|
|
15
|
+
Message,
|
|
16
|
+
Plan,
|
|
17
|
+
RecoveryAction,
|
|
18
|
+
Role,
|
|
19
|
+
Step,
|
|
20
|
+
SubTask,
|
|
21
|
+
ToolCall,
|
|
22
|
+
ToolResult,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"__version__",
|
|
27
|
+
"AgentState",
|
|
28
|
+
"LoopDetection",
|
|
29
|
+
"LoopType",
|
|
30
|
+
"Message",
|
|
31
|
+
"Plan",
|
|
32
|
+
"RecoveryAction",
|
|
33
|
+
"Role",
|
|
34
|
+
"Step",
|
|
35
|
+
"SubTask",
|
|
36
|
+
"ToolCall",
|
|
37
|
+
"ToolResult",
|
|
38
|
+
]
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ACP server: exposes the coding agent over the Agent Client Protocol.
|
|
3
|
+
|
|
4
|
+
The ACP server communicates with ACP-aware editors
|
|
5
|
+
(Zed, JetBrains, Neovim, Emacs, etc.) via the stdio transport.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from coding_agent.acp.server import CodingAgentServer
|
|
9
|
+
|
|
10
|
+
__all__ = [
|
|
11
|
+
"CodingAgentServer",
|
|
12
|
+
]
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ACP server: exposes the coding agent over the Agent Client Protocol.
|
|
3
|
+
|
|
4
|
+
Replaces the previous MCP server. Uses the official `agent-client-protocol`
|
|
5
|
+
Python SDK to communicate with ACP-aware editors (Zed, JetBrains, Neovim, etc.).
|
|
6
|
+
|
|
7
|
+
Usage (standalone):
|
|
8
|
+
python -m coding_agent.acp.server --profile local_model
|
|
9
|
+
|
|
10
|
+
The server registers as an ACP agent. When the editor sends a prompt,
|
|
11
|
+
the coding agent's ReAct loop runs and streams status updates back
|
|
12
|
+
to the editor via `session/update` notifications.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import asyncio
|
|
18
|
+
import logging
|
|
19
|
+
from typing import Any
|
|
20
|
+
from uuid import uuid4
|
|
21
|
+
|
|
22
|
+
from acp import (
|
|
23
|
+
PROTOCOL_VERSION,
|
|
24
|
+
Agent,
|
|
25
|
+
InitializeResponse,
|
|
26
|
+
NewSessionResponse,
|
|
27
|
+
PromptResponse,
|
|
28
|
+
run_agent,
|
|
29
|
+
text_block,
|
|
30
|
+
update_agent_message,
|
|
31
|
+
)
|
|
32
|
+
from acp.interfaces import Client
|
|
33
|
+
from acp.schema import (
|
|
34
|
+
AgentCapabilities,
|
|
35
|
+
AudioContentBlock,
|
|
36
|
+
AuthenticateResponse,
|
|
37
|
+
ClientCapabilities,
|
|
38
|
+
CloseSessionResponse,
|
|
39
|
+
EmbeddedResourceContentBlock,
|
|
40
|
+
ForkSessionResponse,
|
|
41
|
+
HttpMcpServer,
|
|
42
|
+
ImageContentBlock,
|
|
43
|
+
Implementation,
|
|
44
|
+
ListSessionsResponse,
|
|
45
|
+
LoadSessionResponse,
|
|
46
|
+
McpServerStdio,
|
|
47
|
+
ResourceContentBlock,
|
|
48
|
+
ResumeSessionResponse,
|
|
49
|
+
SetSessionConfigOptionResponse,
|
|
50
|
+
SetSessionModelResponse,
|
|
51
|
+
SetSessionModeResponse,
|
|
52
|
+
SseMcpServer,
|
|
53
|
+
TextContentBlock,
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
from coding_agent.agent.core import AgentCore
|
|
57
|
+
from coding_agent.config import load_config
|
|
58
|
+
from coding_agent.main import create_llm_client
|
|
59
|
+
|
|
60
|
+
logger = logging.getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
class CodingAgentServer(Agent):
|
|
64
|
+
"""
|
|
65
|
+
ACP-compatible agent server wrapping the coding agent's AgentCore.
|
|
66
|
+
|
|
67
|
+
Each `prompt` call spawns a fresh AgentCore with the user's text
|
|
68
|
+
as the task, executes the full ReAct loop, and streams a summary
|
|
69
|
+
back to the editor.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
_conn: Client
|
|
73
|
+
|
|
74
|
+
def __init__(self, profile: str = "large_model") -> None:
|
|
75
|
+
self._profile = profile
|
|
76
|
+
self._config = load_config(profile)
|
|
77
|
+
|
|
78
|
+
def on_connect(self, conn: Client) -> None:
|
|
79
|
+
self._conn = conn
|
|
80
|
+
|
|
81
|
+
async def initialize(
|
|
82
|
+
self,
|
|
83
|
+
protocol_version: int,
|
|
84
|
+
client_capabilities: ClientCapabilities | None = None,
|
|
85
|
+
client_info: Implementation | None = None,
|
|
86
|
+
**kwargs: Any,
|
|
87
|
+
) -> InitializeResponse:
|
|
88
|
+
return InitializeResponse(
|
|
89
|
+
protocol_version=PROTOCOL_VERSION,
|
|
90
|
+
agent_capabilities=AgentCapabilities(),
|
|
91
|
+
agent_info=Implementation(
|
|
92
|
+
name="meredith",
|
|
93
|
+
title="meredith",
|
|
94
|
+
version="0.2.6",
|
|
95
|
+
),
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
async def new_session(
|
|
99
|
+
self,
|
|
100
|
+
cwd: str,
|
|
101
|
+
additional_directories: list[str] | None = None,
|
|
102
|
+
mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
|
|
103
|
+
**kwargs: Any,
|
|
104
|
+
) -> NewSessionResponse:
|
|
105
|
+
session_id = uuid4().hex
|
|
106
|
+
logger.info("New ACP session: %s (cwd=%s)", session_id, cwd)
|
|
107
|
+
return NewSessionResponse(session_id=session_id)
|
|
108
|
+
|
|
109
|
+
async def prompt(
|
|
110
|
+
self,
|
|
111
|
+
prompt: list[
|
|
112
|
+
TextContentBlock
|
|
113
|
+
| ImageContentBlock
|
|
114
|
+
| AudioContentBlock
|
|
115
|
+
| ResourceContentBlock
|
|
116
|
+
| EmbeddedResourceContentBlock
|
|
117
|
+
],
|
|
118
|
+
session_id: str,
|
|
119
|
+
message_id: str | None = None,
|
|
120
|
+
**kwargs: Any,
|
|
121
|
+
) -> PromptResponse:
|
|
122
|
+
user_text = self._extract_text(prompt)
|
|
123
|
+
if not user_text:
|
|
124
|
+
user_text = "(no text provided)"
|
|
125
|
+
|
|
126
|
+
logger.info("ACP prompt on session %s: %.80s", session_id, user_text)
|
|
127
|
+
|
|
128
|
+
llm = create_llm_client(self._config)
|
|
129
|
+
async with AgentCore(self._config, llm, user_text) as agent:
|
|
130
|
+
success = await agent.run()
|
|
131
|
+
|
|
132
|
+
summary = self._build_summary(agent, success)
|
|
133
|
+
if self._conn:
|
|
134
|
+
chunk = update_agent_message(text_block(summary))
|
|
135
|
+
await self._conn.session_update(
|
|
136
|
+
session_id=session_id,
|
|
137
|
+
update=chunk,
|
|
138
|
+
source="coding_agent",
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
return PromptResponse(stop_reason="end_turn", user_message_id=message_id)
|
|
142
|
+
|
|
143
|
+
async def cancel(self, session_id: str, **kwargs: Any) -> None:
|
|
144
|
+
logger.info("Cancel requested for session %s", session_id)
|
|
145
|
+
|
|
146
|
+
async def load_session(
|
|
147
|
+
self,
|
|
148
|
+
cwd: str,
|
|
149
|
+
session_id: str,
|
|
150
|
+
additional_directories: list[str] | None = None,
|
|
151
|
+
mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
|
|
152
|
+
**kwargs: Any,
|
|
153
|
+
) -> LoadSessionResponse | None:
|
|
154
|
+
return None
|
|
155
|
+
|
|
156
|
+
async def list_sessions(
|
|
157
|
+
self,
|
|
158
|
+
additional_directories: list[str] | None = None,
|
|
159
|
+
cursor: str | None = None,
|
|
160
|
+
cwd: str | None = None,
|
|
161
|
+
**kwargs: Any,
|
|
162
|
+
) -> ListSessionsResponse:
|
|
163
|
+
return ListSessionsResponse(sessions=[])
|
|
164
|
+
|
|
165
|
+
async def set_session_mode(
|
|
166
|
+
self, mode_id: str, session_id: str, **kwargs: Any
|
|
167
|
+
) -> SetSessionModeResponse | None:
|
|
168
|
+
return None
|
|
169
|
+
|
|
170
|
+
async def set_session_model(
|
|
171
|
+
self, model_id: str, session_id: str, **kwargs: Any
|
|
172
|
+
) -> SetSessionModelResponse | None:
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
async def set_config_option(
|
|
176
|
+
self, config_id: str, session_id: str, value: str | bool, **kwargs: Any
|
|
177
|
+
) -> SetSessionConfigOptionResponse | None:
|
|
178
|
+
return None
|
|
179
|
+
|
|
180
|
+
async def authenticate(self, method_id: str, **kwargs: Any) -> AuthenticateResponse | None:
|
|
181
|
+
return None
|
|
182
|
+
|
|
183
|
+
async def fork_session(
|
|
184
|
+
self,
|
|
185
|
+
cwd: str,
|
|
186
|
+
session_id: str,
|
|
187
|
+
additional_directories: list[str] | None = None,
|
|
188
|
+
mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
|
|
189
|
+
**kwargs: Any,
|
|
190
|
+
) -> ForkSessionResponse:
|
|
191
|
+
return ForkSessionResponse(session_id=session_id)
|
|
192
|
+
|
|
193
|
+
async def resume_session(
|
|
194
|
+
self,
|
|
195
|
+
cwd: str,
|
|
196
|
+
session_id: str,
|
|
197
|
+
additional_directories: list[str] | None = None,
|
|
198
|
+
mcp_servers: list[HttpMcpServer | SseMcpServer | McpServerStdio] | None = None,
|
|
199
|
+
**kwargs: Any,
|
|
200
|
+
) -> ResumeSessionResponse:
|
|
201
|
+
return ResumeSessionResponse()
|
|
202
|
+
|
|
203
|
+
async def close_session(self, session_id: str, **kwargs: Any) -> CloseSessionResponse | None:
|
|
204
|
+
return None
|
|
205
|
+
|
|
206
|
+
async def ext_method(self, method: str, params: dict[str, Any]) -> dict[str, Any]:
|
|
207
|
+
return {}
|
|
208
|
+
|
|
209
|
+
async def ext_notification(self, method: str, params: dict[str, Any]) -> None:
|
|
210
|
+
pass
|
|
211
|
+
|
|
212
|
+
# ── Helpers ────────────────────────────────────────────────
|
|
213
|
+
|
|
214
|
+
@staticmethod
|
|
215
|
+
def _extract_text(
|
|
216
|
+
prompt: list[
|
|
217
|
+
TextContentBlock
|
|
218
|
+
| ImageContentBlock
|
|
219
|
+
| AudioContentBlock
|
|
220
|
+
| ResourceContentBlock
|
|
221
|
+
| EmbeddedResourceContentBlock
|
|
222
|
+
],
|
|
223
|
+
) -> str:
|
|
224
|
+
parts: list[str] = []
|
|
225
|
+
for block in prompt:
|
|
226
|
+
if isinstance(block, dict):
|
|
227
|
+
text = block.get("text", "")
|
|
228
|
+
else:
|
|
229
|
+
text = getattr(block, "text", "") or ""
|
|
230
|
+
if text:
|
|
231
|
+
parts.append(text)
|
|
232
|
+
return "\n".join(parts)
|
|
233
|
+
|
|
234
|
+
@staticmethod
|
|
235
|
+
def _build_summary(agent: AgentCore, success: bool) -> str:
|
|
236
|
+
steps = agent.state.step_count
|
|
237
|
+
files = agent.state.files_modified
|
|
238
|
+
status = "completed" if success else "max steps reached"
|
|
239
|
+
parts = [
|
|
240
|
+
f"Agent finished (status: {status}, steps: {steps})",
|
|
241
|
+
]
|
|
242
|
+
if files:
|
|
243
|
+
parts.append(f"Files modified: {', '.join(sorted(files)[:10])}")
|
|
244
|
+
if steps > 0:
|
|
245
|
+
parts.append(f"Tokens used: {agent.state.total_tokens_used}")
|
|
246
|
+
return "\n".join(parts)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
def main() -> None:
|
|
250
|
+
"""Run the ACP agent server as a standalone process."""
|
|
251
|
+
import argparse
|
|
252
|
+
|
|
253
|
+
parser = argparse.ArgumentParser(description="Coding Agent (ACP Server)")
|
|
254
|
+
parser.add_argument(
|
|
255
|
+
"--profile", "-p",
|
|
256
|
+
default="large_model",
|
|
257
|
+
choices=["large_model", "local_model"],
|
|
258
|
+
help="Configuration profile",
|
|
259
|
+
)
|
|
260
|
+
args = parser.parse_args()
|
|
261
|
+
|
|
262
|
+
logging.basicConfig(level=logging.WARNING)
|
|
263
|
+
|
|
264
|
+
server = CodingAgentServer(profile=args.profile)
|
|
265
|
+
asyncio.run(run_agent(server))
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
if __name__ == "__main__":
|
|
269
|
+
main()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Agent subpackage: the core ReAct loop, planner, and verifier.
|
|
3
|
+
|
|
4
|
+
AgentCore orchestrates the full think → act → observe cycle,
|
|
5
|
+
delegating to Planner for task decomposition and Verifier for
|
|
6
|
+
post-step quality checks.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from coding_agent.agent.core import AgentCore
|
|
10
|
+
from coding_agent.agent.planner import FlatPlanner, Planner, TreeOfThoughtPlanner
|
|
11
|
+
from coding_agent.agent.verifier import VerificationResult, Verifier
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"AgentCore",
|
|
15
|
+
"FlatPlanner",
|
|
16
|
+
"Planner",
|
|
17
|
+
"TreeOfThoughtPlanner",
|
|
18
|
+
"VerificationResult",
|
|
19
|
+
"Verifier",
|
|
20
|
+
]
|