fix(composio): clarify Zoho Mail readiness (#2914) (#3013)

This commit is contained in:
YOMXXX
2026-05-30 08:34:17 -07:00
committed by GitHub
parent 6605dfc966
commit c70ef5c3ef
5 changed files with 168 additions and 28 deletions
+52
View File
@@ -629,6 +629,58 @@ fn retain_connected_tools_drops_unconnected_toolkits_case_insensitively() {
assert!(!names.contains(&"NOTION_CREATE_PAGE"));
}
#[test]
fn retain_connected_tools_keeps_multi_segment_connected_toolkits() {
use crate::openhuman::composio::types::{
ComposioToolFunction, ComposioToolSchema, ComposioToolsResponse,
};
use std::collections::HashSet;
let mut resp = ComposioToolsResponse {
tools: vec![
ComposioToolSchema {
kind: "function".into(),
function: ComposioToolFunction {
name: "ZOHO_MAIL_SEND_EMAIL".into(),
description: None,
parameters: None,
},
},
ComposioToolSchema {
kind: "function".into(),
function: ComposioToolFunction {
name: "ONE_DRIVE_GET_FILE".into(),
description: None,
parameters: None,
},
},
ComposioToolSchema {
kind: "function".into(),
function: ComposioToolFunction {
name: "GMAIL_SEND_EMAIL".into(),
description: None,
parameters: None,
},
},
],
};
let connected: HashSet<String> = ["zoho_mail".to_string(), "one_drive".to_string()]
.into_iter()
.collect();
let dropped = retain_connected_tools(&mut resp, &connected);
assert_eq!(dropped, 1, "should only drop the disconnected gmail tool");
let names: Vec<&str> = resp
.tools
.iter()
.map(|t| t.function.name.as_str())
.collect();
assert!(names.contains(&"ZOHO_MAIL_SEND_EMAIL"));
assert!(names.contains(&"ONE_DRIVE_GET_FILE"));
assert!(!names.contains(&"GMAIL_SEND_EMAIL"));
}
#[test]
fn normalized_scope_toolkits_prefers_requested_filter() {
use std::collections::HashSet;
@@ -85,27 +85,26 @@ pub fn find_curated<'a>(catalog: &'a [CuratedTool], slug: &str) -> Option<&'a Cu
/// Extract the toolkit slug from a Composio action slug.
///
/// All Composio action slugs follow the convention `<TOOLKIT>_<VERB>_…`
/// (e.g. `GMAIL_SEND_EMAIL` → `gmail`). Returns the lowercased prefix
/// before the first underscore, or `None` if the slug has no underscore.
///
/// **Assumption:** toolkit identifiers themselves do not contain
/// underscores. Composio honours this for every action we curate today
/// (`gmail`, `notion`, `googlecalendar`, …). The one historical
/// exception — `MICROSOFT_TEAMS_*` — extracts to `"microsoft"`, and
/// [`super::catalog_for_toolkit`] handles the alias by mapping both
/// `"microsoft"` and `"microsoft_teams"` to the same catalog.
///
/// If a future toolkit ships with a multi-word slug containing an
/// underscore in the *toolkit* portion (e.g. a hypothetical
/// `FOO_BAR_LIST_ITEMS` whose toolkit is `foo_bar`), this naive split
/// must be revised — either by consulting a known-toolkits map or by
/// taking the longest-matching prefix from the registered catalogs.
/// Most Composio action slugs follow `<TOOLKIT>_<VERB>_…`
/// (e.g. `GMAIL_SEND_EMAIL` → `gmail`). A few toolkit identifiers contain
/// underscores themselves; those need known-prefix handling so connected
/// toolkit checks do not drop actions such as `ZOHO_MAIL_*`.
pub fn toolkit_from_slug(slug: &str) -> Option<String> {
let trimmed = slug.trim();
if trimmed.is_empty() {
return None;
}
const MULTI_SEGMENT_TOOLKIT_PREFIXES: &[(&str, &str)] = &[
("MICROSOFT_TEAMS_", "microsoft_teams"),
("ONE_DRIVE_", "one_drive"),
("ZOHO_MAIL_", "zoho_mail"),
];
let upper = trimmed.to_ascii_uppercase();
for (prefix, toolkit) in MULTI_SEGMENT_TOOLKIT_PREFIXES {
if upper.starts_with(prefix) {
return Some((*toolkit).to_string());
}
}
let prefix = trimmed.split('_').next()?;
if prefix.is_empty() {
None
@@ -163,6 +162,22 @@ mod tests {
);
}
#[test]
fn toolkit_from_slug_handles_known_multi_segment_toolkits() {
assert_eq!(
toolkit_from_slug("ZOHO_MAIL_SEND_EMAIL"),
Some("zoho_mail".to_string())
);
assert_eq!(
toolkit_from_slug("ONE_DRIVE_GET_FILE"),
Some("one_drive".to_string())
);
assert_eq!(
toolkit_from_slug("MICROSOFT_TEAMS_SEND_MESSAGE"),
Some("microsoft_teams".to_string())
);
}
#[test]
fn find_curated_is_case_insensitive() {
let catalog = &[CuratedTool {