emdash-core 0.1.7__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.
Files changed (187) hide show
  1. emdash_core/__init__.py +3 -0
  2. emdash_core/agent/__init__.py +37 -0
  3. emdash_core/agent/agents.py +225 -0
  4. emdash_core/agent/code_reviewer.py +476 -0
  5. emdash_core/agent/compaction.py +143 -0
  6. emdash_core/agent/context_manager.py +140 -0
  7. emdash_core/agent/events.py +338 -0
  8. emdash_core/agent/handlers.py +224 -0
  9. emdash_core/agent/inprocess_subagent.py +377 -0
  10. emdash_core/agent/mcp/__init__.py +50 -0
  11. emdash_core/agent/mcp/client.py +346 -0
  12. emdash_core/agent/mcp/config.py +302 -0
  13. emdash_core/agent/mcp/manager.py +496 -0
  14. emdash_core/agent/mcp/tool_factory.py +213 -0
  15. emdash_core/agent/prompts/__init__.py +38 -0
  16. emdash_core/agent/prompts/main_agent.py +104 -0
  17. emdash_core/agent/prompts/subagents.py +131 -0
  18. emdash_core/agent/prompts/workflow.py +136 -0
  19. emdash_core/agent/providers/__init__.py +34 -0
  20. emdash_core/agent/providers/base.py +143 -0
  21. emdash_core/agent/providers/factory.py +80 -0
  22. emdash_core/agent/providers/models.py +220 -0
  23. emdash_core/agent/providers/openai_provider.py +463 -0
  24. emdash_core/agent/providers/transformers_provider.py +217 -0
  25. emdash_core/agent/research/__init__.py +81 -0
  26. emdash_core/agent/research/agent.py +143 -0
  27. emdash_core/agent/research/controller.py +254 -0
  28. emdash_core/agent/research/critic.py +428 -0
  29. emdash_core/agent/research/macros.py +469 -0
  30. emdash_core/agent/research/planner.py +449 -0
  31. emdash_core/agent/research/researcher.py +436 -0
  32. emdash_core/agent/research/state.py +523 -0
  33. emdash_core/agent/research/synthesizer.py +594 -0
  34. emdash_core/agent/reviewer_profile.py +475 -0
  35. emdash_core/agent/rules.py +123 -0
  36. emdash_core/agent/runner.py +601 -0
  37. emdash_core/agent/session.py +262 -0
  38. emdash_core/agent/spec_schema.py +66 -0
  39. emdash_core/agent/specification.py +479 -0
  40. emdash_core/agent/subagent.py +397 -0
  41. emdash_core/agent/subagent_prompts.py +13 -0
  42. emdash_core/agent/toolkit.py +482 -0
  43. emdash_core/agent/toolkits/__init__.py +64 -0
  44. emdash_core/agent/toolkits/base.py +96 -0
  45. emdash_core/agent/toolkits/explore.py +47 -0
  46. emdash_core/agent/toolkits/plan.py +55 -0
  47. emdash_core/agent/tools/__init__.py +141 -0
  48. emdash_core/agent/tools/analytics.py +436 -0
  49. emdash_core/agent/tools/base.py +131 -0
  50. emdash_core/agent/tools/coding.py +484 -0
  51. emdash_core/agent/tools/github_mcp.py +592 -0
  52. emdash_core/agent/tools/history.py +13 -0
  53. emdash_core/agent/tools/modes.py +153 -0
  54. emdash_core/agent/tools/plan.py +206 -0
  55. emdash_core/agent/tools/plan_write.py +135 -0
  56. emdash_core/agent/tools/search.py +412 -0
  57. emdash_core/agent/tools/spec.py +341 -0
  58. emdash_core/agent/tools/task.py +262 -0
  59. emdash_core/agent/tools/task_output.py +204 -0
  60. emdash_core/agent/tools/tasks.py +454 -0
  61. emdash_core/agent/tools/traversal.py +588 -0
  62. emdash_core/agent/tools/web.py +179 -0
  63. emdash_core/analytics/__init__.py +5 -0
  64. emdash_core/analytics/engine.py +1286 -0
  65. emdash_core/api/__init__.py +5 -0
  66. emdash_core/api/agent.py +308 -0
  67. emdash_core/api/agents.py +154 -0
  68. emdash_core/api/analyze.py +264 -0
  69. emdash_core/api/auth.py +173 -0
  70. emdash_core/api/context.py +77 -0
  71. emdash_core/api/db.py +121 -0
  72. emdash_core/api/embed.py +131 -0
  73. emdash_core/api/feature.py +143 -0
  74. emdash_core/api/health.py +93 -0
  75. emdash_core/api/index.py +162 -0
  76. emdash_core/api/plan.py +110 -0
  77. emdash_core/api/projectmd.py +210 -0
  78. emdash_core/api/query.py +320 -0
  79. emdash_core/api/research.py +122 -0
  80. emdash_core/api/review.py +161 -0
  81. emdash_core/api/router.py +76 -0
  82. emdash_core/api/rules.py +116 -0
  83. emdash_core/api/search.py +119 -0
  84. emdash_core/api/spec.py +99 -0
  85. emdash_core/api/swarm.py +223 -0
  86. emdash_core/api/tasks.py +109 -0
  87. emdash_core/api/team.py +120 -0
  88. emdash_core/auth/__init__.py +17 -0
  89. emdash_core/auth/github.py +389 -0
  90. emdash_core/config.py +74 -0
  91. emdash_core/context/__init__.py +52 -0
  92. emdash_core/context/models.py +50 -0
  93. emdash_core/context/providers/__init__.py +11 -0
  94. emdash_core/context/providers/base.py +74 -0
  95. emdash_core/context/providers/explored_areas.py +183 -0
  96. emdash_core/context/providers/touched_areas.py +360 -0
  97. emdash_core/context/registry.py +73 -0
  98. emdash_core/context/reranker.py +199 -0
  99. emdash_core/context/service.py +260 -0
  100. emdash_core/context/session.py +352 -0
  101. emdash_core/core/__init__.py +104 -0
  102. emdash_core/core/config.py +454 -0
  103. emdash_core/core/exceptions.py +55 -0
  104. emdash_core/core/models.py +265 -0
  105. emdash_core/core/review_config.py +57 -0
  106. emdash_core/db/__init__.py +67 -0
  107. emdash_core/db/auth.py +134 -0
  108. emdash_core/db/models.py +91 -0
  109. emdash_core/db/provider.py +222 -0
  110. emdash_core/db/providers/__init__.py +5 -0
  111. emdash_core/db/providers/supabase.py +452 -0
  112. emdash_core/embeddings/__init__.py +24 -0
  113. emdash_core/embeddings/indexer.py +534 -0
  114. emdash_core/embeddings/models.py +192 -0
  115. emdash_core/embeddings/providers/__init__.py +7 -0
  116. emdash_core/embeddings/providers/base.py +112 -0
  117. emdash_core/embeddings/providers/fireworks.py +141 -0
  118. emdash_core/embeddings/providers/openai.py +104 -0
  119. emdash_core/embeddings/registry.py +146 -0
  120. emdash_core/embeddings/service.py +215 -0
  121. emdash_core/graph/__init__.py +26 -0
  122. emdash_core/graph/builder.py +134 -0
  123. emdash_core/graph/connection.py +692 -0
  124. emdash_core/graph/schema.py +416 -0
  125. emdash_core/graph/writer.py +667 -0
  126. emdash_core/ingestion/__init__.py +7 -0
  127. emdash_core/ingestion/change_detector.py +150 -0
  128. emdash_core/ingestion/git/__init__.py +5 -0
  129. emdash_core/ingestion/git/commit_analyzer.py +196 -0
  130. emdash_core/ingestion/github/__init__.py +6 -0
  131. emdash_core/ingestion/github/pr_fetcher.py +296 -0
  132. emdash_core/ingestion/github/task_extractor.py +100 -0
  133. emdash_core/ingestion/orchestrator.py +540 -0
  134. emdash_core/ingestion/parsers/__init__.py +10 -0
  135. emdash_core/ingestion/parsers/base_parser.py +66 -0
  136. emdash_core/ingestion/parsers/call_graph_builder.py +121 -0
  137. emdash_core/ingestion/parsers/class_extractor.py +154 -0
  138. emdash_core/ingestion/parsers/function_extractor.py +202 -0
  139. emdash_core/ingestion/parsers/import_analyzer.py +119 -0
  140. emdash_core/ingestion/parsers/python_parser.py +123 -0
  141. emdash_core/ingestion/parsers/registry.py +72 -0
  142. emdash_core/ingestion/parsers/ts_ast_parser.js +313 -0
  143. emdash_core/ingestion/parsers/typescript_parser.py +278 -0
  144. emdash_core/ingestion/repository.py +346 -0
  145. emdash_core/models/__init__.py +38 -0
  146. emdash_core/models/agent.py +68 -0
  147. emdash_core/models/index.py +77 -0
  148. emdash_core/models/query.py +113 -0
  149. emdash_core/planning/__init__.py +7 -0
  150. emdash_core/planning/agent_api.py +413 -0
  151. emdash_core/planning/context_builder.py +265 -0
  152. emdash_core/planning/feature_context.py +232 -0
  153. emdash_core/planning/feature_expander.py +646 -0
  154. emdash_core/planning/llm_explainer.py +198 -0
  155. emdash_core/planning/similarity.py +509 -0
  156. emdash_core/planning/team_focus.py +821 -0
  157. emdash_core/server.py +153 -0
  158. emdash_core/sse/__init__.py +5 -0
  159. emdash_core/sse/stream.py +196 -0
  160. emdash_core/swarm/__init__.py +17 -0
  161. emdash_core/swarm/merge_agent.py +383 -0
  162. emdash_core/swarm/session_manager.py +274 -0
  163. emdash_core/swarm/swarm_runner.py +226 -0
  164. emdash_core/swarm/task_definition.py +137 -0
  165. emdash_core/swarm/worker_spawner.py +319 -0
  166. emdash_core/swarm/worktree_manager.py +278 -0
  167. emdash_core/templates/__init__.py +10 -0
  168. emdash_core/templates/defaults/agent-builder.md.template +82 -0
  169. emdash_core/templates/defaults/focus.md.template +115 -0
  170. emdash_core/templates/defaults/pr-review-enhanced.md.template +309 -0
  171. emdash_core/templates/defaults/pr-review.md.template +80 -0
  172. emdash_core/templates/defaults/project.md.template +85 -0
  173. emdash_core/templates/defaults/research_critic.md.template +112 -0
  174. emdash_core/templates/defaults/research_planner.md.template +85 -0
  175. emdash_core/templates/defaults/research_synthesizer.md.template +128 -0
  176. emdash_core/templates/defaults/reviewer.md.template +81 -0
  177. emdash_core/templates/defaults/spec.md.template +41 -0
  178. emdash_core/templates/defaults/tasks.md.template +78 -0
  179. emdash_core/templates/loader.py +296 -0
  180. emdash_core/utils/__init__.py +45 -0
  181. emdash_core/utils/git.py +84 -0
  182. emdash_core/utils/image.py +502 -0
  183. emdash_core/utils/logger.py +51 -0
  184. emdash_core-0.1.7.dist-info/METADATA +35 -0
  185. emdash_core-0.1.7.dist-info/RECORD +187 -0
  186. emdash_core-0.1.7.dist-info/WHEEL +4 -0
  187. emdash_core-0.1.7.dist-info/entry_points.txt +3 -0
@@ -0,0 +1,5 @@
1
+ """API routes for emdash-core."""
2
+
3
+ from .router import api_router
4
+
5
+ __all__ = ["api_router"]
@@ -0,0 +1,308 @@
1
+ """Agent chat endpoint with SSE streaming."""
2
+
3
+ import asyncio
4
+ import sys
5
+ import uuid
6
+ from concurrent.futures import ThreadPoolExecutor
7
+ from pathlib import Path
8
+ from typing import Optional
9
+
10
+ from fastapi import APIRouter, HTTPException
11
+ from fastapi.responses import StreamingResponse
12
+
13
+ from ..config import get_config
14
+ from ..models.agent import AgentChatRequest, AgentMode
15
+ from ..sse.stream import SSEHandler, EventType
16
+
17
+ router = APIRouter(prefix="/agent", tags=["agent"])
18
+
19
+ # Thread pool for running blocking agent code
20
+ _executor = ThreadPoolExecutor(max_workers=4)
21
+
22
+ # Active sessions (in-memory for now)
23
+ _sessions: dict[str, dict] = {}
24
+
25
+
26
+ def _ensure_emdash_importable():
27
+ """Ensure the emdash_core package is importable.
28
+
29
+ This is now a no-op since we use emdash_core directly.
30
+ """
31
+ pass # emdash_core is already in the package
32
+
33
+
34
+ def _run_agent_sync(
35
+ message: str,
36
+ model: str,
37
+ max_iterations: int,
38
+ sse_handler: SSEHandler,
39
+ session_id: str,
40
+ images: list = None,
41
+ ):
42
+ """Run the agent synchronously (in thread pool).
43
+
44
+ This function runs in a background thread and emits events
45
+ to the SSE handler for streaming to the client.
46
+ """
47
+ try:
48
+ _ensure_emdash_importable()
49
+
50
+ # Import agent components from emdash_core
51
+ from ..agent.runner import AgentRunner
52
+ from ..agent.events import AgentEventEmitter
53
+
54
+ # Create an emitter that forwards to SSE handler
55
+ class SSEBridgeHandler:
56
+ """Bridges AgentEventEmitter to SSEHandler."""
57
+
58
+ def __init__(self, sse_handler: SSEHandler):
59
+ self._sse = sse_handler
60
+
61
+ def handle(self, event):
62
+ """Forward event to SSE handler."""
63
+ self._sse.handle(event)
64
+
65
+ # Create agent with event emitter
66
+ emitter = AgentEventEmitter(agent_name="Emdash Code")
67
+ emitter.add_handler(SSEBridgeHandler(sse_handler))
68
+
69
+ runner = AgentRunner(
70
+ model=model,
71
+ verbose=True,
72
+ max_iterations=max_iterations,
73
+ emitter=emitter,
74
+ )
75
+
76
+ # Convert image data if provided
77
+ agent_images = None
78
+ if images:
79
+ from ..agent.providers.base import ImageContent
80
+ agent_images = [
81
+ ImageContent(data=img.data, format=img.format)
82
+ for img in images
83
+ ]
84
+
85
+ # Run the agent
86
+ response = runner.run(message, images=agent_images)
87
+
88
+ # Store session state
89
+ _sessions[session_id] = {
90
+ "runner": runner,
91
+ "message_count": 1,
92
+ "model": model,
93
+ }
94
+
95
+ return response
96
+
97
+ except Exception as e:
98
+ # Emit error event
99
+ sse_handler.emit(EventType.ERROR, {
100
+ "message": str(e),
101
+ "details": None,
102
+ })
103
+ raise
104
+
105
+
106
+ async def _run_agent_async(
107
+ request: AgentChatRequest,
108
+ sse_handler: SSEHandler,
109
+ session_id: str,
110
+ ):
111
+ """Run agent in thread pool and stream events."""
112
+ config = get_config()
113
+
114
+ # Get model from request or config
115
+ model = request.model or config.default_model
116
+ max_iterations = request.options.max_iterations
117
+
118
+ # Emit session start
119
+ sse_handler.emit(EventType.SESSION_START, {
120
+ "agent_name": "Emdash Code",
121
+ "model": model,
122
+ "session_id": session_id,
123
+ "query": request.message,
124
+ })
125
+
126
+ loop = asyncio.get_event_loop()
127
+
128
+ try:
129
+ # Run agent in thread pool
130
+ await loop.run_in_executor(
131
+ _executor,
132
+ _run_agent_sync,
133
+ request.message,
134
+ model,
135
+ max_iterations,
136
+ sse_handler,
137
+ session_id,
138
+ request.images,
139
+ )
140
+
141
+ # Emit session end
142
+ sse_handler.emit(EventType.SESSION_END, {
143
+ "success": True,
144
+ "session_id": session_id,
145
+ })
146
+
147
+ except Exception as e:
148
+ sse_handler.emit(EventType.SESSION_END, {
149
+ "success": False,
150
+ "error": str(e),
151
+ "session_id": session_id,
152
+ })
153
+
154
+ finally:
155
+ sse_handler.close()
156
+
157
+
158
+ @router.post("/chat")
159
+ async def agent_chat(request: AgentChatRequest):
160
+ """Start an agent chat session with SSE streaming.
161
+
162
+ The response is a Server-Sent Events stream containing:
163
+ - session_start: Initial session info
164
+ - tool_start: When a tool begins execution
165
+ - tool_result: When a tool completes
166
+ - thinking: Agent reasoning messages
167
+ - response/partial_response: Agent text output
168
+ - clarification: When agent needs user input
169
+ - error/warning: Error messages
170
+ - session_end: Session completion
171
+
172
+ Example:
173
+ curl -N -X POST http://localhost:8765/api/agent/chat \\
174
+ -H "Content-Type: application/json" \\
175
+ -d '{"message": "Find authentication code"}'
176
+ """
177
+ # Generate or use provided session ID
178
+ session_id = request.session_id or str(uuid.uuid4())
179
+
180
+ # Create SSE handler
181
+ sse_handler = SSEHandler(agent_name="Emdash Code")
182
+
183
+ # Start agent in background
184
+ asyncio.create_task(_run_agent_async(request, sse_handler, session_id))
185
+
186
+ # Return SSE stream
187
+ return StreamingResponse(
188
+ sse_handler,
189
+ media_type="text/event-stream",
190
+ headers={
191
+ "Cache-Control": "no-cache",
192
+ "Connection": "keep-alive",
193
+ "X-Session-ID": session_id,
194
+ },
195
+ )
196
+
197
+
198
+ @router.post("/chat/{session_id}/continue")
199
+ async def continue_chat(session_id: str, request: AgentChatRequest):
200
+ """Continue an existing chat session.
201
+
202
+ This allows multi-turn conversations by reusing the session state.
203
+ """
204
+ if session_id not in _sessions:
205
+ raise HTTPException(
206
+ status_code=404,
207
+ detail=f"Session {session_id} not found"
208
+ )
209
+
210
+ session = _sessions[session_id]
211
+ runner = session.get("runner")
212
+ if not runner:
213
+ raise HTTPException(
214
+ status_code=400,
215
+ detail="Session has no active runner"
216
+ )
217
+
218
+ # Create SSE handler
219
+ sse_handler = SSEHandler(agent_name="Emdash Code")
220
+
221
+ async def _continue_session():
222
+ sse_handler.emit(EventType.SESSION_START, {
223
+ "agent_name": "Emdash Code",
224
+ "model": session["model"],
225
+ "session_id": session_id,
226
+ "query": request.message,
227
+ "continued": True,
228
+ })
229
+
230
+ loop = asyncio.get_event_loop()
231
+
232
+ try:
233
+ # Wire up SSE handler to runner's emitter for this request
234
+ from ..agent.events import AgentEventEmitter
235
+
236
+ class SSEBridgeHandler:
237
+ def __init__(self, sse_handler: SSEHandler):
238
+ self._sse = sse_handler
239
+
240
+ def handle(self, event):
241
+ self._sse.handle(event)
242
+
243
+ # Create fresh emitter with new SSE handler
244
+ emitter = AgentEventEmitter(agent_name="Emdash Code")
245
+ emitter.add_handler(SSEBridgeHandler(sse_handler))
246
+ runner.emitter = emitter
247
+
248
+ # Continue conversation in thread pool
249
+ await loop.run_in_executor(
250
+ _executor,
251
+ lambda: runner.chat(request.message),
252
+ )
253
+
254
+ session["message_count"] += 1
255
+
256
+ sse_handler.emit(EventType.SESSION_END, {
257
+ "success": True,
258
+ "session_id": session_id,
259
+ })
260
+
261
+ except Exception as e:
262
+ sse_handler.emit(EventType.ERROR, {
263
+ "message": str(e),
264
+ })
265
+ sse_handler.emit(EventType.SESSION_END, {
266
+ "success": False,
267
+ "error": str(e),
268
+ "session_id": session_id,
269
+ })
270
+
271
+ finally:
272
+ sse_handler.close()
273
+
274
+ asyncio.create_task(_continue_session())
275
+
276
+ return StreamingResponse(
277
+ sse_handler,
278
+ media_type="text/event-stream",
279
+ headers={
280
+ "Cache-Control": "no-cache",
281
+ "Connection": "keep-alive",
282
+ "X-Session-ID": session_id,
283
+ },
284
+ )
285
+
286
+
287
+ @router.get("/sessions")
288
+ async def list_sessions():
289
+ """List active chat sessions."""
290
+ return {
291
+ "sessions": [
292
+ {
293
+ "session_id": sid,
294
+ "model": data.get("model"),
295
+ "message_count": data.get("message_count", 0),
296
+ }
297
+ for sid, data in _sessions.items()
298
+ ]
299
+ }
300
+
301
+
302
+ @router.delete("/sessions/{session_id}")
303
+ async def delete_session(session_id: str):
304
+ """Delete a chat session."""
305
+ if session_id in _sessions:
306
+ del _sessions[session_id]
307
+ return {"deleted": True}
308
+ raise HTTPException(status_code=404, detail="Session not found")
@@ -0,0 +1,154 @@
1
+ """Agent management endpoints."""
2
+
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ from fastapi import APIRouter, HTTPException
7
+ from pydantic import BaseModel, Field
8
+
9
+ from ..config import get_config
10
+
11
+ router = APIRouter(prefix="/agents", tags=["agents"])
12
+
13
+
14
+ class AgentInfo(BaseModel):
15
+ """Agent information."""
16
+ name: str
17
+ path: str
18
+ exists: bool
19
+
20
+
21
+ class AgentListResponse(BaseModel):
22
+ """Response for listing agents."""
23
+ agents: list[AgentInfo]
24
+
25
+
26
+ class CreateAgentRequest(BaseModel):
27
+ """Request to create an agent."""
28
+ name: str = Field(..., description="Agent name")
29
+ description: Optional[str] = Field(default=None, description="Agent description")
30
+
31
+
32
+ class CreateAgentResponse(BaseModel):
33
+ """Response from creating an agent."""
34
+ success: bool
35
+ name: str
36
+ path: Optional[str] = None
37
+ error: Optional[str] = None
38
+
39
+
40
+ def _get_agents_dir() -> Path:
41
+ """Get the agents directory for the current repo."""
42
+ config = get_config()
43
+ if config.repo_root:
44
+ return Path(config.repo_root) / ".emdash" / "agents"
45
+ return Path.cwd() / ".emdash" / "agents"
46
+
47
+
48
+ @router.get("", response_model=AgentListResponse)
49
+ async def list_agents():
50
+ """List all configured agents.
51
+
52
+ Returns agents from the .emdash/agents directory.
53
+ """
54
+ agents_dir = _get_agents_dir()
55
+ agents = []
56
+
57
+ if agents_dir.exists():
58
+ for agent_file in agents_dir.glob("*.md"):
59
+ agents.append(AgentInfo(
60
+ name=agent_file.stem,
61
+ path=str(agent_file),
62
+ exists=True,
63
+ ))
64
+
65
+ return AgentListResponse(agents=agents)
66
+
67
+
68
+ @router.post("", response_model=CreateAgentResponse)
69
+ async def create_agent(request: CreateAgentRequest):
70
+ """Create a new agent configuration.
71
+
72
+ Creates an agent markdown file in .emdash/agents/
73
+ """
74
+ agents_dir = _get_agents_dir()
75
+ agents_dir.mkdir(parents=True, exist_ok=True)
76
+
77
+ agent_file = agents_dir / f"{request.name}.md"
78
+
79
+ if agent_file.exists():
80
+ raise HTTPException(
81
+ status_code=409,
82
+ detail=f"Agent '{request.name}' already exists"
83
+ )
84
+
85
+ # Create agent template
86
+ description = request.description or f"Custom agent: {request.name}"
87
+ content = f"""# {request.name}
88
+
89
+ {description}
90
+
91
+ ## Instructions
92
+
93
+ Define the agent's behavior and capabilities here.
94
+
95
+ ## Tools
96
+
97
+ List the tools this agent can use:
98
+ - search
99
+ - read_file
100
+ - write_file
101
+
102
+ ## Examples
103
+
104
+ Provide example interactions here.
105
+ """
106
+
107
+ try:
108
+ agent_file.write_text(content)
109
+ return CreateAgentResponse(
110
+ success=True,
111
+ name=request.name,
112
+ path=str(agent_file),
113
+ )
114
+ except Exception as e:
115
+ return CreateAgentResponse(
116
+ success=False,
117
+ name=request.name,
118
+ error=str(e),
119
+ )
120
+
121
+
122
+ @router.get("/{name}", response_model=AgentInfo)
123
+ async def get_agent(name: str):
124
+ """Get a specific agent's information."""
125
+ agents_dir = _get_agents_dir()
126
+ agent_file = agents_dir / f"{name}.md"
127
+
128
+ if not agent_file.exists():
129
+ raise HTTPException(
130
+ status_code=404,
131
+ detail=f"Agent '{name}' not found"
132
+ )
133
+
134
+ return AgentInfo(
135
+ name=name,
136
+ path=str(agent_file),
137
+ exists=True,
138
+ )
139
+
140
+
141
+ @router.delete("/{name}")
142
+ async def delete_agent(name: str):
143
+ """Delete an agent configuration."""
144
+ agents_dir = _get_agents_dir()
145
+ agent_file = agents_dir / f"{name}.md"
146
+
147
+ if not agent_file.exists():
148
+ raise HTTPException(
149
+ status_code=404,
150
+ detail=f"Agent '{name}' not found"
151
+ )
152
+
153
+ agent_file.unlink()
154
+ return {"deleted": True, "name": name}