From db91f7b064a7f1e6c04afb8d255e46b0263c7ab8 Mon Sep 17 00:00:00 2001 From: Human <162091348+human058382928@users.noreply.github.com> Date: Mon, 19 May 2025 20:54:51 -0700 Subject: [PATCH] add new tools endpoints --- agent-tools-ts | 2 +- api/tools.py | 193 ++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 193 insertions(+), 2 deletions(-) diff --git a/agent-tools-ts b/agent-tools-ts index 978b7025..bc57b642 160000 --- a/agent-tools-ts +++ b/agent-tools-ts @@ -1 +1 @@ -Subproject commit 978b7025cd51f839c163f8b343a5ab1934e28bb7 +Subproject commit bc57b642987dc649144f317b58cfd4bf5fff54a0 diff --git a/api/tools.py b/api/tools.py index 0cf77ff3..3a8a99e5 100644 --- a/api/tools.py +++ b/api/tools.py @@ -1,10 +1,20 @@ from typing import List, Optional -from fastapi import APIRouter, HTTPException, Query, Request +from fastapi import APIRouter, Depends, HTTPException, Query, Request +from pydantic import BaseModel, Field # Added import for Pydantic models from starlette.responses import JSONResponse +from api.dependencies import ( + verify_profile_from_token, # Added verify_profile_from_token +) +from backend.factory import backend # Added backend factory +from backend.models import UUID, AgentFilter, Profile # Added Profile, AgentFilter from lib.logger import configure_logger from lib.tools import Tool, get_available_tools +from tools.dao_ext_action_proposals import ( + ProposeActionSendMessageTool, # Added ProposeActionSendMessageTool +) +from tools.faktory import FaktoryExecuteBuyTool # Added import for Faktory tool # Configure logger logger = configure_logger(__name__) @@ -16,6 +26,48 @@ available_tools = get_available_tools() +class FaktoryBuyTokenRequest(BaseModel): + """Request body for executing a Faktory buy order.""" + + # agent_id: UUID = Field(..., description="The ID of the agent performing the action") # Removed agent_id + btc_amount: str = Field( + ..., + description="Amount of BTC to spend on the purchase in standard units (e.g. 0.0004 = 0.0004 BTC or 40000 sats)", + ) + dao_token_dex_contract_address: str = Field( + ..., description="Contract principal where the DAO token is listed" + ) + slippage: Optional[str] = Field( + default="15", + description="Slippage tolerance in basis points (default: 15, which is 0.15%)", + ) + + +class ProposeSendMessageRequest(BaseModel): + """Request body for proposing a DAO action to send a message.""" + + action_proposals_voting_extension: str = Field( + ..., + description="Contract principal where the DAO creates action proposals for voting by DAO members.", + ) + action_proposal_contract_to_execute: str = Field( + ..., + description="Contract principal of the action proposal that executes sending a message.", + ) + dao_token_contract_address: str = Field( + ..., + description="Contract principal of the token used by the DAO for voting.", + ) + message: str = Field( + ..., + description="Message to be sent through the DAO proposal system.", + ) + memo: Optional[str] = Field( + None, + description="Optional memo to include with the proposal.", + ) + + @router.get("/available", response_model=List[Tool]) async def get_tools( request: Request, @@ -131,3 +183,142 @@ async def search_tools( except Exception as e: logger.error(f"Failed to search tools with query '{query}'", exc_info=e) raise HTTPException(status_code=500, detail=f"Failed to search tools: {str(e)}") + + +@router.post("/faktory/execute_buy") +async def execute_faktory_buy( + request: Request, + payload: FaktoryBuyTokenRequest, + profile: Profile = Depends(verify_profile_from_token), # Added auth dependency +) -> JSONResponse: + """Execute a buy order on Faktory DEX. + + This endpoint allows an authenticated user's agent to execute a buy order + for a specified token using BTC on the Faktory DEX. + + Args: + request: The FastAPI request object. + payload: The request body containing btc_amount, + dao_token_dex_contract_address, and optional slippage. + profile: The authenticated user's profile. + + Returns: + JSONResponse: The result of the buy order execution. + + Raises: + HTTPException: If there's an error executing the buy order, or if the + agent for the profile is not found. + """ + try: + logger.info( + f"Faktory execute buy request received from {request.client.host if request.client else 'unknown'} for profile {profile.id}" + ) + + # Get agent_id from profile_id + agents = backend.list_agents(AgentFilter(profile_id=profile.id)) + if not agents: + logger.error(f"No agent found for profile ID: {profile.id}") + raise HTTPException( + status_code=404, + detail=f"No agent found for profile ID: {profile.id}", + ) + + agent = agents[0] # Assuming the first agent is the one to use + agent_id = agent.id + + logger.info( + f"Using agent {agent_id} for profile {profile.id} to execute Faktory buy." + ) + + tool = FaktoryExecuteBuyTool(wallet_id=agent_id) # Use fetched agent_id + result = await tool._arun( + btc_amount=payload.btc_amount, + dao_token_dex_contract_address=payload.dao_token_dex_contract_address, + slippage=payload.slippage, + ) + + logger.debug( + f"Faktory execute buy result for agent {agent_id} (profile {profile.id}): {result}" + ) + return JSONResponse(content=result) + + except HTTPException as he: + # Re-raise HTTPExceptions directly + raise he + except Exception as e: + logger.error( + f"Failed to execute Faktory buy for profile {profile.id}", exc_info=e + ) + raise HTTPException( + status_code=500, + detail=f"Failed to execute Faktory buy order: {str(e)}", + ) + + +@router.post("/dao/action_proposals/propose_send_message") +async def propose_dao_action_send_message( + request: Request, + payload: ProposeSendMessageRequest, + profile: Profile = Depends(verify_profile_from_token), +) -> JSONResponse: + """Propose a DAO action to send a message. + + This endpoint allows an authenticated user's agent to create a proposal + for sending a message via the DAO's action proposal system. + + Args: + request: The FastAPI request object. + payload: The request body containing the proposal details. + profile: The authenticated user's profile. + + Returns: + JSONResponse: The result of the proposal creation. + + Raises: + HTTPException: If there's an error, or if the agent for the profile is not found. + """ + try: + logger.info( + f"DAO propose send message request received from {request.client.host if request.client else 'unknown'} for profile {profile.id}" + ) + + agents = backend.list_agents(AgentFilter(profile_id=profile.id)) + if not agents: + logger.error(f"No agent found for profile ID: {profile.id}") + raise HTTPException( + status_code=404, + detail=f"No agent found for profile ID: {profile.id}", + ) + + agent = agents[0] + agent_id = agent.id + + logger.info( + f"Using agent {agent_id} for profile {profile.id} to propose DAO send message action." + ) + + tool = ProposeActionSendMessageTool(wallet_id=agent_id) + result = await tool._arun( + action_proposals_voting_extension=payload.action_proposals_voting_extension, + action_proposal_contract_to_execute=payload.action_proposal_contract_to_execute, + dao_token_contract_address=payload.dao_token_contract_address, + message=payload.message, + memo=payload.memo, + ) + + logger.debug( + f"DAO propose send message result for agent {agent_id} (profile {profile.id}): {result}" + ) + return JSONResponse(content=result) + + except HTTPException as he: + raise he + except Exception as e: + logger.error( + f"Failed to propose DAO send message action for profile {profile.id}", + exc_info=e, + ) + raise HTTPException( + status_code=500, + detail=f"Failed to propose DAO send message action: {str(e)}", + )