Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: update file upload and download mechanism on toolset #1124

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
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
65 changes: 5 additions & 60 deletions python/composio/client/collections.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,8 @@
Composio server object collections
"""

import base64
import difflib
import json
import os
import time
import traceback
import typing as t
Expand Down Expand Up @@ -986,28 +984,15 @@ def get( # type: ignore
return super().get(queries=queries)


def _check_file_uploadable(param_field: dict) -> bool:
return (
isinstance(param_field, dict)
and (param_field.get("title") in ["File", "FileType"])
and all(
field_name in param_field.get("properties", {})
for field_name in ["name", "content"]
)
)


def _check_file_downloadable(param_field: dict) -> bool:
return set(param_field.keys()) == {"name", "content"}


class ActionParametersModel(BaseModel):
"""Action parameter data models."""

properties: t.Dict[str, t.Any]
title: str
type: str

accept: t.List[str] = Field(default_factory=lambda: ["*/*"])
file_uploadable: bool = False
required: t.Optional[t.List[str]] = None


Expand All @@ -1018,6 +1003,7 @@ class ActionResponseModel(BaseModel):
title: str
type: str

file_downloadable: bool = False
required: t.Optional[t.List[str]] = None


Expand Down Expand Up @@ -1246,54 +1232,13 @@ def execute(
if action.is_local:
return self.client.local.execute_action(action=action, request_data=params)

actions = self.get(actions=[action])
if len(actions) == 0:
raise ComposioClientError(f"Action {action} not found")

(action_model,) = actions
action_req_schema = action_model.parameters.properties
modified_params: t.Dict[str, t.Union[str, t.Dict[str, str]]] = {}
for param, value in params.items():
request_param_schema = action_req_schema.get(param)
if request_param_schema is None:
# User has sent a parameter that is not used by this action,
# so we can ignore it.
continue

file_readable = request_param_schema.get("file_readable", False)
file_uploadable = _check_file_uploadable(request_param_schema)

if file_readable and isinstance(value, str) and os.path.isfile(value):
with open(value, "rb") as file:
file_content = file.read()
try:
modified_params[param] = file_content.decode("utf-8")
except UnicodeDecodeError:
# If decoding fails, treat as binary and encode in base64
modified_params[param] = base64.b64encode(file_content).decode(
"utf-8"
)
elif file_uploadable and isinstance(value, str):
if not os.path.isfile(value):
raise ValueError(f"Attachment File with path `{value}` not found.")

with open(value, "rb") as file:
file_content = file.read()

modified_params[param] = {
"name": os.path.basename(value),
"content": base64.b64encode(file_content).decode("utf-8"),
}
else:
modified_params[param] = value

if action.no_auth:
return self._raise_if_required(
self.client.long_timeout_http.post(
url=str(self.endpoint / action.slug / "execute"),
json={
"appName": action.app,
"input": modified_params,
"input": params,
"text": text,
"sessionInfo": {
"sessionId": session_id,
Expand All @@ -1315,7 +1260,7 @@ def execute(
"connectedAccountId": connected_account,
"entityId": entity_id,
"appName": action.app,
"input": modified_params,
"input": params,
"text": text,
"authConfig": self._serialize_auth(auth=auth),
},
Comment on lines 1260 to 1266

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug Fix: Verify the necessity of 'modified_params' before reverting to 'params' to prevent potential logical errors.

🔧 Suggested Code Diff:
- "input": params,
+ "input": modified_params,
📝 Committable Code Suggestion

‼️ Ensure you review the code suggestion before committing it to the branch. Make sure it replaces the highlighted code, contains no missing lines, and has no issues with indentation.

Suggested change
"connectedAccountId": connected_account,
"entityId": entity_id,
"appName": action.app,
"input": modified_params,
"input": params,
"text": text,
"authConfig": self._serialize_auth(auth=auth),
},
{
"connectedAccountId": connected_account,
"entityId": entity_id,
"appName": action.app,
"input": modified_params, # Ensure modified_params is correctly set and validated
"text": text,
"authConfig": self._serialize_auth(auth=auth),
}

Expand Down
22 changes: 20 additions & 2 deletions python/composio/tools/base/abs.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,25 @@ def __init__(self, model: t.Type[ModelType]) -> None:

@classmethod
def wrap(cls, model: t.Type[ModelType]) -> t.Type[BaseModel]:
class wrapper(model): # type: ignore
if "data" not in model.__annotations__:

class wrapper(BaseModel): # type: ignore
data: model = Field( # type: ignore
...,
description="Data from the action execution",
)
successful: bool = Field(
...,
description="Whether or not the action execution was successful or not",
)
error: t.Optional[str] = Field(
None,
description="Error if any occurred during the execution of the action",
)

return t.cast(t.Type[BaseModel], wrapper)

class wrapper(model): # type: ignore # pylint: disable=function-redefined
successful: bool = Field(
...,
description="Whether or not the action execution was successful or not",
Expand Down Expand Up @@ -199,7 +217,7 @@ def schema(self) -> t.Dict:
] += f" Note: choose value only from following options - {prop['enum']}"

schema["properties"] = properties
schema["title"] = self.model.__name__
schema["title"] = f"{self.model.__name__}Wrapper"
return remove_json_ref(schema)


Expand Down
Loading
Loading