mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-28 13:32:23 +00:00
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
This commit is contained in:
@@ -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<ComposioGithubReposResponse> {
|
||||
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::<ComposioGithubReposResponse>(&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<serde_json::Value>,
|
||||
) -> Result<ComposioCreateTriggerResponse> {
|
||||
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::<ComposioCreateTriggerResponse>("/agent-integrations/composio/triggers", &body)
|
||||
.await
|
||||
}
|
||||
|
||||
// ── Raw DELETE ──────────────────────────────────────────────────
|
||||
|
||||
/// Perform an HTTP DELETE and parse the standard backend envelope.
|
||||
|
||||
@@ -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<String>,
|
||||
) -> OpResult<RpcOutcome<ComposioGithubReposResponse>> {
|
||||
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<String>,
|
||||
trigger_config: Option<serde_json::Value>,
|
||||
) -> OpResult<RpcOutcome<ComposioCreateTriggerResponse>> {
|
||||
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(
|
||||
|
||||
@@ -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<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct ListGithubReposParams {
|
||||
connection_id: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct CreateTriggerParams {
|
||||
slug: String,
|
||||
connection_id: Option<String>,
|
||||
trigger_config: Option<Value>,
|
||||
}
|
||||
|
||||
pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
vec![
|
||||
schemas("list_toolkits"),
|
||||
@@ -32,6 +46,8 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
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<RegisteredController> {
|
||||
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<String, Value>) -> ControllerFuture {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_list_github_repos(params: Map<String, Value>) -> 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<String, Value>) -> 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<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move {
|
||||
let config = config_rpc::load_config_with_timeout().await?;
|
||||
|
||||
@@ -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<bool>,
|
||||
#[serde(rename = "defaultBranch", default)]
|
||||
pub default_branch: Option<String>,
|
||||
#[serde(rename = "htmlUrl", default)]
|
||||
pub html_url: Option<String>,
|
||||
}
|
||||
|
||||
/// 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<ComposioGithubRepo>,
|
||||
}
|
||||
|
||||
/// 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<String>,
|
||||
}
|
||||
|
||||
// ── Triggers ────────────────────────────────────────────────────────
|
||||
|
||||
/// Payload of the `composio:trigger` Socket.IO event emitted by the backend
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user