Skip to content

Commit 398a7b9

Browse files
committed
Initial backend setup with core services and API endpoints
1 parent 41f2cf4 commit 398a7b9

56 files changed

Lines changed: 1299 additions & 222 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Main application package for the Atlas AgentVerse backend."""
2+
3+

app/api/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""API subpackage containing routers and endpoints."""
2+
3+

app/api/endpoints/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
"""Endpoints subpackage containing specific API route implementations."""
2+
3+

app/api/endpoints/a2a.py

Lines changed: 123 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
1-
from __future__ import annotations
1+
"""API Endpoints for Google Agent-to-Agent (A2A) Communication Protocol.
22
3+
Implements registration, handshake, and message relay according to the A2A spec.
4+
Ref: https://google.github.io/A2A/#/documentation
35
"""
4-
A2A Endpoints: All endpoints in this file implement the Google Agent2Agent (A2A) protocol.
5-
Any reference to 'a2a' refers exclusively to Google A2A: https://google.github.io/A2A/#/documentation
6-
"""
76

8-
from fastapi import APIRouter, Depends, HTTPException, Request
7+
import logging
8+
from typing import Annotated, Optional
9+
10+
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Request, status
911

1012
from app.core.security import get_current_user_id
1113
from app.models.a2a import (
1214
A2AErrorResponse,
1315
A2AHandshakeRequest,
1416
A2AHandshakeResponse,
17+
A2AMessage,
1518
A2ARegistrationRequest,
1619
A2ARegistrationResponse,
1720
A2ARequest,
@@ -20,23 +23,119 @@
2023
)
2124
from app.services.a2a_service import A2AService
2225

26+
logger = logging.getLogger(__name__)
27+
28+
29+
def get_a2a_service():
30+
"""FastAPI dependency injector for A2AService."""
31+
return A2AService()
32+
33+
2334
router = APIRouter(prefix="/api/v1/a2a", tags=["a2a"])
2435

36+
2537
@router.post("/register", response_model=A2ARegistrationResponse)
26-
async def register_agent(payload: A2ARegistrationRequest):
27-
"""Google A2A: Register this agent with the registry or another agent."""
38+
async def register_agent(
39+
request: A2ARegistrationRequest,
40+
a2a_service: Annotated[A2AService, Depends(get_a2a_service)],
41+
# current_user: Annotated[User, Depends(get_current_active_user)] # TODO: Add auth
42+
) -> A2ARegistrationResponse:
43+
"""Register an agent instance with the A2A service.
44+
45+
Allows an agent to announce its presence and capabilities.
46+
"""
47+
logger.info(f"Received A2A registration request for agent: {request.agent_id}")
2848
try:
29-
return await A2AService.register_agent(payload)
30-
except Exception as exc:
31-
raise HTTPException(status_code=500, detail=str(exc)) from exc
49+
response = await a2a_service.handle_registration(request)
50+
logger.info(f"Agent {request.agent_id} registered successfully.")
51+
return response
52+
except Exception as e:
53+
logger.exception(f"Error during A2A registration for agent {request.agent_id}")
54+
raise HTTPException(
55+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
56+
detail=f"Registration failed: {e}",
57+
) from e
58+
3259

3360
@router.post("/handshake", response_model=A2AHandshakeResponse)
34-
async def handshake(payload: A2AHandshakeRequest):
35-
"""Google A2A: Perform handshake/authentication with another agent."""
61+
async def perform_handshake(
62+
request: A2AHandshakeRequest,
63+
a2a_service: Annotated[A2AService, Depends(get_a2a_service)],
64+
# current_user: Annotated[User, Depends(get_current_active_user)] # TODO: Add auth
65+
) -> A2AHandshakeResponse:
66+
"""Initiate or respond to an A2A handshake between agents.
67+
68+
Establishes secure communication channels.
69+
"""
70+
logger.info(
71+
f"Received A2A handshake request from {request.sender_id} "
72+
f"to {request.receiver_id}"
73+
)
3674
try:
37-
return await A2AService.handshake(payload)
38-
except Exception as exc:
39-
raise HTTPException(status_code=500, detail=str(exc)) from exc
75+
response = await a2a_service.handle_handshake(request)
76+
logger.info(
77+
f"A2A handshake successful between {request.sender_id} "
78+
f"and {request.receiver_id}"
79+
)
80+
return response
81+
except ValueError as ve:
82+
logger.warning(f"A2A handshake validation error: {ve}")
83+
raise HTTPException(
84+
status_code=status.HTTP_400_BAD_REQUEST, detail=f"Handshake error: {ve}"
85+
) from ve
86+
except Exception as e:
87+
logger.exception(
88+
f"Error during A2A handshake between {request.sender_id} "
89+
f"and {request.receiver_id}"
90+
)
91+
raise HTTPException(
92+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
93+
detail=f"Handshake failed: {e}",
94+
) from e
95+
96+
97+
@router.post("/message", status_code=status.HTTP_202_ACCEPTED)
98+
async def relay_message(
99+
request: A2AMessage,
100+
a2a_service: Annotated[A2AService, Depends(get_a2a_service)],
101+
background_tasks: BackgroundTasks,
102+
# current_user: Annotated[User, Depends(get_current_active_user)] # TODO: Add auth
103+
) -> dict[str, str]:
104+
"""Receive and relay an A2A message to the intended recipient agent.
105+
106+
Uses background tasks for asynchronous delivery.
107+
"""
108+
logger.info(
109+
f"Received A2A message relay request from {request.sender_id} "
110+
f"to {request.receiver_id}"
111+
)
112+
try:
113+
# Validate message structure before queueing
114+
# Minimal validation here; deeper validation in the service
115+
if not request.sender_id or not request.receiver_id or not request.payload:
116+
raise ValueError("Invalid A2A message structure")
117+
118+
background_tasks.add_task(a2a_service.handle_message_relay, request)
119+
logger.info(
120+
f"A2A message from {request.sender_id} to {request.receiver_id} "
121+
f"queued for relay."
122+
)
123+
return {"status": "Message relay accepted"}
124+
except ValueError as ve:
125+
logger.warning(f"A2A message relay validation error: {ve}")
126+
raise HTTPException(
127+
status_code=status.HTTP_400_BAD_REQUEST, detail=f"Message relay error: {ve}"
128+
) from ve
129+
except Exception as e:
130+
logger.exception(
131+
f"Error queueing A2A message relay from {request.sender_id} "
132+
f"to {request.receiver_id}"
133+
)
134+
raise HTTPException(
135+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
136+
detail=f"Message relay failed: {e}",
137+
) from e
138+
40139

41140
@router.post("/send", response_model=A2AResponse)
42141
async def send_a2a(
@@ -51,6 +150,7 @@ async def send_a2a(
51150
except Exception as exc:
52151
raise HTTPException(status_code=500, detail=str(exc)) from exc
53152

153+
54154
@router.post("/receive", response_model=A2AResponse)
55155
async def receive_a2a(
56156
request: Request,
@@ -64,6 +164,7 @@ async def receive_a2a(
64164
except Exception as exc:
65165
raise HTTPException(status_code=500, detail=str(exc)) from exc
66166

167+
67168
@router.get("/status/{agent_id}", response_model=A2AStatusResponse)
68169
async def agent_status(agent_id: str):
69170
"""Google A2A: Get the current status of this agent."""
@@ -72,7 +173,11 @@ async def agent_status(agent_id: str):
72173
except Exception as exc:
73174
raise HTTPException(status_code=500, detail=str(exc)) from exc
74175

75-
@router.get("/error", response_model=A2AErrorResponse)
76-
async def error_response(error: str, code: int = None):
176+
177+
@router.post("/error", response_model=A2AErrorResponse)
178+
async def error_response(error: str, code: Optional[int] = None):
77179
"""Google A2A: Return a protocol-compliant error response."""
78-
return await A2AService.error_response(error, code)
180+
try:
181+
return await A2AService.error_response(error, code)
182+
except Exception as exc:
183+
raise HTTPException(status_code=500, detail=str(exc)) from exc

app/api/endpoints/agents.py

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -14,28 +14,23 @@
1414

1515
get_current_user_id = Depends(_get_current_user_id)
1616

17+
1718
@router.post("/", response_model=AgentOut, status_code=status.HTTP_201_CREATED)
18-
async def create_agent_endpoint(
19-
agent: AgentCreate,
20-
user_id: str = get_current_user_id
21-
):
19+
async def create_agent_endpoint(agent: AgentCreate, user_id: str = get_current_user_id):
2220
"""Create a new agent for the current user."""
2321
db_agent = await create_agent(user_id, agent)
2422
return db_agent
2523

24+
2625
@router.get("/", response_model=list[AgentOut])
27-
async def list_agents_endpoint(
28-
user_id: str = get_current_user_id
29-
):
26+
async def list_agents_endpoint(user_id: str = get_current_user_id):
3027
"""List all agents for the current user."""
3128
agents = await get_agents_by_user(user_id)
3229
return agents
3330

31+
3432
@router.get("/{agent_id}", response_model=AgentOut)
35-
async def get_agent_endpoint(
36-
agent_id: str,
37-
user_id: str = get_current_user_id
38-
):
33+
async def get_agent_endpoint(agent_id: str, user_id: str = get_current_user_id):
3934
"""Retrieve a specific agent by ID for the current user."""
4035
agent = await get_agent_by_id(agent_id, user_id)
4136
if not agent:
@@ -45,11 +40,10 @@ async def get_agent_endpoint(
4540
)
4641
return agent
4742

43+
4844
@router.put("/{agent_id}", response_model=AgentOut)
4945
async def update_agent_endpoint(
50-
agent_id: str,
51-
agent_update: AgentUpdate,
52-
user_id: str = get_current_user_id
46+
agent_id: str, agent_update: AgentUpdate, user_id: str = get_current_user_id
5347
):
5448
"""Update an agent by ID for the current user."""
5549
updated = await update_agent(agent_id, user_id, agent_update)
@@ -60,11 +54,9 @@ async def update_agent_endpoint(
6054
)
6155
return updated
6256

57+
6358
@router.delete("/{agent_id}", response_model=AgentOut)
64-
async def archive_agent_endpoint(
65-
agent_id: str,
66-
user_id: str = get_current_user_id
67-
):
59+
async def archive_agent_endpoint(agent_id: str, user_id: str = get_current_user_id):
6860
"""Archive (delete) an agent by ID for the current user."""
6961
archived = await archive_agent(agent_id, user_id)
7062
if not archived:

app/api/endpoints/chat.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -12,31 +12,31 @@
1212

1313
router = APIRouter(prefix="/chat", tags=["chat"])
1414

15+
1516
@router.post(
1617
"/sessions",
1718
response_model=ChatSessionOut,
1819
status_code=status.HTTP_201_CREATED,
1920
)
2021
async def create_session_endpoint(
21-
session: ChatSessionCreate,
22-
user_id: str = Depends(get_current_user_id)
22+
session: ChatSessionCreate, user_id: str = Depends(get_current_user_id)
2323
):
2424
db_session = await create_session(user_id, session)
2525
return db_session
2626

27+
2728
@router.get("/sessions", response_model=List[ChatSessionOut])
28-
async def list_sessions_endpoint(
29-
user_id: str = Depends(get_current_user_id)
30-
):
29+
async def list_sessions_endpoint(user_id: str = Depends(get_current_user_id)):
3130
sessions = await list_sessions(user_id)
3231
return sessions
3332

33+
3434
@router.get("/sessions/{session_id}/messages", response_model=List[ChatMessageOut])
3535
async def get_messages_endpoint(
3636
session_id: str,
3737
limit: int = Query(50, ge=1, le=100),
3838
offset: int = Query(0, ge=0),
39-
user_id: str = Depends(get_current_user_id)
39+
user_id: str = Depends(get_current_user_id),
4040
):
4141
# (Optional: check session ownership here)
4242
messages = await get_messages(session_id, limit=limit, offset=offset)

app/api/endpoints/keys.py

Lines changed: 8 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,34 +12,31 @@
1212

1313
router = APIRouter(prefix="/keys", tags=["keys"])
1414

15+
1516
class APIKeyIn(BaseModel):
1617
service: str
1718
key: str
1819

20+
1921
class APIKeyMetaOut(BaseModel):
2022
id: str
2123
service: str
2224
created_at: Optional[str]
2325
last_used_at: Optional[str]
2426

27+
2528
@router.post("/", status_code=status.HTTP_201_CREATED)
26-
async def add_key(
27-
key_in: APIKeyIn,
28-
user_id: str = Depends(get_current_user_id)
29-
):
29+
async def add_key(key_in: APIKeyIn, user_id: str = Depends(get_current_user_id)):
3030
await store_api_key(user_id, key_in.service, key_in.key)
3131
return {"status": "ok"}
3232

33+
3334
@router.get("/", response_model=List[APIKeyMetaOut])
34-
async def get_keys(
35-
user_id: str = Depends(get_current_user_id)
36-
):
35+
async def get_keys(user_id: str = Depends(get_current_user_id)):
3736
return await list_api_keys(user_id)
3837

38+
3939
@router.delete("/{key_id}", status_code=status.HTTP_204_NO_CONTENT)
40-
async def remove_key(
41-
key_id: str,
42-
user_id: str = Depends(get_current_user_id)
43-
):
40+
async def remove_key(key_id: str, user_id: str = Depends(get_current_user_id)):
4441
await delete_api_key(user_id, key_id)
4542
return

0 commit comments

Comments
 (0)