diff --git a/apps/rowboat_agents/poetry.lock b/apps/rowboat_agents/poetry.lock index 43b83c76..4332d3d6 100644 --- a/apps/rowboat_agents/poetry.lock +++ b/apps/rowboat_agents/poetry.lock @@ -2822,6 +2822,25 @@ azure-key-vault = ["azure-identity (>=1.16.0)", "azure-keyvault-secrets (>=4.8.0 toml = ["tomli (>=2.0.1)"] yaml = ["pyyaml (>=6.0.1)"] +[[package]] +name = "pyjwt" +version = "2.10.1" +description = "JSON Web Token implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +markers = "python_version <= \"3.11\" or python_version == \"3.12\" or python_version >= \"3.13\"" +files = [ + {file = "PyJWT-2.10.1-py3-none-any.whl", hash = "sha256:dcdd193e30abefd5debf142f9adfcdd2b58004e644f25406ffaebd50bd98dacb"}, + {file = "pyjwt-2.10.1.tar.gz", hash = "sha256:3cc5772eb20009233caf06e9d8a0577824723b44e6648ee0a2aedb6cf9381953"}, +] + +[package.extras] +crypto = ["cryptography (>=3.4.0)"] +dev = ["coverage[toml] (==5.0.4)", "cryptography (>=3.4.0)", "pre-commit", "pytest (>=6.0.0,<7.0.0)", "sphinx", "sphinx-rtd-theme", "zope.interface"] +docs = ["sphinx", "sphinx-rtd-theme", "zope.interface"] +tests = ["coverage[toml] (==5.0.4)", "pytest (>=6.0.0,<7.0.0)"] + [[package]] name = "pymongo" version = "4.10.1" @@ -4131,4 +4150,4 @@ cffi = ["cffi (>=1.11)"] [metadata] lock-version = "2.1" python-versions = ">=3.10,<4.0" -content-hash = "fa1d4cfd411dca631759ca36e0c7a02c1fe4c11b1ee121557a92cd616867e9a7" +content-hash = "e384375098923696153ae702a6ac3eae30c2aeaf56844a2de66072194420d042" diff --git a/apps/rowboat_agents/pyproject.toml b/apps/rowboat_agents/pyproject.toml index 6bdc8bbe..2cb85ee6 100644 --- a/apps/rowboat_agents/pyproject.toml +++ b/apps/rowboat_agents/pyproject.toml @@ -73,6 +73,7 @@ poetry-core = "^2.0.1" pycparser = "^2.22" pydantic = "^2.10.5" pydantic_core = "^2.27.2" +PyJWT = "^2.10.1" pymongo = "^4.10.1" pyproject_hooks = "^1.2.0" python-dateutil = "^2.9.0.post0" diff --git a/apps/rowboat_agents/requirements.txt b/apps/rowboat_agents/requirements.txt index 3d2be2cc..aaa4caa4 100644 --- a/apps/rowboat_agents/requirements.txt +++ b/apps/rowboat_agents/requirements.txt @@ -59,6 +59,7 @@ poetry-core==2.0.1 pycparser==2.22 pydantic==2.10.5 pydantic_core==2.27.2 +PyJWT==2.10.1 pymongo==4.10.1 pyproject_hooks==1.2.0 python-dateutil==2.9.0.post0 diff --git a/apps/rowboat_agents/src/graph/swarm_wrapper.py b/apps/rowboat_agents/src/graph/swarm_wrapper.py index ea15682e..6f428a49 100644 --- a/apps/rowboat_agents/src/graph/swarm_wrapper.py +++ b/apps/rowboat_agents/src/graph/swarm_wrapper.py @@ -1,6 +1,9 @@ import logging import json import aiohttp +import jwt +import hashlib + # Import helper functions needed for get_agents from .helpers.access import ( get_tool_config_by_name, @@ -22,6 +25,11 @@ from mcp.client.sse import sse_client from pydantic import BaseModel from typing import List, Optional, Dict from .tool_calling import call_rag_tool +from pymongo import MongoClient +import os +MONGO_URI = os.environ.get("MONGODB_URI", "mongodb://localhost:27017/rowboat").strip() +mongo_client = MongoClient(MONGO_URI) +db = mongo_client["rowboat"] class NewResponse(BaseModel): messages: List[Dict] @@ -56,7 +64,7 @@ async def mock_tool(tool_name: str, args: str, tool_config: str) -> str: response_content = generate_openai_output(messages, output_type='text', model="gpt-4o") return response_content -async def call_webhook(tool_name: str, args: str, webhook_url: str) -> str: +async def call_webhook(tool_name: str, args: str, webhook_url: str, signing_secret: str) -> str: """ Calls the webhook with the given tool name and arguments. @@ -78,9 +86,19 @@ async def call_webhook(tool_name: str, args: str, webhook_url: str) -> str: request_body = { "content": json.dumps(content_dict) } + + # Prepare headers + headers = {} + if signing_secret: + content_str = request_body["content"] + body_hash = hashlib.sha256(content_str.encode('utf-8')).hexdigest() + payload = {"bodyHash": body_hash} + signature_jwt = jwt.encode(payload, signing_secret, algorithm="HS256") + headers["X-Signature-Jwt"] = signature_jwt + try: async with aiohttp.ClientSession() as session: - async with session.post(webhook_url, json=request_body) as response: + async with session.post(webhook_url, json=request_body, headers=headers) as response: if response.status == 200: response_json = await response.json() return response_json.get("result", "") @@ -132,8 +150,11 @@ async def catch_all(ctx: RunContextWrapper[Any], args: str, tool_name: str, tool mcp_server_url = next((server.get("url", "") for server in mcp_servers if server.get("name") == mcp_server_name), "") response_content = await call_mcp(tool_name, args, mcp_server_url) else: + collection = db["projects"] + doc = collection.find_one({"_id": complete_request.get("projectId", "")}) + signing_secret = doc.get("secret", "") webhook_url = complete_request.get("toolWebhookUrl", "") - response_content = await call_webhook(tool_name, args, webhook_url) + response_content = await call_webhook(tool_name, args, webhook_url, signing_secret) return response_content @@ -380,7 +401,7 @@ async def run_streamed( logger.info("Beginning Swarm streaming run") print("Beginning Swarm streaming run") - + try: # Use the Runner.run_streamed method stream_result = Runner.run_streamed(agent, formatted_messages)