Skip to content

chore: development to master #1490

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

Draft
wants to merge 5 commits into
base: master
Choose a base branch
from
Draft
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
92 changes: 48 additions & 44 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,52 +144,51 @@ composio add github # Run this in terminal
from openai import OpenAI
from composio_openai import ComposioToolSet, App, Action

# Initialize OpenAI client
openai_client = OpenAI(
api_key="{{OPENAIKEY}}"
api_key="{{OPENAIKEY}}"
)

# Initialise the Composio Tool Set

# Initialize the Composio Tool Set
composio_tool_set = ComposioToolSet()

# Get GitHub tools that are pre-configured
actions = composio_tool_set.get_actions(
actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER]
actions=[Action.GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER]
)

my_task = "Star a repo composiodev/composio on GitHub"

# Setup openai assistant
# Setup OpenAI assistant
assistant_instruction = "You are a super intelligent personal assistant"

assistant = openai_client.beta.assistants.create(
name="Personal Assistant",
instructions=assistant_instruction,
model="gpt-4-turbo",
tools=actions,
name="Personal Assistant",
instructions=assistant_instruction,
model="gpt-4-turbo",
tools=actions,
)

# create a thread
# Create a thread
thread = openai_client.beta.threads.create()

# Add user message to thread
message = openai_client.beta.threads.messages.create(
thread_id=thread.id,
role="user",
content=my_task
thread_id=thread.id,
role="user",
content=my_task
)

# Execute Agent with integrations
run = openai_client.beta.threads.runs.create(
thread_id=thread.id,
assistant_id=assistant.id
thread_id=thread.id,
assistant_id=assistant.id
)


# Execute Function calls
response_after_tool_calls = composio_tool_set.wait_and_handle_assistant_tool_calls(
client=openai_client,
run=run,
thread=thread,
client=openai_client,
run=run,
thread=thread,
)

print(response_after_tool_calls)
Expand Down Expand Up @@ -223,38 +222,43 @@ import OpenAI from "openai";
const toolset = new OpenAIToolSet({ apiKey: process.env.COMPOSIO_API_KEY });
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });

const tools = await toolset.getTools({ actions: ["GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER"] });
const tools = await toolset.getTools({
actions: ["GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER"]
});

async function createGithubAssistant(openai, tools) {
return await openai.beta.assistants.create({
name: "Github Assistant",
instructions: "You're a GitHub Assistant, you can do operations on GitHub",
tools: tools,
model: "gpt-4o"
});
return await openai.beta.assistants.create({
name: "Github Assistant",
instructions: "You're a GitHub Assistant, you can do operations on GitHub",
tools: tools,
model: "gpt-4o"
});
}

async function executeAssistantTask(openai, toolset, assistant, task) {
const thread = await openai.beta.threads.create();
const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
instructions: task,
tools: tools,
model: "gpt-4o",
stream: false
});
const call = await toolset.waitAndHandleAssistantToolCalls(openai, run, thread);
console.log(call);
const thread = await openai.beta.threads.create();

const run = await openai.beta.threads.runs.create(thread.id, {
assistant_id: assistant.id,
instructions: task,
tools: tools,
model: "gpt-4o",
stream: false
});

const call = await toolset.waitAndHandleAssistantToolCalls(openai, run, thread);
console.log(call);
}

(async () => {
const githubAssistant = await createGithubAssistant(openai, tools);
await executeAssistantTask(
openai,
toolset,
githubAssistant,
"Star the repository 'composiohq/composio'"
);
const githubAssistant = await createGithubAssistant(openai, tools);

await executeAssistantTask(
openai,
toolset,
githubAssistant,
"Star the repository 'composiohq/composio'"
);
})();
```

Expand Down
29 changes: 26 additions & 3 deletions docs/patterns/tools/use-tools/use-actions-with-custom-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ Composio allows you to [execute actions directly](action-guide-without-agents) u
<Tabs>
<Tab title="Python">
Use the `add_auth` method to add the custom authentication to the toolset for the app you want to use. `in_` is where you want to add the auth, `name` is the name of the header you want to add and `value` is the value of the header.
You can also use `DescopeAuth` for simpler Descope integration.

```python {7-13}
from composio import ComposioToolSet, App

Expand All @@ -27,11 +29,32 @@ toolset.add_auth(
],
)

toolset.execute_action(
action="GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER",
params={"owner": "composiohq", "repo": "composio"},
# Method 2: Using DescopeAuth (for Descope)
from composio.utils.descope import DescopeAuth

# Initialize DescopeAuth with your credentials
descope = DescopeAuth(
project_id="your_project_id", # Or uses DESCOPE_PROJECT_ID env var
management_key="your_management_key" # Or uses DESCOPE_MANAGEMENT_KEY env var
)

toolset = ComposioToolSet()

# Add authentication using DescopeAuth
toolset.add_auth(
app=App.GITHUB,
parameters=descope.get_auth(
app=App.GITHUB,
user_id="your_user_id",
scopes=["user", "public_repo"] # Permissions for the token
)
)
```

The `DescopeAuth` utility simplifies authentication with Descope by:
- Generating the necessary authentication tokens for external services
- Managing the authorization headers and metadata
- Setting appropriate scopes for the required permissions
</Tab>
<Tab title="JavaScript">
Here you need to pass the authentication parameters inside the `authConfig`. `in_` is where you want to add the auth, `name` is the name of the header you want to add and `value` is the value of the header.
Expand Down
16 changes: 16 additions & 0 deletions docs/patterns/tools/use-tools/use-tools-with-your-auth.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ pip install composio-core
```

Use the `add_auth` method to add the existing authentication to the toolset for the app you want to use. `in_` is where you want to add the auth, `name` is the name of the header you want to add and `value` is the value of the header.
You can also use `DescopeAuth` for simpler Descope integration.
```python {7-13}
from composio import ComposioToolSet, App

Expand All @@ -32,11 +33,26 @@ toolset.add_auth(
],
)

# Method 2: Using DescopeAuth (for Descope)
from composio.utils.descope import DescopeAuth

descope = DescopeAuth(project_id="your_project_id", management_key="your_management_key")
toolset = ComposioToolSet()

toolset.add_auth(
App.GITHUB,
descope.get_auth(user_id="your_user_id", scopes=["user", "public_repo"])
)

toolset.execute_action(
action="GITHUB_STAR_A_REPOSITORY_FOR_THE_AUTHENTICATED_USER",
params={"owner": "composiohq", "repo": "composio"},
)
```

# Additional Context for DescopeAuth Usage

To use `DescopeAuth`, ensure you have the required `project_id` and `management_key` from your Descope account. These credentials are necessary to authenticate and generate the required headers for API calls. The `scopes` parameter defines the permissions for the generated token.
</Tab>
<Tab title="JavaScript">
Install packages:
Expand Down
8 changes: 8 additions & 0 deletions python/composio/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -250,3 +250,11 @@ class InvalidConnectedAccount(ValidationError, ConnectedAccountError):

class ErrorProcessingToolExecutionRequest(PluginError):
pass


class DescopeAuthError(ComposioSDKError):
pass


class DescopeConfigError(ComposioSDKError):
pass
5 changes: 5 additions & 0 deletions python/composio/tools/local/clipboardtool/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""
Clipboard manager.
"""

from .tool import Clipboardtool
15 changes: 15 additions & 0 deletions python/composio/tools/local/clipboardtool/actions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Clipboard actions."""

from .files import CopyFilePaths, PasteFilePaths
from .image import CopyImage, PasteImage
from .text import CopyText, PasteText


__all__ = [
"CopyText",
"PasteText",
"CopyImage",
"PasteImage",
"CopyFilePaths",
"PasteFilePaths",
]
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Base classes for clipboard actions."""

from typing import Any, Dict, TypedDict

from pydantic import BaseModel, Field


class ClipboardState(TypedDict, total=False):
"""Type definition for clipboard state."""

text_data: str
image_data: str

Choose a reason for hiding this comment

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

Consider using bytes type for image_data instead of str since it's storing binary image data that's base64 encoded. This would make the type hint more accurate and explicit about the expected data type.

file_paths: list[str]
Comment on lines +8 to +13
Copy link
Contributor

Choose a reason for hiding this comment

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

The ClipboardState TypedDict uses total=False but doesn't handle the case where keys might be missing when accessed. This could lead to KeyError exceptions when trying to access non-existent keys.

📝 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
class ClipboardState(TypedDict, total=False):
"""Type definition for clipboard state."""
text_data: str
image_data: str
file_paths: list[str]
class ClipboardState(TypedDict, total=False):
"""Type definition for clipboard state."""
text_data: Optional[str] = None
image_data: Optional[str] = None
file_paths: Optional[list[str]] = None



class BaseClipboardRequest(BaseModel):
"""Base request for clipboard actions."""

pass


class BaseClipboardResponse(BaseModel):
"""Base response for clipboard actions."""

message: str = Field(
default="",
description="Message describing the result of the action",
)
error: str = Field(
default="",
description="Error message if the action failed",
)


def get_clipboard_state(metadata: Dict[str, Any]) -> ClipboardState:
"""Get clipboard state from metadata.

Args:
metadata: The metadata dictionary containing clipboard state

Returns:
The clipboard state dictionary, initialized if it doesn't exist
"""
if "clipboard_state" not in metadata:
metadata["clipboard_state"] = {}
return metadata["clipboard_state"] # type: ignore
Comment on lines +35 to +46
Copy link
Contributor

Choose a reason for hiding this comment

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

The get_clipboard_state function uses # type: ignore to suppress a type error instead of properly typing the return value. The function should explicitly cast the return value to ClipboardState.

📝 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
def get_clipboard_state(metadata: Dict[str, Any]) -> ClipboardState:
"""Get clipboard state from metadata.
Args:
metadata: The metadata dictionary containing clipboard state
Returns:
The clipboard state dictionary, initialized if it doesn't exist
"""
if "clipboard_state" not in metadata:
metadata["clipboard_state"] = {}
return metadata["clipboard_state"] # type: ignore
def get_clipboard_state(metadata: Dict[str, Any]) -> ClipboardState:
"""Get clipboard state from metadata.
Args:
metadata: The metadata dictionary containing clipboard state
Returns:
The clipboard state dictionary, initialized if it doesn't exist
"""
if "clipboard_state" not in metadata:
metadata["clipboard_state"] = {}
return ClipboardState(metadata["clipboard_state"])

Loading
Loading