python/scripts/create-provider.sh
#!/bin/bash

# Check if provider name is provided
if [ -z "$1" ]; then
    echo "Please provide a provider name"
    echo "Usage: ./scripts/create-provider.sh <provider-name> [--agentic] [--output-dir <directory>]"
    exit 1
fi

PROVIDER_NAME=$1
shift

# Default output directory
OUTPUT_DIR="python/providers"
IS_AGENTIC=false

# Parse optional arguments
while [[ $# -gt 0 ]]; do
    case $1 in
        --agentic)
            IS_AGENTIC=true
            shift
            ;;
        --output-dir)
            OUTPUT_DIR="$2"
            shift 2
            ;;
        *)
            echo "Unknown option: $1"
            echo "Usage: ./scripts/create-provider.sh <provider-name> [--agentic] [--output-dir <directory>]"
            exit 1
            ;;
    esac
done

PROVIDER_PATH="$OUTPUT_DIR/$PROVIDER_NAME"
# Convert to title case (e.g., myai -> Myai)
TITLE_CASE_PROVIDER_NAME="$(tr '[:lower:]' '[:upper:]' <<< ${PROVIDER_NAME:0:1})${PROVIDER_NAME:1}"
PACKAGE_NAME="composio_$PROVIDER_NAME"

# Check if provider directory already exists
if [ -d "$PROVIDER_PATH" ]; then
    echo "❌ Provider '$PROVIDER_NAME' already exists in $PROVIDER_PATH"
    exit 1
fi

# Create directory structure
mkdir -p "$PROVIDER_PATH/$PACKAGE_NAME"

# Create pyproject.toml
cat > "$PROVIDER_PATH/pyproject.toml" << EOL
[project]
name = "$PACKAGE_NAME"
version = "0.1.0"
description = "${IS_AGENTIC:+Agentic }Provider for ${TITLE_CASE_PROVIDER_NAME} in the Composio SDK"
readme = "README.md"
requires-python = ">=3.10,<4"
authors = [
    { name = "Composio", email = "tech@composio.dev" }
]
classifiers = [
    "Programming Language :: Python :: 3",
    "License :: OSI Approved :: Apache Software License",
    "Operating System :: OS Independent",
]
dependencies = [
    "composio",
]

[project.urls]
Homepage = "https://github.com/ComposioHQ/composio"
EOL

# Create setup.py for backwards compatibility
cat > "$PROVIDER_PATH/setup.py" << EOL
from setuptools import setup, find_packages

setup(
    name="$PACKAGE_NAME",
    version="0.1.0",
    packages=find_packages(),
    install_requires=[
        "composio>=0.1.0",
    ],
    python_requires=">=3.10,<4",
    author="Your Name",
    author_email="you@example.com",
    description="${IS_AGENTIC:+Agentic }Provider for ${TITLE_CASE_PROVIDER_NAME} in the Composio SDK",
    long_description=open("README.md").read(),
    long_description_content_type="text/markdown",
    url="https://github.com/composio/composio",
    classifiers=[
        "Development Status :: 3 - Alpha",
        "Intended Audience :: Developers",
        "License :: OSI Approved :: Apache Software License",
        "Programming Language :: Python :: 3",
        "Programming Language :: Python :: 3.10",
        "Programming Language :: Python :: 3.11",
        "Programming Language :: Python :: 3.12",
    ],
)
EOL

# Create __init__.py
cat > "$PROVIDER_PATH/$PACKAGE_NAME/__init__.py" << EOL
"""
${TITLE_CASE_PROVIDER_NAME} Provider for composio SDK.
"""

from .provider import ${TITLE_CASE_PROVIDER_NAME}Provider

__all__ = ["${TITLE_CASE_PROVIDER_NAME}Provider"]
EOL

# Create provider.py
if [ "$IS_AGENTIC" = true ]; then
cat > "$PROVIDER_PATH/$PACKAGE_NAME/provider.py" << EOL
"""
${TITLE_CASE_PROVIDER_NAME} Provider implementation.
"""

from __future__ import annotations

import typing as t

from composio.core.provider import AgenticProvider
from composio.core.provider.agentic import AgenticProviderExecuteFn
from composio.types import Tool


# Define your tool format
class ${TITLE_CASE_PROVIDER_NAME}Tool:
    """${TITLE_CASE_PROVIDER_NAME} tool format"""
    def __init__(self, name: str, description: str, execute: t.Callable, schema: dict):
        self.name = name
        self.description = description
        self.execute = execute
        self.schema = schema


class ${TITLE_CASE_PROVIDER_NAME}Provider(
    AgenticProvider[${TITLE_CASE_PROVIDER_NAME}Tool, t.List[${TITLE_CASE_PROVIDER_NAME}Tool]],
    name="${PROVIDER_NAME}"
):
    """
    Composio toolset for ${TITLE_CASE_PROVIDER_NAME} framework.
    """

    def wrap_tool(
        self, 
        tool: Tool, 
        execute_tool: AgenticProviderExecuteFn
    ) -> ${TITLE_CASE_PROVIDER_NAME}Tool:
        """Wrap a tool in the ${PROVIDER_NAME} format."""
        def execute_wrapper(**kwargs) -> t.Dict:
            result = execute_tool(tool.slug, kwargs)
            if not result.get("successful", False):
                raise Exception(result.get("error", "Tool execution failed"))
            return result.get("data", {})
        
        return ${TITLE_CASE_PROVIDER_NAME}Tool(
            name=tool.slug,
            description=tool.description or "",
            execute=execute_wrapper,
            schema=tool.input_parameters
        )

    def wrap_tools(
        self,
        tools: t.Sequence[Tool],
        execute_tool: AgenticProviderExecuteFn
    ) -> t.List[${TITLE_CASE_PROVIDER_NAME}Tool]:
        """
        Get composio tools wrapped as a list of ${TITLE_CASE_PROVIDER_NAME} tools.
        """
        return [self.wrap_tool(tool, execute_tool) for tool in tools]
EOL
else
cat > "$PROVIDER_PATH/$PACKAGE_NAME/provider.py" << EOL
"""
${TITLE_CASE_PROVIDER_NAME} Provider implementation.
"""

from __future__ import annotations

import typing as t

from composio.core.provider import NonAgenticProvider
from composio.types import Tool, Modifiers, ToolExecutionResponse


# Define your tool format
class ${TITLE_CASE_PROVIDER_NAME}Tool:
    """${TITLE_CASE_PROVIDER_NAME} tool format"""
    def __init__(self, name: str, description: str, parameters: dict):
        self.name = name
        self.description = description
        self.parameters = parameters


# Define your tool collection format
${TITLE_CASE_PROVIDER_NAME}ToolCollection: t.TypeAlias = t.List[${TITLE_CASE_PROVIDER_NAME}Tool]


class ${TITLE_CASE_PROVIDER_NAME}Provider(
    NonAgenticProvider[${TITLE_CASE_PROVIDER_NAME}Tool, ${TITLE_CASE_PROVIDER_NAME}ToolCollection],
    name="${PROVIDER_NAME}"
):
    """
    Composio toolset for ${TITLE_CASE_PROVIDER_NAME} platform.
    """

    def wrap_tool(self, tool: Tool) -> ${TITLE_CASE_PROVIDER_NAME}Tool:
        """Transform a single tool to platform format"""
        return ${TITLE_CASE_PROVIDER_NAME}Tool(
            name=tool.slug,
            description=tool.description or "",
            parameters={
                "type": "object",
                "properties": tool.input_parameters.get("properties", {}),
                "required": tool.input_parameters.get("required", [])
            }
        )

    def wrap_tools(self, tools: t.Sequence[Tool]) -> ${TITLE_CASE_PROVIDER_NAME}ToolCollection:
        """Transform a collection of tools"""
        return [self.wrap_tool(tool) for tool in tools]

    def execute_tool_call(
        self,
        user_id: str,
        tool_call: dict,
        modifiers: t.Optional[Modifiers] = None
    ) -> ToolExecutionResponse:
        """
        Execute a tool call.

        :param user_id: User ID to use for executing function calls.
        :param tool_call: Tool call metadata containing 'name' and 'arguments'.
        :param modifiers: Modifiers to use for executing function calls.
        :return: Object containing output data from the tool call.
        """
        return self.execute_tool(
            slug=tool_call["name"],
            arguments=tool_call["arguments"],
            modifiers=modifiers,
            user_id=user_id
        )

    def handle_tool_calls(
        self,
        user_id: str,
        response: t.Union[dict, t.Any],
        modifiers: t.Optional[Modifiers] = None
    ) -> t.List[ToolExecutionResponse]:
        """
        Handle tool calls from ${TITLE_CASE_PROVIDER_NAME} response.

        :param response: Response object from ${TITLE_CASE_PROVIDER_NAME}
        :param user_id: User ID to use for executing function calls.
        :param modifiers: Modifiers to use for executing function calls.
        :return: A list of output objects from the tool calls.
        """
        # TODO: Implement based on ${TITLE_CASE_PROVIDER_NAME}'s response format
        # Example:
        # outputs = []
        # for tool_call in response.tool_calls:
        #     outputs.append(
        #         self.execute_tool_call(
        #             user_id=user_id,
        #             tool_call=tool_call,
        #             modifiers=modifiers,
        #         )
        #     )
        # return outputs
        raise NotImplementedError("Tool call handling not implemented yet")
EOL
fi

# Create py.typed marker for PEP 561
touch "$PROVIDER_PATH/$PACKAGE_NAME/py.typed"

# Create demo script
if [ "$IS_AGENTIC" = true ]; then
cat > "$PROVIDER_PATH/${PROVIDER_NAME}_demo.py" << EOL
"""
${TITLE_CASE_PROVIDER_NAME} demo.
"""

import asyncio

# from ${PROVIDER_NAME}_framework import Agent, Runner  # Import your agent framework
from composio_${PROVIDER_NAME} import ${TITLE_CASE_PROVIDER_NAME}Provider

from composio import Composio

# Initialize Composio toolset
composio = Composio(provider=${TITLE_CASE_PROVIDER_NAME}Provider())

# Get all the tools
tools = composio.tools.get(
    user_id="default",
    tools=["GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER"],
)

# TODO: Create an agent with the tools
# agent = Agent(
#     name="GitHub Agent",
#     instructions="You are a helpful assistant that helps users with GitHub tasks.",
#     tools=tools,
# )


# Run the agent
async def main():
    # TODO: Implement your agent execution logic
    # result = await Runner.run(
    #     starting_agent=agent,
    #     input=(
    #         "Star the repository composiohq/composio on GitHub. If done "
    #         "successfully, respond with 'Action executed successfully'"
    #     ),
    # )
    # print(result.final_output)
    
    print("Implement your ${TITLE_CASE_PROVIDER_NAME} agent logic here!")
    print(f"Got {len(tools)} tools:")
    for tool in tools:
        print(f"  - {tool.name}: {tool.description}")


asyncio.run(main())
EOL
else
cat > "$PROVIDER_PATH/${PROVIDER_NAME}_demo.py" << EOL
"""
${TITLE_CASE_PROVIDER_NAME} demo.
"""

from composio_${PROVIDER_NAME} import ${TITLE_CASE_PROVIDER_NAME}Provider
# from ${PROVIDER_NAME} import ${TITLE_CASE_PROVIDER_NAME}  # Import your AI client

from composio import Composio

# Initialize tools.
# ${PROVIDER_NAME}_client = ${TITLE_CASE_PROVIDER_NAME}()  # Initialize your AI client
composio = Composio(provider=${TITLE_CASE_PROVIDER_NAME}Provider())

# Define task.
task = "Star a repo composiohq/composio on GitHub"

# Get GitHub tools that are pre-configured
tools = composio.tools.get(user_id="default", toolkits=["GITHUB"])

# TODO: Get response from the LLM
# response = ${PROVIDER_NAME}_client.chat.completions.create(
#     model="your-model",
#     tools=tools,
#     messages=[
#         {"role": "system", "content": "You are a helpful assistant."},
#         {"role": "user", "content": task},
#     ],
# )
# print(response)

# TODO: Execute the function calls.
# result = composio.provider.handle_tool_calls(response=response, user_id="default")
# print(result)

print(f"Got {len(tools)} tools for the task: {task}")
for tool in tools:
    print(f"  - {tool.name}: {tool.description}")
print("\nImplement your ${TITLE_CASE_PROVIDER_NAME} integration here!")
EOL
fi

# Create README.md
cat > "$PROVIDER_PATH/README.md" << EOL
# $PACKAGE_NAME

${IS_AGENTIC:+Agentic }Provider for ${TITLE_CASE_PROVIDER_NAME} in the Composio SDK.

## Features

${IS_AGENTIC:+- **Full Tool Execution**: Execute tools with proper parameter handling
- **Agent Support**: Create agents with wrapped tools
- **Type Safety**: Full type annotations for better IDE support}${!IS_AGENTIC:+- **Tool Transformation**: Convert Composio tools to ${TITLE_CASE_PROVIDER_NAME} format
- **Tool Execution**: Execute tools with proper parameter handling
- **Type Safety**: Full type annotations for better IDE support}

## Installation

\`\`\`bash
pip install $PACKAGE_NAME
# or
uv add $PACKAGE_NAME
\`\`\`

## Quick Start

\`\`\`python
from composio import Composio
from composio_${PROVIDER_NAME} import ${TITLE_CASE_PROVIDER_NAME}Provider

# Initialize Composio with ${TITLE_CASE_PROVIDER_NAME} provider
composio = Composio(
    api_key="your-composio-api-key",
    provider=${TITLE_CASE_PROVIDER_NAME}Provider()
)

# Get available tools
tools = composio.tools.get(
    user_id="default",
    toolkits=["github", "gmail"]
)

# Use tools with ${TITLE_CASE_PROVIDER_NAME}
# TODO: Add your usage example here
\`\`\`

## Usage Examples

### Basic Example

\`\`\`python
from composio import Composio
from composio_${PROVIDER_NAME} import ${TITLE_CASE_PROVIDER_NAME}Provider

# Initialize provider
provider = ${TITLE_CASE_PROVIDER_NAME}Provider()

# Initialize Composio
composio = Composio(
    api_key="your-api-key",
    provider=provider
)

# Get tools
tools = composio.tools.get(
    user_id="default", 
    toolkits=["github"]
)

${IS_AGENTIC:+# Use the tools with your agent framework
# TODO: Implement your agent logic here
for tool in tools:
    print(f"Tool: {tool.name} - {tool.description}")}${!IS_AGENTIC:+# TODO: Get response from your AI platform (example)
# response = ${PROVIDER_NAME}_client.chat.completions.create(
#     model="your-model",
#     tools=tools,
#     messages=[
#         {"role": "system", "content": "You are a helpful assistant."},
#         {"role": "user", "content": "Star the composiohq/composio repository"},
#     ],
# )
# 
# # Execute the function calls
# result = composio.provider.handle_tool_calls(response=response, user_id="default")
# print(result)}
\`\`\`

## API Reference

### ${TITLE_CASE_PROVIDER_NAME}Provider Class

The \`${TITLE_CASE_PROVIDER_NAME}Provider\` class extends \`${IS_AGENTIC:+AgenticProvider}${!IS_AGENTIC:+NonAgenticProvider}\` and provides ${PROVIDER_NAME}-specific functionality.

#### Methods

##### \`wrap_tool(tool: Tool${IS_AGENTIC:+, execute_tool: AgenticProviderExecuteFn}) -> ${TITLE_CASE_PROVIDER_NAME}Tool\`

Wraps a tool in the ${PROVIDER_NAME} format.

\`\`\`python
tool = provider.wrap_tool(composio_tool${IS_AGENTIC:+, execute_tool})
\`\`\`

##### \`wrap_tools(tools: Sequence[Tool]${IS_AGENTIC:+, execute_tool: AgenticProviderExecuteFn}) -> ${IS_AGENTIC:+${TITLE_CASE_PROVIDER_NAME}Toolkit}${!IS_AGENTIC:+${TITLE_CASE_PROVIDER_NAME}ToolCollection}\`

Wraps multiple tools in the ${PROVIDER_NAME} format.

\`\`\`python
tools = provider.wrap_tools(composio_tools${IS_AGENTIC:+, execute_tool})
\`\`\`

${!IS_AGENTIC:+##### \`execute_tool_call(user_id: str, tool_call: dict, modifiers: Optional[Modifiers] = None) -> ToolExecutionResponse\`

Executes a tool call.

\`\`\`python
result = provider.execute_tool_call(
    user_id="default",
    tool_call={"name": "TOOL_NAME", "arguments": {...}}
)
\`\`\`}

## Development

1. Clone the repository
2. Install dependencies: \`uv sync\`
3. Make your changes
4. Run tests: \`pytest\`
5. Format code: \`ruff format\`
6. Lint code: \`ruff check\`

## Contributing

We welcome contributions! Please see our [Contributing Guide](../../CONTRIBUTING.md) for more details.

## License

Apache License 2.0

## Support

For support, please visit our [Documentation](https://docs.composio.dev) or join our [Discord Community](https://discord.gg/composio).
EOL

# Make the script executable
chmod +x "$PROVIDER_PATH/${PROVIDER_NAME}_demo.py"

echo "✨ Created new ${IS_AGENTIC:+agentic }provider at $PROVIDER_PATH"
echo "✨ Provider structure:"
echo "   📁 $PROVIDER_PATH/"
echo "   ├── 📄 README.md"
echo "   ├── 📄 pyproject.toml"
echo "   ├── 📄 setup.py"
echo "   ├── 📄 ${PROVIDER_NAME}_demo.py"
echo "   └── 📁 $PACKAGE_NAME/"
echo "       ├── 📄 __init__.py"
echo "       ├── 📄 provider.py"
echo "       └── 📄 py.typed"
echo ""
echo "🚀 Next steps:"
echo "   1. cd $PROVIDER_PATH"
echo "   2. Update provider.py with your implementation"
echo "   3. Install in development mode: uv pip install -e ."
echo "   4. Test your provider: python ${PROVIDER_NAME}_demo.py"