Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions plugins/communication_protocols/http/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ dependencies = [
"pydantic>=2.0",
"authlib>=1.0",
"aiohttp>=3.8",
"multidict>=4.5,<8",
"pyyaml>=6.0",
"utcp>=1.1.4"
]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import re
import traceback
from urllib.parse import quote
from multidict import MultiDict, MultiMapping
Comment thread
cubic-dev-ai[bot] marked this conversation as resolved.
Resolved

from utcp.interfaces.communication_protocol import CommunicationProtocol
from utcp.data.call_template import CallTemplate
Expand Down Expand Up @@ -277,22 +278,7 @@ async def deregister_manual(self, caller, manual_call_template: CallTemplate) ->
"""
pass

async def call_tool(self, caller, tool_name: str, tool_args: Dict[str, Any], tool_call_template: CallTemplate) -> Any:
"""REQUIRED
Execute a tool call through this transport.

Args:
caller: The UTCP client that is calling this method.
tool_name: Name of the tool to call (may include provider prefix).
tool_args: Dictionary of arguments to pass to the tool.
tool_call_template: Call template of the tool to call.

Returns:
The tool's response, with type depending on the tool's output schema.
"""
if not isinstance(tool_call_template, HttpCallTemplate):
raise ValueError("HttpCommunicationProtocol can only be used with HttpCallTemplate")

def _prepare_request_arguments(self, tool_args: Dict[str, Any], tool_call_template: HttpCallTemplate) -> tuple:
request_headers = tool_call_template.headers.copy() if tool_call_template.headers else {}
body_content = None
remaining_args = tool_args.copy()
Expand Down Expand Up @@ -320,6 +306,50 @@ async def call_tool(self, caller, tool_name: str, tool_args: Dict[str, Any], too

# The rest of the arguments are query parameters
query_params = remaining_args
return request_headers, body_content, url, query_params

def _prepare_request_body(self, body_content: Any, request_headers: Dict[str, str], tool_call_template: HttpCallTemplate) -> tuple:
# Set content-type header if body is provided and header not already set
if body_content is not None and "Content-Type" not in request_headers:
request_headers["Content-Type"] = tool_call_template.content_type

# Prepare body content based on content type
data = None
json_data = None
if body_content is not None:
if "application/json" in request_headers.get("Content-Type", ""):
json_data = body_content
else:
data = body_content
return data, json_data

def _normalize_query_params(self, query_params):
items = [
(name, json.dumps(value)
if isinstance(value, (dict, bool, type(None))) else value)
for name, value in query_params.items()
]
if isinstance(query_params, MultiMapping):
return MultiDict(items)
return dict(items)

async def call_tool(self, caller, tool_name: str, tool_args: Dict[str, Any], tool_call_template: CallTemplate) -> Any:
"""REQUIRED
Execute a tool call through this transport.

Args:
caller: The UTCP client that is calling this method.
tool_name: Name of the tool to call (may include provider prefix).
tool_args: Dictionary of arguments to pass to the tool.
tool_call_template: Call template of the tool to call.

Returns:
The tool's response, with type depending on the tool's output schema.
"""
if not isinstance(tool_call_template, HttpCallTemplate):
raise ValueError("HttpCommunicationProtocol can only be used with HttpCallTemplate")

request_headers, body_content, url, query_params = self._prepare_request_arguments(tool_args, tool_call_template)

# Handle authentication
auth, cookies, auth_header_names = self._apply_auth(tool_call_template, request_headers, query_params)
Expand All @@ -329,20 +359,11 @@ async def call_tool(self, caller, tool_name: str, tool_args: Dict[str, Any], too
token = await self._handle_oauth2(tool_call_template.auth)
request_headers["Authorization"] = f"Bearer {token}"

query_params = self._normalize_query_params(query_params)

async with aiohttp.ClientSession() as session:
try:
# Set content-type header if body is provided and header not already set
if body_content is not None and "Content-Type" not in request_headers:
request_headers["Content-Type"] = tool_call_template.content_type

# Prepare body content based on content type
data = None
json_data = None
if body_content is not None:
if "application/json" in request_headers.get("Content-Type", ""):
json_data = body_content
else:
data = body_content
data, json_data = self._prepare_request_body(body_content, request_headers, tool_call_template)

# Re-validate every redirect hop -- aiohttp's default
# ``allow_redirects=True`` would otherwise let an
Expand Down
Loading