chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 12:38:34 +08:00
commit 0549b088a4
2405 changed files with 810255 additions and 0 deletions
+13
View File
@@ -0,0 +1,13 @@
from __future__ import annotations
from .agentic import AgenticProvider, AgenticProviderExecuteFn
from .base import TTool, TToolCollection
from .none_agentic import NonAgenticProvider
__all__ = [
"TTool",
"TToolCollection",
"AgenticProvider",
"NonAgenticProvider",
"AgenticProviderExecuteFn",
]
+152
View File
@@ -0,0 +1,152 @@
"""
OpenAI provider implementation.
"""
from __future__ import annotations
import json
import time
import typing as t
from openai import Client
from openai.types.beta.thread import Thread
from openai.types.beta.threads.run import Run
from openai.types.chat.chat_completion import ChatCompletion
from openai.types.chat.chat_completion_message_tool_call import (
ChatCompletionMessageToolCall,
)
from openai.types.chat.chat_completion_tool_param import ChatCompletionToolParam
from openai.types.shared_params.function_definition import FunctionDefinition
from openai.types.shared_params.function_parameters import FunctionParameters
from composio.core.provider import NonAgenticProvider
from composio.types import Modifiers, Tool, ToolExecutionResponse
from composio.utils.shared import normalize_tool_arguments
OpenAITool: t.TypeAlias = ChatCompletionToolParam
OpenAIToolCollection: t.TypeAlias = t.List[OpenAITool]
class OpenAIProvider(
NonAgenticProvider[OpenAITool, OpenAIToolCollection], name="openai"
):
"""OpenAIProvider class definition"""
def wrap_tool(self, tool: Tool) -> OpenAITool:
return ChatCompletionToolParam(
function=FunctionDefinition(
name=tool.slug,
description=tool.description,
parameters=t.cast(FunctionParameters, tool.input_parameters),
strict=None,
),
type="function",
)
def wrap_tools(self, tools: t.Sequence[Tool]) -> OpenAIToolCollection:
return [self.wrap_tool(tool) for tool in tools]
def execute_tool_call(
self,
user_id: str,
tool_call: ChatCompletionMessageToolCall,
modifiers: t.Optional[Modifiers] = None,
) -> ToolExecutionResponse:
"""Execute a tool call.
:param tool_call: Tool call metadata.
:param user_id: User ID to use for executing the function call.
:return: Object containing output data from the tool call.
"""
# OpenAI always serializes tool arguments as a JSON string; normalize
# tolerates empty / object-shaped payloads too (issue #2406).
return self.execute_tool(
slug=tool_call.function.name,
arguments=normalize_tool_arguments(tool_call.function.arguments),
modifiers=modifiers,
user_id=user_id,
)
def handle_tool_calls(
self,
user_id: str,
response: ChatCompletion,
modifiers: t.Optional[Modifiers] = None,
) -> t.List[ToolExecutionResponse]:
"""
Handle tool calls from OpenAI chat completion object.
:param response: Chat completion object from
openai.OpenAI.chat.completions.create function call
:param user_id: User ID to use for executing the function call.
:return: A list of output objects from the function calls.
"""
outputs = []
# Only the first choice is actionable: its tool results feed back into a
# single assistant turn. With n > 1, iterating every choice would run each
# tool call once per choice and orphan the tool_call_ids belonging to the
# alternative completions.
choice = response.choices[0] if response.choices else None
# A single assistant message can carry several tool calls (parallel tool
# calls, on by default); each one needs its own tool result.
if choice is not None and choice.message.tool_calls is not None:
for tool_call in choice.message.tool_calls:
outputs.append(
self.execute_tool_call(
user_id=user_id,
tool_call=t.cast(ChatCompletionMessageToolCall, tool_call),
modifiers=modifiers,
)
)
return outputs
def handle_assistant_tool_calls(
self,
user_id: str,
run: Run,
) -> t.List:
"""Wait and handle assistant function calls"""
tool_outputs: list[dict] = []
if run.required_action is None:
return tool_outputs
for tool_call in run.required_action.submit_tool_outputs.tool_calls:
tool_outputs.append(
{
"tool_call_id": tool_call.id,
"output": json.dumps(
self.execute_tool_call(
tool_call=t.cast(ChatCompletionMessageToolCall, tool_call),
user_id=user_id,
)
),
}
)
return tool_outputs
def wait_and_handle_assistant_tool_calls(
self,
user_id: str,
client: Client,
run: Run,
thread: Thread,
) -> Run:
"""Wait and handle assistant function calls"""
while run.status in ("queued", "in_progress", "requires_action"):
if run.status != "requires_action":
run = client.beta.threads.runs.retrieve(
thread_id=thread.id,
run_id=run.id,
)
time.sleep(0.5)
continue
run = client.beta.threads.runs.submit_tool_outputs(
thread_id=thread.id,
run_id=run.id,
tool_outputs=self.handle_assistant_tool_calls(
run=run,
user_id=user_id,
),
)
return run
@@ -0,0 +1,90 @@
"""
OpenAI Responses API provider implementation.
"""
from __future__ import annotations
import typing as t
from openai.types.responses.response import Response
from openai.types.responses.response_output_item import ResponseFunctionToolCall
from composio.core.provider import NonAgenticProvider
from composio.types import Modifiers, Tool, ToolExecutionResponse
from composio.utils.shared import normalize_tool_arguments
# Responses API uses a flattened tool structure
ResponsesTool = t.Dict[str, t.Any]
ResponsesToolCollection = t.List[ResponsesTool]
class OpenAIResponsesProvider(
NonAgenticProvider[ResponsesTool, ResponsesToolCollection], name="openai_responses"
):
"""OpenAI Responses API Provider class definition."""
def wrap_tool(self, tool: Tool) -> ResponsesTool:
"""Wrap a tool for the Responses API format."""
return {
"type": "function",
"name": tool.slug,
"description": tool.description,
"parameters": tool.input_parameters,
}
def wrap_tools(self, tools: t.Sequence[Tool]) -> ResponsesToolCollection:
"""Wrap multiple tools for the Responses API format."""
return [self.wrap_tool(tool) for tool in tools]
def execute_tool_call(
self,
user_id: str,
tool_call: t.Union[ResponseFunctionToolCall],
modifiers: t.Optional[Modifiers] = None,
) -> ToolExecutionResponse:
"""Execute a tool call from the Responses API.
:param tool_call: Tool call metadata from Responses API.
:param user_id: User ID to use for executing the function call.
:param modifiers: Optional modifiers for tool execution.
:return: Object containing output data from the tool call.
"""
# OpenAI always serializes tool arguments as a JSON string; normalize
# tolerates empty / object-shaped payloads too (issue #2406).
slug = tool_call.name
arguments = normalize_tool_arguments(tool_call.arguments)
return self.execute_tool(
slug=slug,
arguments=arguments,
modifiers=modifiers,
user_id=user_id,
)
def handle_tool_calls(
self,
user_id: str,
response: Response,
modifiers: t.Optional[Modifiers] = None,
) -> t.List[ToolExecutionResponse]:
"""
Handle tool calls from OpenAI Responses API.
:param response: Response object from openai.OpenAI.beta.responses.create
:param user_id: User ID to use for executing the function call.
:param modifiers: Optional modifiers for tool execution
:return: List[ToolExecutionResponse] with tool execution results
"""
outputs = []
if response.output:
for item in response.output:
if isinstance(item, ResponseFunctionToolCall):
result = self.execute_tool_call(
user_id=user_id,
tool_call=item,
modifiers=modifiers,
)
outputs.append(result)
return outputs
+48
View File
@@ -0,0 +1,48 @@
from __future__ import annotations
import typing as t
from composio.client.types import Tool
from composio.core.provider.base import BaseProvider, TTool, TToolCollection
class AgenticProviderExecuteFn(t.Protocol):
def __call__(
self,
slug: str,
arguments: t.Dict,
) -> t.Dict:
"""
Execute a wrapped tool by slug, passing an arbitrary input dict.
Returns a dict with the following keys:
- data: The data returned by the tool.
- error: The error returned by the tool.
- successful: Whether the tool was successful.
"""
...
class AgenticProvider(BaseProvider[TTool, TToolCollection]):
"""
Base class for all agentic providers. This class is not meant to be used
directly but rather to be extended by concrete provider implementations.
"""
def __init_subclass__(cls, name: str) -> None:
cls.name = name
def wrap_tool(
self,
tool: Tool,
execute_tool: AgenticProviderExecuteFn,
) -> TTool:
"""Wrap a tool in the provider-specific format"""
raise NotImplementedError
def wrap_tools(
self,
tools: t.Sequence[Tool],
execute_tool: AgenticProviderExecuteFn,
) -> TToolCollection:
"""Wrap a list of tools in the provider-specific format"""
raise NotImplementedError
+73
View File
@@ -0,0 +1,73 @@
"""
BaseProvider module
Defines the barebones provider metaclass that needs to be subclassed for every provider.
"""
from __future__ import annotations
import typing as t
import typing_extensions as te
if t.TYPE_CHECKING:
from composio.core.models.tools import Modifiers, ToolExecutionResponse
TTool = t.TypeVar("TTool")
TToolCollection = t.TypeVar("TToolCollection")
class ExecuteToolFn(t.Protocol):
def __call__(
self,
slug: str,
arguments: t.Dict,
*,
modifiers: t.Optional[Modifiers] = None,
user_id: t.Optional[str] = None,
) -> ToolExecutionResponse:
"""
Execute a wrapped tool by slug, passing an arbitrary input dict.
This function is used by the providers to execute tools for the helper methods.
Returns a dict with the following keys:
- data: The data returned by the tool.
- error: The error returned by the tool.
- successful: Whether the tool was successful.
"""
...
class SchemaConfig(te.TypedDict):
skip_defaults: te.NotRequired[bool]
class BaseProviderConfig(te.TypedDict):
schema_config: te.NotRequired[SchemaConfig]
class BaseProvider(t.Generic[TTool, TToolCollection]):
"""
BaseProvider class
All providers should inherit from this class and implement `wrap_tools` so that
they can be used with the core Composio class.
"""
name: str
"""Name of the provider"""
__schema_skip_defaults__ = False
execute_tool: ExecuteToolFn
"""
The function to execute a tool for the provider's helper methods.
This is automatically injected by the core SDK.
"""
def __init__(self, **kwargs: t.Unpack[BaseProviderConfig]) -> None:
self.skip_default = kwargs.get("schema_config", {}).get(
"skip_defaults", self.__schema_skip_defaults__
)
def set_execute_tool_fn(self, execute_tool_fn: ExecuteToolFn) -> None:
self.execute_tool = execute_tool_fn
@@ -0,0 +1,31 @@
from __future__ import annotations
import typing as t
from composio.client.types import Tool
from composio.core.provider.base import BaseProvider, TTool, TToolCollection
class NonAgenticProvider(BaseProvider[TTool, TToolCollection]):
"""
Base class for all non-agentic providers, such as `openai` This class is not
meant to be used directly, but rather to be extended by concrete implementations
This version doesn't have the execute_tool_fn for `wrap_tool` and `wrap_tools`
"""
def __init_subclass__(cls, name: str) -> None:
cls.name = name
def wrap_tool(
self,
tool: Tool,
) -> TTool:
"""Wrap a tool in the provider-specific format"""
raise NotImplementedError
def wrap_tools(
self,
tools: t.Sequence[Tool],
) -> TToolCollection:
"""Wrap a list of tools in the provider-specific format"""
raise NotImplementedError