from datetime import datetime from typing import Annotated, TypeVar from core_domain import ServiceHealth from fastapi import APIRouter, Depends, HTTPException, Query, Request from sqlalchemy import text from sqlalchemy.orm import Session from app.application.services import ToolApplicationService, build_tool_application_service from app.bootstrap.settings import ToolServiceSettings from app.db.session import get_db from app.schemas.tool import ( ApiResponse, DeleteData, McpConnectData, McpConnectRequestDto, PageRequest, PageResult, ToolBindingCreateRequest, ToolBindingCreateRequestDto, ToolBindingDeleteRequestDto, ToolBindingDetailRequestDto, ToolBindingDetailResponse, ToolBindingDto, ToolBindingListRequestDto, ToolBindingResponse, ToolBindingUpdateRequestDto, ToolCreateRequest, ToolCreateRequestDto, ToolCredentialCreateRequest, ToolCredentialCreateRequestDto, ToolCredentialDeleteRequestDto, ToolCredentialDetailRequestDto, ToolCredentialDto, ToolCredentialResponse, ToolCredentialRevealDto, ToolCredentialRevealRequestDto, ToolCredentialRevealResponse, ToolCredentialUpdateRequestDto, ToolDeleteRequestDto, ToolDetailRequestDto, ToolDto, ToolResponse, ToolUpdateRequestDto, ToolConnectionCreateRequest, ToolConnectionCreateRequestDto, ToolConnectionDeleteRequestDto, ToolConnectionDetailRequestDto, ToolConnectionDto, ToolConnectionListRequestDto, ToolConnectionResponse, ToolConnectionUpdateRequestDto, ) router = APIRouter() DbSession = Annotated[Session, Depends(get_db)] T = TypeVar("T") def get_tool_application_service( request: Request, db: DbSession) -> ToolApplicationService: settings: ToolServiceSettings = request.app.state.settings return build_tool_application_service(db=db, settings=settings) ToolServiceDep = Annotated[ToolApplicationService, Depends(get_tool_application_service)] def ok(data: T) -> ApiResponse[T]: return ApiResponse[T]( data=data, requestId="", serverTime=datetime.utcnow()) @router.get("/health", response_model=ServiceHealth) def health_check(db: DbSession) -> ServiceHealth: db.execute(text("SELECT 1")) return ServiceHealth(service="tool-service", status="ok", database="ok") @router.post("", response_model=ToolResponse) def create_tool_definition( payload: ToolCreateRequest, service: ToolServiceDep) -> ToolResponse: entity = service.create_tool_definition(payload) return ToolResponse.from_entity(entity) @router.get("", response_model=list[ToolResponse]) def list_tool_definitions( service: ToolServiceDep) -> list[ToolResponse]: return [ToolResponse.from_entity(item) for item in service.list_tool_definitions()] @router.post("/bindings", response_model=ToolBindingResponse) def create_tool_binding( payload: ToolBindingCreateRequest, service: ToolServiceDep) -> ToolBindingResponse: try: entity = service.create_tool_binding(payload) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc return ToolBindingResponse.from_entity(entity) @router.get("/bindings", response_model=list[ToolBindingResponse]) def list_tool_bindings( service: ToolServiceDep, app_id: str | None = Query(default=None)) -> list[ToolBindingResponse]: return [ ToolBindingResponse.from_entity(item) for item in service.list_tool_bindings(app_id=app_id) ] @router.get("/bindings/{binding_id}", response_model=ToolBindingDetailResponse) def get_tool_binding_detail( binding_id: str, service: ToolServiceDep) -> ToolBindingDetailResponse: result = service.get_tool_binding_detail(binding_id=binding_id) if result is None: raise HTTPException(status_code=404, detail=f"tool_binding not found: {binding_id}") binding, tool_connection, tool_definition = result return ToolBindingDetailResponse( binding=ToolBindingResponse.from_entity(binding), connection=ToolConnectionResponse.from_entity(tool_connection), tool_definition=ToolResponse.from_entity(tool_definition)) @router.post("/credentials", response_model=ToolCredentialResponse) def create_tool_credential( payload: ToolCredentialCreateRequest, service: ToolServiceDep) -> ToolCredentialResponse: entity = service.create_tool_credential(payload) return ToolCredentialResponse.from_entity(entity) @router.get("/credentials", response_model=list[ToolCredentialResponse]) def list_tool_credentials( service: ToolServiceDep) -> list[ToolCredentialResponse]: return [ ToolCredentialResponse.from_entity(item) for item in service.list_tool_credentials() ] @router.post("/credentials/{credential_id}/reveal", response_model=ToolCredentialRevealResponse) def reveal_tool_credential( credential_id: str, service: ToolServiceDep) -> ToolCredentialRevealResponse: result = service.reveal_tool_credential( credential_id=credential_id) if result is None: raise HTTPException(status_code=404, detail=f"tool credential not found: {credential_id}") credential, secret_json = result return ToolCredentialRevealResponse( credential=ToolCredentialResponse.from_entity(credential), secret_json=secret_json) @router.post("/list", response_model=ApiResponse[PageResult[ToolDto]]) def list_tools_contract( payload: PageRequest, service: ToolServiceDep) -> ApiResponse[PageResult[ToolDto]]: keyword = (payload.keyword or "").lower().strip() items = [ item for item in service.list_tool_definitions() if not keyword or keyword in item.name.lower() or keyword in item.tool_type.lower() or keyword in (item.description or "").lower() ] page_items = items[payload.offset:payload.offset + payload.pageSize] return ok( PageResult[ToolDto].from_items( items=[ToolDto.from_entity(item) for item in page_items], total=len(items), page=payload.page, page_size=payload.pageSize)) @router.post("/create", response_model=ApiResponse[ToolDto]) def create_tool_contract( payload: ToolCreateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolDto]: entity = service.create_tool_definition_from_contract(payload) return ok(ToolDto.from_entity(entity)) @router.post("/detail", response_model=ApiResponse[ToolDto]) def get_tool_contract( payload: ToolDetailRequestDto, service: ToolServiceDep) -> ApiResponse[ToolDto]: entity = service.get_tool_definition_from_contract(payload) if entity is None: raise HTTPException(status_code=404, detail=f"tool not found: {payload.toolId}") return ok(ToolDto.from_entity(entity)) @router.post("/update", response_model=ApiResponse[ToolDto]) def update_tool_contract( payload: ToolUpdateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolDto]: entity = service.update_tool_definition_from_contract(payload) if entity is None: raise HTTPException(status_code=404, detail=f"tool not found: {payload.toolId}") return ok(ToolDto.from_entity(entity)) @router.post("/delete", response_model=ApiResponse[DeleteData]) def delete_tool_contract( payload: ToolDeleteRequestDto, service: ToolServiceDep) -> ApiResponse[DeleteData]: deleted = service.delete_tool_definition_from_contract(payload) return ok(DeleteData(deleted=deleted, toolId=payload.toolId)) @router.post("/connections/list", response_model=ApiResponse[PageResult[ToolConnectionDto]]) def list_tool_connections_contract( payload: ToolConnectionListRequestDto, service: ToolServiceDep) -> ApiResponse[PageResult[ToolConnectionDto]]: items = service.list_tool_connections(tool_id=payload.toolId) page_items = items[payload.offset:payload.offset + payload.pageSize] return ok( PageResult[ToolConnectionDto].from_items( items=[ToolConnectionDto.from_entity(item) for item in page_items], total=len(items), page=payload.page, page_size=payload.pageSize)) @router.post("/connections/create", response_model=ApiResponse[ToolConnectionDto]) def create_tool_connection_contract( payload: ToolConnectionCreateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolConnectionDto]: entity = service.create_tool_connection_from_contract(payload) return ok(ToolConnectionDto.from_entity(entity)) @router.post("/connections/detail", response_model=ApiResponse[ToolConnectionDto]) def get_tool_connection_contract( payload: ToolConnectionDetailRequestDto, service: ToolServiceDep) -> ApiResponse[ToolConnectionDto]: entity = service.get_tool_connection_from_contract(payload) if entity is None: raise HTTPException( status_code=404, detail=f"tool connection not found: {payload.connectionId}") return ok(ToolConnectionDto.from_entity(entity)) @router.post("/connections/update", response_model=ApiResponse[ToolConnectionDto]) def update_tool_connection_contract( payload: ToolConnectionUpdateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolConnectionDto]: entity = service.update_tool_connection_from_contract(payload) if entity is None: raise HTTPException( status_code=404, detail=f"tool connection not found: {payload.connectionId}") return ok(ToolConnectionDto.from_entity(entity)) @router.post("/connections/delete", response_model=ApiResponse[DeleteData]) def delete_tool_connection_contract( payload: ToolConnectionDeleteRequestDto, service: ToolServiceDep) -> ApiResponse[DeleteData]: connection_id = payload.connectionId deleted = service.delete_tool_connection(connection_id=connection_id) return ok(DeleteData(deleted=deleted, connectionId=connection_id)) @router.post("/mcp/connect", response_model=ApiResponse[McpConnectData]) def connect_mcp_server_contract( payload: McpConnectRequestDto, service: ToolServiceDep) -> ApiResponse[McpConnectData]: return ok(service.connect_mcp_server(payload)) @router.post("/bindings/list", response_model=ApiResponse[PageResult[ToolBindingDto]]) def list_tool_bindings_contract( payload: ToolBindingListRequestDto, service: ToolServiceDep) -> ApiResponse[PageResult[ToolBindingDto]]: items = service.list_tool_bindings(app_id=payload.appId) page_items = items[payload.offset:payload.offset + payload.pageSize] return ok( PageResult[ToolBindingDto].from_items( items=[ToolBindingDto.from_entity(item) for item in page_items], total=len(items), page=payload.page, page_size=payload.pageSize)) @router.post("/bindings/create", response_model=ApiResponse[ToolBindingDto]) def create_tool_binding_contract( payload: ToolBindingCreateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolBindingDto]: try: entity = service.create_tool_binding_from_contract(payload) except ValueError as exc: raise HTTPException(status_code=422, detail=str(exc)) from exc return ok(ToolBindingDto.from_entity(entity)) @router.post("/bindings/detail", response_model=ApiResponse[ToolBindingDto]) def get_tool_binding_contract( payload: ToolBindingDetailRequestDto, service: ToolServiceDep) -> ApiResponse[ToolBindingDto]: entity = service.get_tool_binding_from_contract(payload) if entity is None: raise HTTPException( status_code=404, detail=f"tool binding not found: {payload.bindingId}") return ok(ToolBindingDto.from_entity(entity)) @router.post("/bindings/update", response_model=ApiResponse[ToolBindingDto]) def update_tool_binding_contract( payload: ToolBindingUpdateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolBindingDto]: entity = service.update_tool_binding_from_contract(payload) if entity is None: raise HTTPException( status_code=404, detail=f"tool binding not found: {payload.bindingId}") return ok(ToolBindingDto.from_entity(entity)) @router.post("/bindings/delete", response_model=ApiResponse[DeleteData]) def delete_tool_binding_contract( payload: ToolBindingDeleteRequestDto, service: ToolServiceDep) -> ApiResponse[DeleteData]: deleted = service.delete_tool_binding(payload) return ok(DeleteData(deleted=deleted, bindingId=payload.bindingId)) @router.post("/credentials/list", response_model=ApiResponse[PageResult[ToolCredentialDto]]) def list_tool_credentials_contract( payload: PageRequest, service: ToolServiceDep) -> ApiResponse[PageResult[ToolCredentialDto]]: keyword = (payload.keyword or "").lower().strip() items = [ item for item in service.list_tool_credentials() if not keyword or keyword in item.name.lower() or keyword in item.credential_type.lower() ] page_items = items[payload.offset:payload.offset + payload.pageSize] return ok( PageResult[ToolCredentialDto].from_items( items=[ToolCredentialDto.from_entity(item) for item in page_items], total=len(items), page=payload.page, page_size=payload.pageSize)) @router.post("/credentials/create", response_model=ApiResponse[ToolCredentialDto]) def create_tool_credential_contract( payload: ToolCredentialCreateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolCredentialDto]: entity = service.create_tool_credential_from_contract(payload) return ok(ToolCredentialDto.from_entity(entity)) @router.post("/credentials/detail", response_model=ApiResponse[ToolCredentialDto]) def get_tool_credential_contract( payload: ToolCredentialDetailRequestDto, service: ToolServiceDep) -> ApiResponse[ToolCredentialDto]: entity = service.get_tool_credential_from_contract(payload) if entity is None: raise HTTPException( status_code=404, detail=f"tool credential not found: {payload.credentialId}") return ok(ToolCredentialDto.from_entity(entity)) @router.post("/credentials/update", response_model=ApiResponse[ToolCredentialDto]) def update_tool_credential_contract( payload: ToolCredentialUpdateRequestDto, service: ToolServiceDep) -> ApiResponse[ToolCredentialDto]: entity = service.update_tool_credential_from_contract(payload) if entity is None: raise HTTPException( status_code=404, detail=f"tool credential not found: {payload.credentialId}") return ok(ToolCredentialDto.from_entity(entity)) @router.post("/credentials/delete", response_model=ApiResponse[DeleteData]) def delete_tool_credential_contract( payload: ToolCredentialDeleteRequestDto, service: ToolServiceDep) -> ApiResponse[DeleteData]: deleted = service.delete_tool_credential(payload) return ok(DeleteData(deleted=deleted, credentialId=payload.credentialId)) @router.post("/credentials/reveal", response_model=ApiResponse[ToolCredentialRevealDto]) def reveal_tool_credential_contract( payload: ToolCredentialRevealRequestDto, service: ToolServiceDep) -> ApiResponse[ToolCredentialRevealDto]: result = service.reveal_tool_credential_from_contract( credential_id=payload.credentialId) if result is None: raise HTTPException( status_code=404, detail=f"tool credential not found: {payload.credentialId}") return ok(result)