claxcode 0.1.1__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.
- claxcode/__init__.py +3 -0
- claxcode/api/__init__.py +1 -0
- claxcode/api/chat.py +51 -0
- claxcode/api/session.py +66 -0
- claxcode/api/subagents.py +82 -0
- claxcode/api/tools.py +38 -0
- claxcode/cli.py +1214 -0
- claxcode/cli_completion.py +174 -0
- claxcode/config.py +54 -0
- claxcode/core/__init__.py +1 -0
- claxcode/core/agent.py +327 -0
- claxcode/core/checkpoint.py +100 -0
- claxcode/core/context.py +114 -0
- claxcode/core/models.py +446 -0
- claxcode/extensions/__init__.py +1 -0
- claxcode/extensions/hooks.py +131 -0
- claxcode/extensions/mcp.py +13 -0
- claxcode/extensions/plugins.py +10 -0
- claxcode/extensions/skills.py +205 -0
- claxcode/main.py +55 -0
- claxcode/memory/__init__.py +23 -0
- claxcode/memory/auto_memory.py +68 -0
- claxcode/memory/long_term.py +196 -0
- claxcode/memory/project_context.py +20 -0
- claxcode/memory/project_memory.py +194 -0
- claxcode/memory/session_store.py +206 -0
- claxcode/models/__init__.py +1 -0
- claxcode/models/session.py +25 -0
- claxcode/models/subagent.py +25 -0
- claxcode/models/tool.py +24 -0
- claxcode/security/__init__.py +1 -0
- claxcode/security/audit.py +54 -0
- claxcode/security/permissions.py +29 -0
- claxcode/skills/bug-fix/SKILL.md +33 -0
- claxcode/skills/code-review/SKILL.md +35 -0
- claxcode/skills/python-refactor/SKILL.md +22 -0
- claxcode/skills/task-workflow/SKILL.md +38 -0
- claxcode/subagents/__init__.py +1 -0
- claxcode/subagents/base.py +108 -0
- claxcode/subagents/executor.py +16 -0
- claxcode/subagents/explore.py +25 -0
- claxcode/subagents/planner.py +25 -0
- claxcode/tools/__init__.py +14 -0
- claxcode/tools/admin.py +170 -0
- claxcode/tools/base.py +19 -0
- claxcode/tools/browser.py +499 -0
- claxcode/tools/code_intelligence.py +68 -0
- claxcode/tools/custom_example.py +76 -0
- claxcode/tools/execution.py +114 -0
- claxcode/tools/file_operations.py +287 -0
- claxcode/tools/memory_tool.py +29 -0
- claxcode/tools/registry.py +35 -0
- claxcode/tools/search.py +157 -0
- claxcode/tools/ssh.py +534 -0
- claxcode/tools/web.py +83 -0
- claxcode-0.1.1.dist-info/METADATA +359 -0
- claxcode-0.1.1.dist-info/RECORD +60 -0
- claxcode-0.1.1.dist-info/WHEEL +4 -0
- claxcode-0.1.1.dist-info/entry_points.txt +3 -0
- claxcode-0.1.1.dist-info/licenses/LICENSE +201 -0
claxcode/__init__.py
ADDED
claxcode/api/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""API routes for ClaxCode."""
|
claxcode/api/chat.py
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"""Chat and streaming API."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from uuid import UUID
|
|
5
|
+
|
|
6
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
7
|
+
from fastapi.responses import StreamingResponse
|
|
8
|
+
from sqlalchemy import select
|
|
9
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
10
|
+
|
|
11
|
+
from claxcode.core.agent import Agent
|
|
12
|
+
from claxcode.memory.session_store import (
|
|
13
|
+
SessionORM,
|
|
14
|
+
get_db_session,
|
|
15
|
+
get_session_messages,
|
|
16
|
+
save_session_messages,
|
|
17
|
+
)
|
|
18
|
+
from claxcode.models.session import ChatRequest
|
|
19
|
+
|
|
20
|
+
router = APIRouter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@router.post("/{session_id}/chat")
|
|
24
|
+
async def chat(
|
|
25
|
+
session_id: UUID,
|
|
26
|
+
request: ChatRequest,
|
|
27
|
+
db: AsyncSession = Depends(get_db_session),
|
|
28
|
+
):
|
|
29
|
+
"""Stream chat events via SSE."""
|
|
30
|
+
result = await db.execute(select(SessionORM).where(SessionORM.id == str(session_id)))
|
|
31
|
+
row = result.scalar_one_or_none()
|
|
32
|
+
if row is None:
|
|
33
|
+
raise HTTPException(status_code=404, detail="会话不存在")
|
|
34
|
+
|
|
35
|
+
agent = Agent(
|
|
36
|
+
session_id=session_id,
|
|
37
|
+
project_path=row.project_path,
|
|
38
|
+
permission_mode=row.permission_mode,
|
|
39
|
+
)
|
|
40
|
+
agent.context.restore(await get_session_messages(session_id))
|
|
41
|
+
|
|
42
|
+
async def event_stream():
|
|
43
|
+
async for event in agent.run(request.message):
|
|
44
|
+
yield f"data: {json.dumps(event)}\n\n"
|
|
45
|
+
# Persist the conversation (minus the system prompt) for the next request
|
|
46
|
+
await save_session_messages(session_id, agent.context.messages[1:])
|
|
47
|
+
|
|
48
|
+
return StreamingResponse(
|
|
49
|
+
event_stream(),
|
|
50
|
+
media_type="text/event-stream",
|
|
51
|
+
)
|
claxcode/api/session.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Session management API."""
|
|
2
|
+
|
|
3
|
+
from uuid import UUID, uuid4
|
|
4
|
+
|
|
5
|
+
from fastapi import APIRouter, Depends, HTTPException
|
|
6
|
+
from sqlalchemy import select
|
|
7
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
8
|
+
|
|
9
|
+
from claxcode.memory.session_store import SessionORM, get_db_session
|
|
10
|
+
from claxcode.models.session import Session, SessionCreate
|
|
11
|
+
|
|
12
|
+
router = APIRouter()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _orm_to_model(orm: SessionORM) -> Session:
|
|
16
|
+
return Session(
|
|
17
|
+
id=UUID(orm.id),
|
|
18
|
+
name=orm.name,
|
|
19
|
+
project_path=orm.project_path,
|
|
20
|
+
permission_mode=orm.permission_mode,
|
|
21
|
+
created_at=orm.created_at,
|
|
22
|
+
updated_at=orm.updated_at,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@router.post("", response_model=Session)
|
|
27
|
+
async def create_session(
|
|
28
|
+
data: SessionCreate,
|
|
29
|
+
db: AsyncSession = Depends(get_db_session),
|
|
30
|
+
):
|
|
31
|
+
session_orm = SessionORM(
|
|
32
|
+
id=str(uuid4()),
|
|
33
|
+
name=data.name,
|
|
34
|
+
project_path=data.project_path,
|
|
35
|
+
permission_mode=data.permission_mode,
|
|
36
|
+
)
|
|
37
|
+
db.add(session_orm)
|
|
38
|
+
await db.commit()
|
|
39
|
+
await db.refresh(session_orm)
|
|
40
|
+
return _orm_to_model(session_orm)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@router.get("", response_model=list[Session])
|
|
44
|
+
async def list_sessions(db: AsyncSession = Depends(get_db_session)):
|
|
45
|
+
result = await db.execute(select(SessionORM).order_by(SessionORM.created_at.desc()))
|
|
46
|
+
return [_orm_to_model(row) for row in result.scalars().all()]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
@router.get("/{session_id}", response_model=Session)
|
|
50
|
+
async def get_session(session_id: UUID, db: AsyncSession = Depends(get_db_session)):
|
|
51
|
+
result = await db.execute(select(SessionORM).where(SessionORM.id == str(session_id)))
|
|
52
|
+
row = result.scalar_one_or_none()
|
|
53
|
+
if row is None:
|
|
54
|
+
raise HTTPException(status_code=404, detail="会话不存在")
|
|
55
|
+
return _orm_to_model(row)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
@router.delete("/{session_id}", status_code=204)
|
|
59
|
+
async def delete_session(session_id: UUID, db: AsyncSession = Depends(get_db_session)):
|
|
60
|
+
result = await db.execute(select(SessionORM).where(SessionORM.id == str(session_id)))
|
|
61
|
+
row = result.scalar_one_or_none()
|
|
62
|
+
if row is None:
|
|
63
|
+
raise HTTPException(status_code=404, detail="会话不存在")
|
|
64
|
+
await db.delete(row)
|
|
65
|
+
await db.commit()
|
|
66
|
+
return None
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""Subagent management API."""
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import json
|
|
5
|
+
from uuid import UUID
|
|
6
|
+
|
|
7
|
+
from fastapi import APIRouter, HTTPException
|
|
8
|
+
from fastapi.responses import StreamingResponse
|
|
9
|
+
|
|
10
|
+
from claxcode.memory.session_store import (
|
|
11
|
+
create_subagent_task,
|
|
12
|
+
finish_subagent_task,
|
|
13
|
+
get_subagent_task,
|
|
14
|
+
)
|
|
15
|
+
from claxcode.models.subagent import SubagentStatus, SubagentTask
|
|
16
|
+
from claxcode.subagents.executor import ExecutorSubagent
|
|
17
|
+
from claxcode.subagents.explore import ExploreSubagent
|
|
18
|
+
from claxcode.subagents.planner import PlannerSubagent
|
|
19
|
+
|
|
20
|
+
router = APIRouter()
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _spawn_subagent(session_id: UUID, agent_type: str, instruction: str):
|
|
24
|
+
if agent_type == "explore":
|
|
25
|
+
return ExploreSubagent(session_id, instruction)
|
|
26
|
+
if agent_type == "planner":
|
|
27
|
+
return PlannerSubagent(session_id, instruction)
|
|
28
|
+
if agent_type == "executor":
|
|
29
|
+
return ExecutorSubagent(session_id, instruction)
|
|
30
|
+
raise ValueError(f"未知的代理类型: {agent_type}")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@router.post("", response_model=SubagentTask)
|
|
34
|
+
async def spawn_subagent(
|
|
35
|
+
session_id: UUID,
|
|
36
|
+
instruction: str,
|
|
37
|
+
agent_type: str = "explore",
|
|
38
|
+
):
|
|
39
|
+
task = SubagentTask(
|
|
40
|
+
session_id=session_id,
|
|
41
|
+
agent_type=agent_type,
|
|
42
|
+
instruction=instruction,
|
|
43
|
+
status=SubagentStatus.RUNNING,
|
|
44
|
+
)
|
|
45
|
+
await create_subagent_task(task)
|
|
46
|
+
|
|
47
|
+
async def run_task():
|
|
48
|
+
results = []
|
|
49
|
+
try:
|
|
50
|
+
agent = _spawn_subagent(session_id, agent_type, instruction)
|
|
51
|
+
async for event in agent.run():
|
|
52
|
+
if event.get("type") == "result":
|
|
53
|
+
results.append(event.get("content", ""))
|
|
54
|
+
await finish_subagent_task(task.id, SubagentStatus.COMPLETED, "\n".join(results))
|
|
55
|
+
except Exception as exc:
|
|
56
|
+
await finish_subagent_task(task.id, SubagentStatus.FAILED, str(exc))
|
|
57
|
+
|
|
58
|
+
asyncio.create_task(run_task())
|
|
59
|
+
return task
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
@router.get("/{task_id}", response_model=SubagentTask)
|
|
63
|
+
async def get_subagent(task_id: UUID):
|
|
64
|
+
task = await get_subagent_task(task_id)
|
|
65
|
+
if task is None:
|
|
66
|
+
raise HTTPException(status_code=404, detail="子代理任务不存在")
|
|
67
|
+
return task
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@router.post("/{task_id}/stream")
|
|
71
|
+
async def stream_subagent(task_id: UUID):
|
|
72
|
+
task = await get_subagent_task(task_id)
|
|
73
|
+
if task is None:
|
|
74
|
+
raise HTTPException(status_code=404, detail="子代理任务不存在")
|
|
75
|
+
|
|
76
|
+
async def event_stream():
|
|
77
|
+
agent = _spawn_subagent(task.session_id, task.agent_type, task.instruction)
|
|
78
|
+
async for event in agent.run():
|
|
79
|
+
yield f"data: {json.dumps(event)}\n\n"
|
|
80
|
+
yield "data: {\"type\": \"done\"}\n\n"
|
|
81
|
+
|
|
82
|
+
return StreamingResponse(event_stream(), media_type="text/event-stream")
|
claxcode/api/tools.py
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"""Tool listing and invocation API."""
|
|
2
|
+
|
|
3
|
+
from fastapi import APIRouter
|
|
4
|
+
|
|
5
|
+
from claxcode.models.tool import ToolInfo, ToolInvokeRequest, ToolInvokeResponse
|
|
6
|
+
from claxcode.tools.registry import registry
|
|
7
|
+
|
|
8
|
+
router = APIRouter()
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@router.get("", response_model=list[ToolInfo])
|
|
12
|
+
async def list_tools():
|
|
13
|
+
return [
|
|
14
|
+
ToolInfo(
|
|
15
|
+
name=tool.name,
|
|
16
|
+
description=tool.description,
|
|
17
|
+
input_schema=tool.input_schema,
|
|
18
|
+
category=tool.category,
|
|
19
|
+
required_permissions=tool.required_permissions,
|
|
20
|
+
)
|
|
21
|
+
for tool in registry.list_tools()
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@router.post("/invoke", response_model=ToolInvokeResponse)
|
|
26
|
+
async def invoke_tool(request: ToolInvokeRequest):
|
|
27
|
+
tool = registry.get(request.name)
|
|
28
|
+
if tool is None:
|
|
29
|
+
return ToolInvokeResponse(
|
|
30
|
+
name=request.name,
|
|
31
|
+
output=f"工具 '{request.name}' 不存在",
|
|
32
|
+
error=True,
|
|
33
|
+
)
|
|
34
|
+
try:
|
|
35
|
+
output = await tool.execute(**request.arguments)
|
|
36
|
+
return ToolInvokeResponse(name=tool.name, output=str(output))
|
|
37
|
+
except Exception as exc:
|
|
38
|
+
return ToolInvokeResponse(name=tool.name, output=str(exc), error=True)
|