Skip to content
Merged
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 autogen_composio/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.10.0
Empty file added autogen_composio/__init__.py
Empty file.
Binary file added autogen_composio/__pycache__/api.cpython-310.pyc
Binary file not shown.
85 changes: 85 additions & 0 deletions autogen_composio/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import json
import os
import time
import requests


COMPOSIO_TOKEN = 'ghp_1J2g3h4i5j6k7l8m9n0o33'
BASE_URL = "https://hermes-production-6901.up.railway.app/api"

ACCESS_TOKEN = "COMPOSIO-X3125-ZUA-1"

Choose a reason for hiding this comment

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

Great addition to the codebase!

SKILLS_FILE = os.path.join(os.path.dirname(__file__), 'skills.json')

def get_url_for_composio_action(toolName: str, actionName: str):
return f"{BASE_URL}/tools/{toolName}/actions/{actionName}"

def identify_user(identifer: str):
response = requests.post(f"{BASE_URL}/user/create/${identifer}", headers={
'X_COMPOSIO_TOKEN': COMPOSIO_TOKEN
})
if response.status_code == 200:
user_id = response.json().get('userId')
return user_id
raise Exception("Failed to identify user")

def get_user_id():
if os.path.exists('user_data.json'):
with open('user_data.json', 'r') as infile:
user_data = json.load(infile)
return user_data.get('user_id')
return None

def get_redirect_url_for_integration(integrationName: str, scopes = []):
user_id = get_user_id()
response = requests.get(f"{BASE_URL}/user/auth", headers={
'X_COMPOSIO_TOKEN': COMPOSIO_TOKEN,
'X_ENDUSER_ID': user_id
}, json={
'endUserID': user_id,
'provider': {
'name': integrationName,
'scope': scopes
}
})

if response.status_code == 200:
return response.json().get('providerAuthURL')

print(response.text)
raise Exception("Failed to get auth URL for integration")

def wait_for_tool_auth_completion(toolName: str):
user_id = get_user_id()
start_time = time.time()
while time.time() - start_time < 40:
response = requests.get(f"{BASE_URL}/user/providerid/{toolName}/credentials", headers={
'X_COMPOSIO_TOKEN': COMPOSIO_TOKEN,
'X_ENDUSER_ID': user_id
})
if response.status_code == 200 and response.json().get('is_authenticated') == True:
return True
time.sleep(5)
raise Exception("Authentication timeout or failed to get auth status for tool")

def list_tools():
# @TODO: Dummy API call response, replace with actual API call after it is ready
with open('data/hardcoded_tools.json', 'r') as infile:
user_data = json.load(infile)
return user_data

user_id = get_user_id()
if user_id is None:
raise Exception("No authenticated user found. Please authenticate first.")

url = f"{BASE_URL}/tools"
headers = {
'X_COMPOSIO_TOKEN': COMPOSIO_TOKEN,
'X_ENDUSER_ID': user_id
}
response = requests.get(url, headers=headers)
print(response.text)
if response.status_code == 200:
tools_data = response.json()
return tools_data
else:
raise Exception("Failed to fetch tools.")
209 changes: 209 additions & 0 deletions autogen_composio/cli.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
#!/usr/bin/env python

import json
import os
import time
from beaupy.spinners import Spinner, BARS, DOTS
from rich.console import Console
from beaupy import select_multiple
import termcolor
import requests
import jinja2
from uuid import getnode as get_mac

from api import get_redirect_url_for_integration, get_url_for_composio_action, identify_user, list_tools, wait_for_tool_auth_completion

console = Console()

ACCESS_TOKEN = "COMPOSIO-X3125-ZUA-1"
SKILLS_FILE = os.path.join(os.path.dirname(__file__), 'skills.json')

jinja2_env = jinja2.Environment(loader=jinja2.FileSystemLoader(''))
def get_autogen_skill_content_from_func_signature(skillRawId, skillId, skillTitle, skillDescription, skillFileName, toolName, functionSignature):
template = jinja2_env.get_template('templates/skills.txt')
descriptionComment = f"{skillTitle}\n# {skillDescription}"
input_parameters = [];
for parameter_name, parameter_info in functionSignature.get('Input', {}).get('properties', {}).items():
input_parameters.append({
"name": parameter_name,
"type": get_python_types_from_json_types(parameter_info.get('type')),
"description": parameter_info.get('description')
})

method_url = get_url_for_composio_action(toolName, actionName = skillRawId)
rendered_template = template.render(method_url=method_url,description=descriptionComment, method_name=skillRawId, method_parameters=input_parameters)
return rendered_template

def get_python_types_from_json_types(type: str):
if type == "string":
return "str"
elif type == "integer":
return "int"
elif type == "number":
return "float"
elif type == "boolean":
return "bool"
elif type == "object":
return "dict"
elif type == "array":
return "list"
else:
return "Any"

def get_autogen_tools_from_composio_tools(composio_tools):
autogen_tools = {}
for tool in composio_tools["tools"]:
name = tool.get('Name')
skills = [];
for skill in tool.get('Actions', []):
skillId = f"{name}-{skill.get('Id')}"
skillTitle = f"{name}: {skill.get('DisplayName')}"
skillDescription = skill.get('Description')
skillFileName = f"{skillId}.py"
skillRawId = skill.get('Id')
function_signature = skill.get('Signature')
skills.append({
"id": skillId,
"title": skillTitle,
"description": skillDescription,
"file_name": skillFileName,
"content": get_autogen_skill_content_from_func_signature(skillRawId,skillId, skillTitle, skillDescription, skillFileName, name, function_signature)
})
autogen_tools[name] = skills
return autogen_tools

def load_skills():
composio_tools = list_tools()
return get_autogen_tools_from_composio_tools(composio_tools)

def check_and_install(package_name):
try:
__import__(package_name)
except ImportError:
console.print(f"[yellow]{package_name} not installed. Installing...[/yellow]")
os.system(f"pip install {package_name}")

def install_skills(dbManager):
from autogenstudio.utils.dbutils import upsert_skill
from autogenstudio.datamodel import Skill
tools = load_skills()
for toolKey, toolSkills in tools.items():
for skillInfo in toolSkills:
upsert_skill(skill=Skill(
id=skillInfo["id"],
content=skillInfo["content"],
file_name=skillInfo["file_name"],
title=skillInfo["title"],
description=skillInfo["description"]
), dbmanager=dbManager)

def setup_autogen_studio():
check_and_install('autogenstudio')
from autogenstudio.utils.dbutils import DBManager
import autogenstudio

database_path = os.path.join(os.path.dirname(autogenstudio.__file__), "web/database.sqlite")
return DBManager(path=database_path)

def print_intro():
text = termcolor.colored('Composio', 'white', attrs=['bold'])
aiPlatformText = termcolor.colored('100+', 'green', attrs=['bold'])
pinkEmojiText = termcolor.colored('hello@composio.dev', 'magenta', attrs=['bold'])
boldNoteText = termcolor.colored('Note*', 'white', attrs=['bold'])
print(f"""
┌───────────────────────────────────────────────────────────────────────────┐
│ │
│ {text} <-> AutoGen │
│ │
│ Plug {aiPlatformText} platforms in your agent │
│ │
│ {boldNoteText}: This package is in closed beta, please contact {pinkEmojiText} │
│ to get early access. │
│ │
└───────────────────────────────────────────────────────────────────────────┘
""")

def save_user_id(user_id):
user_data = {'user_id': user_id}
with open('user_data.json', 'w') as outfile:
json.dump(user_data, outfile)

def run_user_session_logic():
spinner = Spinner(BARS, f"Authenticating you...")
spinner.start()

if os.path.exists('user_data.json'):
time.sleep(1)
console.print(f" [green]✔[/green]\n");
else:
try:
user_mac_address = get_mac()
unique_identifier = f"{user_mac_address}-autogen"
session_token = identify_user(unique_identifier)

if session_token:
console.print(f" [green]✔[/green]\n");
save_user_id(session_token)

except Exception as e:
raise e

spinner.stop()
pass

def setup_integrations(integrations_selected):
console.print(f"\n[green]> Setting up {len(integrations_selected)} integrations...[/green]\n")
tools = list_tools()
tools_map = {}
for tool in tools["tools"]:
tools_map[tool["Name"]] = tool

for integration in integrations_selected:
skillAgent = tools_map[integration]
is_authenticated = skillAgent.get("Authentication", {}).get("isAuthenticated")
# TODO: Check with actual API, if is_autenticated is really a string
if is_authenticated == "False":
auth_url = get_redirect_url_for_integration(integration.lower(), scopes=skillAgent.get("Authentication", {}).get("Scopes", []))
spinner = Spinner(DOTS, f"[yellow]⚠[/yellow] {integration} requires authentication. Please visit the following URL to authenticate: {auth_url}")
spinner.start()
time.sleep(2)
wait_for_tool_auth_completion(integration)
console.print(f"[green]✔[/green] {integration} authenticated successfully!")
spinner.stop()
print("\n")
def run():
try:
print_intro()

run_user_session_logic()

spinner = Spinner(BARS, "Checking Requirements")
spinner.start()
check_and_install('autogen')
db_manager = setup_autogen_studio()
spinner.stop()
console.print("[green]> All requirements are met... Good to go!\n[/green]")

# access_token = input("Paste your beta access token here: ")
# if access_token != ACCESS_TOKEN:
# console.print("[red]\n> Invalid access token ❌\n[/red]")
# return

skills = load_skills()
if skills:
capitalized_integration_names = [name.capitalize() for name in skills]
console.print("> Which integrations do you want to install?")
integrations_selected = select_multiple(capitalized_integration_names, tick_character='■', ticked_indices=[0], maximal_count=100, cursor_style='bold', tick_style='green', pagination=True)
setup_integrations(integrations_selected)
spinner = Spinner(BARS, "Setting up your skills...")
spinner.start()
install_skills(db_manager)
time.sleep(2)
spinner.stop()
console.print("[green]> All skills installed successfully! 🚀\n[/green]")
from autogenstudio.cli import ui
ui()
except Exception as e:
console.print(f"[red]Error occurred: {e}[/red]")

run()
Loading