diff --git a/src/openhuman/composio/error_mapping.rs b/src/openhuman/composio/error_mapping.rs index f12e3ba2c..1f13a5d70 100644 --- a/src/openhuman/composio/error_mapping.rs +++ b/src/openhuman/composio/error_mapping.rs @@ -14,6 +14,13 @@ pub enum ComposioErrorClass { /// matches it). Distinct so the user gets reconnect guidance. See #2913. TriggerPermission, RateLimited, + /// Composio answered the execute call with an HTTP 404/410 — the action + /// endpoint or slug is unknown (or the endpoint is deprecated/Gone), NOT a + /// broken connection. Distinct so the user is told their integration is + /// still connected and is **not** advised to re-authenticate a healthy + /// account. See #3219 (direct-mode sent the lowercase toolkit slug to the + /// wrong execute path → 404/410 → misclassified as [`Self::ComposioPlatform`]). + ActionNotFound, UpstreamProvider, ComposioPlatform, Gateway, @@ -27,6 +34,7 @@ impl ComposioErrorClass { Self::InsufficientScope => "insufficient_scope", Self::TriggerPermission => "trigger_permission", Self::RateLimited => "rate_limited", + Self::ActionNotFound => "action_not_found", Self::UpstreamProvider => "upstream_provider", Self::ComposioPlatform => "composio_platform", Self::Gateway => "gateway", @@ -37,7 +45,15 @@ impl ComposioErrorClass { pub fn classify_composio_error(tool: &str, message: &str) -> ComposioErrorClass { let lower = message.to_ascii_lowercase(); - let class = if is_validation_shape(&lower) { + // EARLY status arm (#3219): a Composio HTTP 404/410 means the action + // endpoint/slug is unknown or deprecated — NOT a broken connection. It must + // win over the substring arms below, because the 404 body frequently carries + // the phrase "connection error, try to authenticate" that + // `is_composio_platform_shape` would otherwise match, telling the user to + // re-authenticate a perfectly healthy connection. + let class = if is_action_not_found_shape(&lower) { + ComposioErrorClass::ActionNotFound + } else if is_validation_shape(&lower) { ComposioErrorClass::Validation } else if is_insufficient_scope_shape(&lower) { ComposioErrorClass::InsufficientScope @@ -74,6 +90,7 @@ pub fn format_provider_error(tool: &str, raw: &str) -> String { ComposioErrorClass::InsufficientScope => format_insufficient_scope_message(tool, detail), ComposioErrorClass::TriggerPermission => format_trigger_permission_message(tool), ComposioErrorClass::RateLimited => format_rate_limited_message(tool, detail), + ComposioErrorClass::ActionNotFound => format_action_not_found_message(tool), ComposioErrorClass::UpstreamProvider => { format!("`{tool}` failed at the connected provider: {detail}") } @@ -101,6 +118,7 @@ pub fn remap_transport_error(tool: &str, raw: &str) -> String { ComposioErrorClass::InsufficientScope => format_insufficient_scope_message(tool, &detail), ComposioErrorClass::TriggerPermission => format_trigger_permission_message(tool), ComposioErrorClass::RateLimited => format_rate_limited_message(tool, &detail), + ComposioErrorClass::ActionNotFound => format_action_not_found_message(tool), ComposioErrorClass::Gateway => format!( "Temporary gateway error while calling `{tool}`: {}", summarize_gateway(raw) @@ -157,6 +175,23 @@ fn format_trigger_permission_message(tool: &str) -> String { ) } +/// Build the action-not-found guidance (issue #3219). +/// +/// Deliberately does **not** echo the raw provider `detail`: a Composio 404 +/// body often literally reads "connection error, try to authenticate", and +/// surfacing that verbatim would re-introduce the exact misleading re-auth +/// nudge this class exists to suppress. The raw text is still captured in the +/// `tracing::debug!` line in [`classify_composio_error`] for diagnosis. +fn format_action_not_found_message(tool: &str) -> String { + let toolkit = derive_toolkit_slug(tool); + format!( + "`{tool}` couldn't run: Composio reported this action as not found. Your {toolkit} \ + integration is still connected and working — this is not a sign-in problem. The action \ + name is likely out of date or Composio's API changed; try again with the current action \ + name, or report this if it keeps happening." + ) +} + fn format_rate_limited_message(tool: &str, detail: &str) -> String { format!( "`{tool}` hit an upstream rate limit ({detail}). Wait a minute and retry, or reduce \ @@ -210,6 +245,17 @@ fn is_rate_limited_shape(lower: &str) -> bool { || lower.contains("429") } +/// True when the message carries a Composio HTTP 404 or 410 status (#3219). +/// +/// Both `response_error` shapes (`HTTP 404` and `HTTP 404:
`) and the +/// wrapped v3/v2 fallback string keep the literal `HTTP` token, so a
+/// substring scan is reliable. Scoped to 404 (action/endpoint unknown) and 410
+/// (endpoint Gone/deprecated) on purpose — other 4xx/5xx keep their existing
+/// validation / scope / rate-limit / gateway classifications.
+fn is_action_not_found_shape(lower: &str) -> bool {
+ lower.contains("http 404") || lower.contains("http 410")
+}
+
fn is_composio_platform_shape(lower: &str) -> bool {
lower.contains("connection error, try to authenticate")
|| lower.contains("not enabled")
@@ -232,6 +278,7 @@ fn is_embedded_provider_failure(lower: &str) -> bool {
|| is_insufficient_scope_shape(lower)
|| is_trigger_permission_shape(lower)
|| is_rate_limited_shape(lower)
+ || is_action_not_found_shape(lower)
|| is_composio_platform_shape(lower)
|| lower.contains("composio")
|| lower.contains("google")
diff --git a/src/openhuman/composio/error_mapping_tests.rs b/src/openhuman/composio/error_mapping_tests.rs
index 26469cd82..6a9fbd98b 100644
--- a/src/openhuman/composio/error_mapping_tests.rs
+++ b/src/openhuman/composio/error_mapping_tests.rs
@@ -80,6 +80,81 @@ fn true_gateway_stays_gateway_class() {
);
}
+// ── HTTP 404/410 action-not-found vs auth (issue #3219) ────────────────
+
+#[test]
+fn classifies_http_404_with_auth_phrase_as_action_not_found_not_platform() {
+ // The exact #3219 shape: a 404 whose body carries the misleading
+ // "connection error, try to authenticate" phrase. Status must win.
+ let msg = "HTTP 404: connection error, try to authenticate";
+ assert_eq!(
+ classify_composio_error("GMAIL_SEND_EMAIL", msg),
+ ComposioErrorClass::ActionNotFound
+ );
+}
+
+#[test]
+fn classifies_http_410_gone_as_action_not_found() {
+ let msg = "HTTP 410: This endpoint is deprecated";
+ assert_eq!(
+ classify_composio_error("GOOGLEDOCS_UPDATE_DOCUMENT", msg),
+ ComposioErrorClass::ActionNotFound
+ );
+}
+
+#[test]
+fn action_not_found_message_does_not_recommend_reauth() {
+ let mapped = format_provider_error(
+ "GMAIL_SEND_EMAIL",
+ "HTTP 404: connection error, try to authenticate",
+ );
+ let lower = mapped.to_lowercase();
+ assert!(mapped.contains("[composio:error:action_not_found]"));
+ assert!(lower.contains("still connected"));
+ // Must NOT echo the misleading provider phrase or nudge re-auth/reconnect.
+ assert!(
+ !lower.contains("authenticate"),
+ "must not surface the re-auth phrase: {mapped}"
+ );
+ assert!(
+ !lower.contains("reconnect"),
+ "must not tell the user to reconnect a healthy connection: {mapped}"
+ );
+ assert!(
+ !mapped.contains("Settings → Connections"),
+ "must not show the re-auth CTA: {mapped}"
+ );
+}
+
+#[test]
+fn genuine_auth_phrase_without_http_status_stays_platform() {
+ // Same phrase, but with NO 4xx status — a real platform/connection issue
+ // must still classify as ComposioPlatform.
+ let msg = "connection error, try to authenticate";
+ assert_eq!(
+ classify_composio_error("GMAIL_SEND_EMAIL", msg),
+ ComposioErrorClass::ComposioPlatform
+ );
+}
+
+#[test]
+fn wrapped_v3_v2_404_fallback_classifies_as_action_not_found() {
+ // The real transport string after both v3 and v2 fail — the wrapped form
+ // produced by `execute_action`, fed through `remap_transport_error`.
+ let raw = "Composio execute failed on v3 (Composio v3 action execution failed: \
+ HTTP 404: connection error, try to authenticate) and v2 fallback \
+ (Composio v2 action execution failed: HTTP 410: Gone)";
+ let mapped = remap_transport_error("GMAIL_SEND_EMAIL", raw);
+ assert!(
+ mapped.contains("[composio:error:action_not_found]"),
+ "wrapped 404/410 must map to action_not_found, got: {mapped}"
+ );
+ assert!(
+ !mapped.to_lowercase().contains("authenticate"),
+ "wrapped path must not surface the re-auth phrase: {mapped}"
+ );
+}
+
// ── Trigger-permission denial (issue #2913) ───────────────────────────
#[test]
diff --git a/src/openhuman/composio/tools/direct.rs b/src/openhuman/composio/tools/direct.rs
index 96d924dc3..287524193 100644
--- a/src/openhuman/composio/tools/direct.rs
+++ b/src/openhuman/composio/tools/direct.rs
@@ -362,10 +362,22 @@ impl ComposioTool {
entity_id: Option<&str>,
connected_account_ref: Option<&str>,
) -> anyhow::Result {
- let tool_slug = normalize_tool_slug(action_name);
+ // The Composio v3 action-execute contract keys off the UPPERCASE_SNAKE
+ // *action* slug (e.g. `GMAIL_SEND_EMAIL`) at `/tools/execute/{slug}`.
+ // The previous code lowercased + dashed it into the *toolkit* slug
+ // (`gmail-send-email`) and posted to the wrong `/tools/{slug}/execute`
+ // path, so every direct-mode execute 404'd (issue #3219). Pass the
+ // action slug through verbatim (trimmed only); the v2 fallback already
+ // used the same untransformed name.
+ let action_slug = action_name.trim();
match self
- .execute_action_v3(&tool_slug, params.clone(), entity_id, connected_account_ref)
+ .execute_action_v3(
+ action_slug,
+ params.clone(),
+ entity_id,
+ connected_account_ref,
+ )
.await
{
Ok(result) => Ok(result),
@@ -379,12 +391,15 @@ impl ComposioTool {
}
fn build_execute_action_v3_request(
- tool_slug: &str,
+ action_slug: &str,
params: serde_json::Value,
entity_id: Option<&str>,
connected_account_ref: Option<&str>,
) -> (String, serde_json::Value) {
- let url = format!("{COMPOSIO_API_BASE_V3}/tools/{tool_slug}/execute");
+ // POST /api/v3/tools/execute/{ACTION_SLUG} — the action slug stays
+ // UPPERCASE_SNAKE (see `execute_action`). Path is `/tools/execute/{slug}`,
+ // NOT `/tools/{slug}/execute` (issue #3219).
+ let url = format!("{COMPOSIO_API_BASE_V3}/tools/execute/{action_slug}");
let account_ref = connected_account_ref.and_then(|candidate| {
let trimmed_candidate = candidate.trim();
(!trimmed_candidate.is_empty()).then_some(trimmed_candidate)
@@ -406,18 +421,18 @@ impl ComposioTool {
async fn execute_action_v3(
&self,
- tool_slug: &str,
+ action_slug: &str,
params: serde_json::Value,
entity_id: Option<&str>,
connected_account_ref: Option<&str>,
) -> anyhow::Result {
let (_default_url, body) = Self::build_execute_action_v3_request(
- tool_slug,
+ action_slug,
params,
entity_id,
connected_account_ref,
);
- let url = format!("{}/tools/{tool_slug}/execute", self.base_v3);
+ let url = format!("{}/tools/execute/{action_slug}", self.base_v3);
self.ensure_request_url(&url)?;
@@ -885,10 +900,6 @@ fn normalize_entity_id(entity_id: &str) -> String {
}
}
-fn normalize_tool_slug(action_name: &str) -> String {
- action_name.trim().replace('_', "-").to_ascii_lowercase()
-}
-
fn map_v3_tools_to_actions(items: Vec) -> Vec {
items
.into_iter()
diff --git a/src/openhuman/composio/tools/direct_tests.rs b/src/openhuman/composio/tools/direct_tests.rs
index 83284dff7..aa83a3d0e 100644
--- a/src/openhuman/composio/tools/direct_tests.rs
+++ b/src/openhuman/composio/tools/direct_tests.rs
@@ -184,18 +184,6 @@ fn normalize_entity_id_falls_back_to_default_when_blank() {
assert_eq!(normalize_entity_id("workspace-user"), "workspace-user");
}
-#[test]
-fn normalize_tool_slug_supports_legacy_action_name() {
- assert_eq!(
- normalize_tool_slug("GMAIL_FETCH_EMAILS"),
- "gmail-fetch-emails"
- );
- assert_eq!(
- normalize_tool_slug(" github-list-repos "),
- "github-list-repos"
- );
-}
-
#[test]
fn extract_redirect_url_supports_v2_and_v3_shapes() {
let v2 = json!({"redirectUrl": "https://app.composio.dev/connect-v2"});
@@ -313,19 +301,24 @@ fn composio_api_base_url_is_v3() {
}
#[test]
-fn build_execute_action_v3_request_uses_fixed_endpoint_and_body_account_id() {
+fn build_execute_action_v3_request_uses_execute_path_and_uppercase_action_slug() {
+ // #3219: v3 action execute is POST /tools/execute/{ACTION_SLUG} with the
+ // UPPERCASE_SNAKE action slug — NOT /tools/{lowercase-dashed}/execute.
let (url, body) = ComposioTool::build_execute_action_v3_request(
- "gmail-send-email",
- json!({"to": "test@example.com"}),
+ "GMAIL_SEND_EMAIL",
+ json!({"recipient_email": "test@example.com"}),
Some("workspace-user"),
Some("account-42"),
);
assert_eq!(
url,
- "https://backend.composio.dev/api/v3/tools/gmail-send-email/execute"
+ "https://backend.composio.dev/api/v3/tools/execute/GMAIL_SEND_EMAIL"
+ );
+ assert_eq!(
+ body["arguments"]["recipient_email"],
+ json!("test@example.com")
);
- assert_eq!(body["arguments"]["to"], json!("test@example.com"));
assert_eq!(body["user_id"], json!("workspace-user"));
assert_eq!(body["connected_account_id"], json!("account-42"));
}
@@ -333,7 +326,7 @@ fn build_execute_action_v3_request_uses_fixed_endpoint_and_body_account_id() {
#[test]
fn build_execute_action_v3_request_drops_blank_optional_fields() {
let (url, body) = ComposioTool::build_execute_action_v3_request(
- "github-list-repos",
+ "GITHUB_LIST_REPOSITORIES",
json!({}),
None,
Some(" "),
@@ -341,7 +334,7 @@ fn build_execute_action_v3_request_drops_blank_optional_fields() {
assert_eq!(
url,
- "https://backend.composio.dev/api/v3/tools/github-list-repos/execute"
+ "https://backend.composio.dev/api/v3/tools/execute/GITHUB_LIST_REPOSITORIES"
);
assert_eq!(body["arguments"], json!({}));
assert!(body.get("connected_account_id").is_none());
@@ -488,6 +481,61 @@ async fn list_tool_schemas_v3_sends_repeated_tags_to_v3_tools_endpoint() {
assert_eq!(items[0].toolkit_slug.as_deref(), Some("github"));
}
+// ── execute_action over HTTP (correct v3 path/slug/body reach the wire) ────
+
+#[tokio::test]
+async fn execute_action_v3_posts_uppercase_slug_to_execute_path() {
+ use axum::{extract::Path, routing::post, Json, Router};
+ use std::sync::Mutex;
+
+ // Capture the path slug + body the server actually received so we assert on
+ // the WIRE shape, not just the pure builder. Regression guard for #3219.
+ let captured: Arc>> = Arc::new(Mutex::new(None));
+ let sink = captured.clone();
+ let app = Router::new().route(
+ "/tools/execute/{slug}",
+ post(
+ move |Path(slug): Path, Json(body): Json| {
+ let sink = sink.clone();
+ async move {
+ *sink.lock().unwrap() = Some((slug, body));
+ Json(json!({ "successful": true, "data": { "id": "msg_1" } }))
+ }
+ },
+ ),
+ );
+ let base = start_mock_backend(app).await;
+
+ let tool = ComposioTool::new_with_v3_base("ck_test_direct", None, test_security(), base);
+ let result = tool
+ .execute_action(
+ "GMAIL_SEND_EMAIL",
+ json!({ "recipient_email": "a@b.com" }),
+ Some("workspace-user"),
+ Some("ca_42"),
+ )
+ .await
+ .expect("v3 execute should succeed against the mock");
+
+ assert_eq!(result["successful"], json!(true));
+
+ let (slug, body) = captured
+ .lock()
+ .unwrap()
+ .clone()
+ .expect("mock server should have observed the execute request");
+
+ // The action slug must reach the URL UPPERCASE_SNAKE — the toolkit-slug
+ // transform (gmail-send-email) was the root cause of the 404 in #3219.
+ assert_eq!(
+ slug, "GMAIL_SEND_EMAIL",
+ "must POST the uppercase action slug"
+ );
+ assert_eq!(body["arguments"]["recipient_email"], json!("a@b.com"));
+ assert_eq!(body["user_id"], json!("workspace-user"));
+ assert_eq!(body["connected_account_id"], json!("ca_42"));
+}
+
// ── ensure_https ──────────────────────────────────────────────────────────
#[test]
diff --git a/tests/composio_tools_direct_raw_coverage_e2e.rs b/tests/composio_tools_direct_raw_coverage_e2e.rs
index 2f70cc8a5..74f77bb36 100644
--- a/tests/composio_tools_direct_raw_coverage_e2e.rs
+++ b/tests/composio_tools_direct_raw_coverage_e2e.rs
@@ -164,7 +164,7 @@ async fn direct_composio_tool_uses_loopback_for_list_execute_connect_and_account
let execute_result = tool
.execute(json!({
"action": "execute",
- "tool_slug": "gmail-fetch-emails",
+ "tool_slug": "GMAIL_FETCH_EMAILS",
"params": { "query": "newer_than:1d" },
"connected_account_id": "acct-gmail"
}))
@@ -204,7 +204,7 @@ async fn direct_composio_tool_uses_loopback_for_list_execute_connect_and_account
}));
assert!(requests.iter().any(|request| {
request.method == "POST"
- && request.path == "/api/v3/tools/gmail-fetch-emails/execute"
+ && request.path == "/api/v3/tools/execute/GMAIL_FETCH_EMAILS"
&& request.body.pointer("/user_id") == Some(&json!(" entity-override "))
&& request.body.pointer("/connected_account_id") == Some(&json!("acct-gmail"))
}));
@@ -319,14 +319,14 @@ async fn composio_direct_handler(State(state): State, request: Reques
]
}))
.into_response(),
- (Method::POST, "/api/v3/tools/gmail-fetch-emails/execute") => Json(json!({
+ (Method::POST, "/api/v3/tools/execute/GMAIL_FETCH_EMAILS") => Json(json!({
"successful": true,
"data": {
"messages": [{ "id": "msg-direct", "subject": "hello" }]
}
}))
.into_response(),
- (Method::POST, "/api/v3/tools/fallback-action/execute") => (
+ (Method::POST, "/api/v3/tools/execute/FALLBACK_ACTION") => (
StatusCode::BAD_GATEWAY,
Json(json!({
"error": { "message": "temporary v3 outage" }
@@ -338,7 +338,7 @@ async fn composio_direct_handler(State(state): State, request: Reques
"input": body.get("input").cloned().unwrap_or_else(|| json!({}))
}))
.into_response(),
- (Method::POST, "/api/v3/tools/broken-action/execute") => (
+ (Method::POST, "/api/v3/tools/execute/BROKEN_ACTION") => (
StatusCode::BAD_REQUEST,
Json(json!({
"error": {
diff --git a/tests/tools_composio_large_round25_raw_coverage_e2e.rs b/tests/tools_composio_large_round25_raw_coverage_e2e.rs
index 83c70c4b9..ff9427785 100644
--- a/tests/tools_composio_large_round25_raw_coverage_e2e.rs
+++ b/tests/tools_composio_large_round25_raw_coverage_e2e.rs
@@ -282,7 +282,7 @@ async fn round25_direct_mode_ops_use_loopback_factory_for_tools_connections_and_
}));
assert!(requests.iter().any(|request| {
request.method == Method::POST
- && request.path == "/api/v3/tools/gmail-fetch-emails/execute"
+ && request.path == "/api/v3/tools/execute/GMAIL_FETCH_EMAILS"
&& request.body["arguments"]["query"] == "label:INBOX"
&& request.body["user_id"] == "entity-round25"
}));
@@ -402,7 +402,7 @@ async fn composio_direct_handler(State(state): State, request: Reques
}
}))
.into_response(),
- (Method::POST, "/api/v3/tools/gmail-fetch-emails/execute") => Json(json!({
+ (Method::POST, "/api/v3/tools/execute/GMAIL_FETCH_EMAILS") => Json(json!({
"successful": true,
"data": {
"messages": [
@@ -411,7 +411,7 @@ async fn composio_direct_handler(State(state): State, request: Reques
}
}))
.into_response(),
- (Method::POST, "/api/v3/tools/gmail-send-email/execute") => Json(json!({
+ (Method::POST, "/api/v3/tools/execute/GMAIL_SEND_EMAIL") => Json(json!({
"successful": false,
"error": "provider rejected send",
"data": {
diff --git a/tests/tools_composio_network_leftovers_raw_coverage_e2e.rs b/tests/tools_composio_network_leftovers_raw_coverage_e2e.rs
index a4a166e07..9886e7ba5 100644
--- a/tests/tools_composio_network_leftovers_raw_coverage_e2e.rs
+++ b/tests/tools_composio_network_leftovers_raw_coverage_e2e.rs
@@ -454,7 +454,7 @@ async fn round20_direct_composio_tool_covers_fallback_sanitizing_and_account_edg
}));
assert!(requests.iter().any(|request| {
request.method == Method::POST
- && request.path == "/api/v3/tools/gmail-fetch-emails/execute"
+ && request.path == "/api/v3/tools/execute/GMAIL_FETCH_EMAILS"
&& request.body.pointer("/connected_account_id") == Some(&json!("acct-gmail"))
}));
}
@@ -780,13 +780,13 @@ async fn composio_direct_handler(State(state): State, request: Reques
StatusCode::BAD_REQUEST,
"v2 broken list mentions connected_account_id and user_id",
),
- (Method::POST, "/api/v3/tools/gmail-fetch-emails/execute") => Json(json!({
+ (Method::POST, "/api/v3/tools/execute/GMAIL_FETCH_EMAILS") => Json(json!({
"successful": true,
"data": { "messages": [{ "id": "msg-round20" }] },
"error": null
}))
.into_response(),
- (Method::POST, "/api/v3/tools/broken-action/execute") => message_fail(
+ (Method::POST, "/api/v3/tools/execute/BROKEN_ACTION") => message_fail(
StatusCode::BAD_REQUEST,
"bad execute connected_account_id user_id entity_id",
),
diff --git a/tests/tools_composio_round24_raw_coverage_e2e.rs b/tests/tools_composio_round24_raw_coverage_e2e.rs
index 5e1b4632f..c16c75cf7 100644
--- a/tests/tools_composio_round24_raw_coverage_e2e.rs
+++ b/tests/tools_composio_round24_raw_coverage_e2e.rs
@@ -91,7 +91,7 @@ async fn round24_composio_direct_covers_v3_v2_fallbacks_and_account_shapes() {
.iter()
.find(|request| {
request.method == Method::POST
- && request.path == "/api/v3/tools/gmail-send-email/execute"
+ && request.path == "/api/v3/tools/execute/GMAIL_SEND_EMAIL"
})
.expect("v3 execute request");
assert_eq!(v3_execute.body["connected_account_id"], "conn-secret");
@@ -267,7 +267,7 @@ async fn composio_handler(State(state): State, request: Request) -> R
]
}))
.into_response(),
- (Method::POST, "/api/v3/tools/gmail-send-email/execute") => (
+ (Method::POST, "/api/v3/tools/execute/GMAIL_SEND_EMAIL") => (
StatusCode::BAD_REQUEST,
Json(json!({
"error": {
diff --git a/tests/tools_composio_round26_raw_coverage_e2e.rs b/tests/tools_composio_round26_raw_coverage_e2e.rs
index 2bc7bf5a8..d6d71d6f4 100644
--- a/tests/tools_composio_round26_raw_coverage_e2e.rs
+++ b/tests/tools_composio_round26_raw_coverage_e2e.rs
@@ -320,7 +320,7 @@ async fn round26_composio_direct_tool_covers_connect_execute_and_error_fallbacks
let v3_execute = requests
.iter()
.find(|request| {
- request.method == Method::POST && request.path == "/api/v3/tools/round26-action/execute"
+ request.method == Method::POST && request.path == "/api/v3/tools/execute/ROUND26_ACTION"
})
.expect("v3 execute request");
assert_eq!(v3_execute.body["user_id"], "entity-round26");
@@ -463,12 +463,12 @@ async fn composio_handler(State(state): State, request: Request) -> R
]
}))
.into_response(),
- (Method::POST, "/api/v3/tools/round26-action/execute") => Json(json!({
+ (Method::POST, "/api/v3/tools/execute/ROUND26_ACTION") => Json(json!({
"successful": true,
"data": { "message": "v3-execute-round26" }
}))
.into_response(),
- (Method::POST, "/api/v3/tools/round26-v2-only/execute") => (
+ (Method::POST, "/api/v3/tools/execute/ROUND26_V2_ONLY") => (
StatusCode::BAD_GATEWAY,
Json(json!({ "message": "v3 execute unavailable" })),
)