From 219a2ff9afb1e89f29c02cce6328e243445d1bcb Mon Sep 17 00:00:00 2001 From: YellowSnnowmann <167776381+YellowSnnowmann@users.noreply.github.com> Date: Tue, 14 Apr 2026 15:58:12 +0530 Subject: [PATCH] Fix: GitHub composeio (#543) * feat(composio): add GitHub repository management and trigger creation - Enhanced the ComposioConnectModal component to support loading and selecting GitHub repositories for connected users. - Introduced new API functions for listing GitHub repositories and creating triggers, improving integration capabilities with GitHub. - Added state management for repository loading, selection, and trigger action feedback, enhancing user experience during GitHub integration. - Updated types to include GitHub repository structures and responses, ensuring type safety and clarity in the codebase. * feat(composio): add GitHub repository listing and trigger creation APIs - Implemented new API endpoints for listing GitHub repositories and creating triggers, enhancing integration capabilities with GitHub. - Updated types to include responses for GitHub repositories and trigger creation, ensuring type safety and clarity. - Added corresponding handler functions and schemas to support the new operations, improving overall functionality and user experience in the Composio integration. * refactor(composio): remove GitHub repository and trigger management from ComposioConnectModal - Eliminated GitHub repository loading and trigger creation logic from the ComposioConnectModal component, streamlining its functionality. - Removed associated state management and API calls for GitHub repositories and triggers, enhancing code clarity and maintainability. - Updated types to reflect the removal of GitHub-related structures, ensuring consistency across the codebase. * format: ran format --- src/openhuman/composio/client.rs | 44 +++++++++- src/openhuman/composio/ops.rs | 49 +++++++++++- src/openhuman/composio/schemas.rs | 106 +++++++++++++++++++++++++ src/openhuman/composio/types.rs | 35 ++++++++ src/openhuman/socket/event_handlers.rs | 7 ++ 5 files changed, 236 insertions(+), 5 deletions(-) diff --git a/src/openhuman/composio/client.rs b/src/openhuman/composio/client.rs index e26e0003f..093f32457 100644 --- a/src/openhuman/composio/client.rs +++ b/src/openhuman/composio/client.rs @@ -17,8 +17,9 @@ use serde_json::json; use crate::openhuman::integrations::IntegrationClient; use super::types::{ - ComposioAuthorizeResponse, ComposioConnectionsResponse, ComposioDeleteResponse, - ComposioExecuteResponse, ComposioToolkitsResponse, ComposioToolsResponse, + ComposioAuthorizeResponse, ComposioConnectionsResponse, ComposioCreateTriggerResponse, + ComposioDeleteResponse, ComposioExecuteResponse, ComposioGithubReposResponse, + ComposioToolkitsResponse, ComposioToolsResponse, }; /// High-level client for all backend-proxied Composio operations. @@ -137,6 +138,45 @@ impl ComposioClient { .await } + /// `GET /agent-integrations/composio/github/repos` — list repositories + /// available via the user's authorized GitHub connected account. + pub async fn list_github_repos( + &self, + connection_id: Option<&str>, + ) -> Result { + let path = match connection_id.map(str::trim).filter(|id| !id.is_empty()) { + Some(id) => format!("/agent-integrations/composio/github/repos?connectionId={id}"), + None => "/agent-integrations/composio/github/repos".to_string(), + }; + tracing::debug!(path = %path, "[composio] list_github_repos"); + self.inner.get::(&path).await + } + + /// `POST /agent-integrations/composio/triggers` — create a trigger + /// instance for the authenticated user. + pub async fn create_trigger( + &self, + slug: &str, + connection_id: Option<&str>, + trigger_config: Option, + ) -> Result { + let slug = slug.trim(); + if slug.is_empty() { + anyhow::bail!("composio.create_trigger: slug must not be empty"); + } + let mut body = json!({ "slug": slug }); + if let Some(connection_id) = connection_id.map(str::trim).filter(|id| !id.is_empty()) { + body["connectionId"] = json!(connection_id); + } + if let Some(config) = trigger_config { + body["triggerConfig"] = config; + } + tracing::debug!(slug = %slug, "[composio] create_trigger"); + self.inner + .post::("/agent-integrations/composio/triggers", &body) + .await + } + // ── Raw DELETE ────────────────────────────────────────────────── /// Perform an HTTP DELETE and parse the standard backend envelope. diff --git a/src/openhuman/composio/ops.rs b/src/openhuman/composio/ops.rs index a82b688ae..13ee61bc6 100644 --- a/src/openhuman/composio/ops.rs +++ b/src/openhuman/composio/ops.rs @@ -26,9 +26,9 @@ use super::providers::{ get_provider, ProviderContext, ProviderUserProfile, SyncOutcome, SyncReason, }; use super::types::{ - ComposioAuthorizeResponse, ComposioConnectionsResponse, ComposioDeleteResponse, - ComposioExecuteResponse, ComposioToolkitsResponse, ComposioToolsResponse, - ComposioTriggerHistoryResult, + ComposioAuthorizeResponse, ComposioConnectionsResponse, ComposioCreateTriggerResponse, + ComposioDeleteResponse, ComposioExecuteResponse, ComposioGithubReposResponse, + ComposioToolkitsResponse, ComposioToolsResponse, ComposioTriggerHistoryResult, }; /// Resolve a [`ComposioClient`] from the root config, or return an @@ -197,6 +197,49 @@ pub async fn composio_execute( } } +// ── GitHub repos + trigger provisioning ───────────────────────────── + +pub async fn composio_list_github_repos( + config: &Config, + connection_id: Option, +) -> OpResult> { + tracing::debug!(?connection_id, "[composio] rpc list_github_repos"); + let client = resolve_client(config)?; + let resp = client + .list_github_repos(connection_id.as_deref()) + .await + .map_err(|e| format!("[composio] list_github_repos failed: {e:#}"))?; + let count = resp.repositories.len(); + let connection_id = resp.connection_id.clone(); + Ok(RpcOutcome::new( + resp, + vec![format!( + "composio: {count} github repo(s) listed for connection {connection_id}" + )], + )) +} + +pub async fn composio_create_trigger( + config: &Config, + slug: &str, + connection_id: Option, + trigger_config: Option, +) -> OpResult> { + tracing::debug!(slug = %slug, ?connection_id, "[composio] rpc create_trigger"); + let client = resolve_client(config)?; + let resp = client + .create_trigger(slug, connection_id.as_deref(), trigger_config) + .await + .map_err(|e| format!("[composio] create_trigger failed: {e:#}"))?; + let trigger_id = resp.trigger_id.clone(); + Ok(RpcOutcome::new( + resp, + vec![format!( + "composio: trigger {trigger_id} created for slug {slug}" + )], + )) +} + // ── Trigger history ──────────────────────────────────────────────── pub async fn composio_list_trigger_history( diff --git a/src/openhuman/composio/schemas.rs b/src/openhuman/composio/schemas.rs index 2ee97533f..cef5df603 100644 --- a/src/openhuman/composio/schemas.rs +++ b/src/openhuman/composio/schemas.rs @@ -8,6 +8,8 @@ //! - `composio.delete_connection` → `openhuman.composio_delete_connection` //! - `composio.list_tools` → `openhuman.composio_list_tools` //! - `composio.execute` → `openhuman.composio_execute` +//! - `composio.list_github_repos` → `openhuman.composio_list_github_repos` +//! - `composio.create_trigger` → `openhuman.composio_create_trigger` //! - `composio.get_user_profile` → `openhuman.composio_get_user_profile` //! - `composio.sync` → `openhuman.composio_sync` @@ -24,6 +26,18 @@ struct TriggerHistoryParams { limit: Option, } +#[derive(Debug, serde::Deserialize)] +struct ListGithubReposParams { + connection_id: Option, +} + +#[derive(Debug, serde::Deserialize)] +struct CreateTriggerParams { + slug: String, + connection_id: Option, + trigger_config: Option, +} + pub fn all_controller_schemas() -> Vec { vec![ schemas("list_toolkits"), @@ -32,6 +46,8 @@ pub fn all_controller_schemas() -> Vec { schemas("delete_connection"), schemas("list_tools"), schemas("execute"), + schemas("list_github_repos"), + schemas("create_trigger"), schemas("get_user_profile"), schemas("sync"), schemas("list_trigger_history"), @@ -64,6 +80,14 @@ pub fn all_registered_controllers() -> Vec { schema: schemas("execute"), handler: handle_execute, }, + RegisteredController { + schema: schemas("list_github_repos"), + handler: handle_list_github_repos, + }, + RegisteredController { + schema: schemas("create_trigger"), + handler: handle_create_trigger, + }, RegisteredController { schema: schemas("get_user_profile"), handler: handle_get_user_profile, @@ -191,6 +215,58 @@ pub fn schemas(function: &str) -> ControllerSchema { required: true, }], }, + "list_github_repos" => ControllerSchema { + namespace: "composio", + function: "list_github_repos", + description: + "List repositories available through the caller's authorized GitHub Composio connection.", + inputs: vec![FieldSchema { + name: "connection_id", + ty: TypeSchema::Option(Box::new(TypeSchema::String)), + comment: + "Optional GitHub connection id. If omitted, backend picks the first active GitHub connection.", + required: false, + }], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Payload: { connectionId, repositories:[{ owner, repo, fullName, ... }] }.", + required: true, + }], + }, + "create_trigger" => ControllerSchema { + namespace: "composio", + function: "create_trigger", + description: + "Create a Composio trigger instance for a connected account. For GitHub triggers, pass owner/repo in trigger_config.", + inputs: vec![ + FieldSchema { + name: "slug", + ty: TypeSchema::String, + comment: "Trigger slug, e.g. GITHUB_PULL_REQUEST_EVENT.", + required: true, + }, + FieldSchema { + name: "connection_id", + ty: TypeSchema::Option(Box::new(TypeSchema::String)), + comment: "Optional connected account id. Backend resolves from slug toolkit when omitted.", + required: false, + }, + FieldSchema { + name: "trigger_config", + ty: TypeSchema::Option(Box::new(TypeSchema::Json)), + comment: + "Trigger config object. For GitHub, include owner/repo or repoFullName.", + required: false, + }, + ], + outputs: vec![FieldSchema { + name: "result", + ty: TypeSchema::Json, + comment: "Payload: { triggerId, status? }.", + required: true, + }], + }, "get_user_profile" => ControllerSchema { namespace: "composio", function: "get_user_profile", @@ -327,6 +403,36 @@ fn handle_execute(params: Map) -> ControllerFuture { }) } +fn handle_list_github_repos(params: Map) -> ControllerFuture { + Box::pin(async move { + let config = config_rpc::load_config_with_timeout().await?; + let payload: ListGithubReposParams = serde_json::from_value(Value::Object(params)) + .map_err(|e| format!("invalid params: {e}"))?; + to_json(super::ops::composio_list_github_repos(&config, payload.connection_id).await?) + }) +} + +fn handle_create_trigger(params: Map) -> ControllerFuture { + Box::pin(async move { + let config = config_rpc::load_config_with_timeout().await?; + let payload: CreateTriggerParams = serde_json::from_value(Value::Object(params)) + .map_err(|e| format!("invalid params: {e}"))?; + let slug = payload.slug.trim(); + if slug.is_empty() { + return Err("invalid params: 'slug' must not be empty".to_string()); + } + to_json( + super::ops::composio_create_trigger( + &config, + slug, + payload.connection_id, + payload.trigger_config, + ) + .await?, + ) + }) +} + fn handle_list_trigger_history(params: Map) -> ControllerFuture { Box::pin(async move { let config = config_rpc::load_config_with_timeout().await?; diff --git a/src/openhuman/composio/types.rs b/src/openhuman/composio/types.rs index e23024580..7b96ff165 100644 --- a/src/openhuman/composio/types.rs +++ b/src/openhuman/composio/types.rs @@ -114,6 +114,41 @@ pub struct ComposioExecuteResponse { pub cost_usd: f64, } +// ── GitHub repos + triggers ───────────────────────────────────────── + +/// One repository returned by `GET /agent-integrations/composio/github/repos`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComposioGithubRepo { + pub owner: String, + pub repo: String, + #[serde(rename = "fullName")] + pub full_name: String, + #[serde(default)] + pub private: Option, + #[serde(rename = "defaultBranch", default)] + pub default_branch: Option, + #[serde(rename = "htmlUrl", default)] + pub html_url: Option, +} + +/// Response body of `GET /agent-integrations/composio/github/repos`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComposioGithubReposResponse { + #[serde(rename = "connectionId")] + pub connection_id: String, + #[serde(default, rename = "repositories")] + pub repositories: Vec, +} + +/// Response body of `POST /agent-integrations/composio/triggers`. +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct ComposioCreateTriggerResponse { + #[serde(rename = "triggerId")] + pub trigger_id: String, + #[serde(default)] + pub status: Option, +} + // ── Triggers ──────────────────────────────────────────────────────── /// Payload of the `composio:trigger` Socket.IO event emitted by the backend diff --git a/src/openhuman/socket/event_handlers.rs b/src/openhuman/socket/event_handlers.rs index db4e8f4ad..e8a387edb 100644 --- a/src/openhuman/socket/event_handlers.rs +++ b/src/openhuman/socket/event_handlers.rs @@ -127,6 +127,13 @@ pub(super) fn handle_sio_event( "[socket] composio:trigger missing toolkit/trigger; dropping event" ); } else { + log::info!( + "[socket] Publishing composio:trigger to event bus: toolkit={}, trigger={}, metadata_id={}, metadata_uuid={}", + event.toolkit, + event.trigger, + event.metadata.id, + event.metadata.uuid + ); publish_global(DomainEvent::ComposioTriggerReceived { toolkit: event.toolkit, trigger: event.trigger,