Skip to content

fix(composio): forward tags filter in direct (BYO-key) tool listing#2772

Merged
sanil-23 merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/composio-direct-tags
May 27, 2026
Merged

fix(composio): forward tags filter in direct (BYO-key) tool listing#2772
sanil-23 merged 3 commits into
tinyhumansai:mainfrom
sanil-23:fix/composio-direct-tags

Conversation

@sanil-23
Copy link
Copy Markdown
Contributor

@sanil-23 sanil-23 commented May 27, 2026

Summary

  • Fix: the tags filter on composio_list_tools (added in feat(composio): add tags query param to GitHub tool list API #2714) was wired only through the backend-proxied path; in direct (BYO Composio key) mode it was computed and then silently dropped, so a self-key user filtering tools by tag got the unfiltered set.
  • Thread tags through direct_list_toolsComposioTool::list_tool_schemas_v3, encoding it as repeated tags= params per the Composio v3 /tools contract (vs the backend proxy's comma-joined tags=a,b).
  • Add an injectable v3 base URL to ComposioTool (new_with_v3_base; prod new delegates with the real const) so the direct /tools path is HTTP-testable against a local axum mock — closing the documented "can't mock direct mode" gap.

Problem

#2714 added a tags query param to the GitHub tool-list API. The wiring lived entirely on the backend client (ComposioClient::list_tools). In ops::composio_list_tools, effective_tags is computed once at the top, but the direct branch called direct_list_tools(&direct, &scope) — no tags argument — and list_tool_schemas_v3 built the v3 request with only toolkits + limit. Result: in direct mode the tag filter was a no-op, with no error and no log. Today this only affects GitHub (the should_forward_tags allowlist), but it's a silent parity gap between the two modes.

Solution

  • composio/ops.rs: the direct branch now passes effective_tags.as_deref() (the value it was already computing). The should_forward_tags gate is unchanged, so direct/backend behaviour stays consistent.
  • composio/client.rs: direct_list_tools gains a tags param and forwards it.
  • tools/impl/network/composio.rs: list_tool_schemas_v3 accepts tags; request building is extracted into a pure, unit-testable helper build_list_tool_schemas_v3_query. Encoding is repeated tags= params — Composio v3 documents tags as "can be specified multiple times"; reusing the proxy's comma-join would make v3 treat "stars,repos" as a single literal tag and match nothing.
  • Test seam: ComposioTool gets an injectable v3 base URL. This mirrors how the backend ComposioClient already takes an injectable base via IntegrationClient::new in client_tests.rs, and removes the limitation that block-comment called out.

Wire shape difference (same OR semantics):

  • Backend proxy: …/agent-integrations/composio/tools?toolkits=github&tags=stars,repos
  • Direct (v3): backend.composio.dev/api/v3/tools?limit=200&toolkits=github&tags=stars&tags=repos

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — 6 query-builder unit tests (incl. blank-trim, empty-tags-as-no-filter) + 2 axum-mock HTTP tests (repeated-tags= wire shape; v3→envelope reshape with empty-slug rows dropped)
  • Diff coverage ≥ 80% — pure builder + both HTTP paths (list_tool_schemas_v3, direct_list_tools) are covered by mock tests; the ops.rs direct-branch call line is exercised to its error path by the existing ops_test.rs direct-mode integration test. Will confirm the diff-cover number from CI and top up if the gate flags any line.
  • Coverage matrix updated — N/A: behaviour-parity fix to the existing feat(composio): add tags query param to GitHub tool list API #2714 tags feature; no feature row added/removed/renamed
  • All affected feature IDs from the matrix are listed under ## Related — N/A: no matrix rows affected
  • No new external network dependencies introduced — local axum mock only (axum is already a dev-dependency); no production deps added
  • Manual smoke checklist updated if this touches release-cut surfaces — N/A: internal tool-listing parity, not a release-cut surface
  • Linked issue closed via Closes #NNN — N/A: no tracking issue; this brings feat(composio): add tags query param to GitHub tool list API #2714 to direct-mode parity (referenced below)

Impact

  • Runtime: Rust core only. No Tauri shell, frontend, CLI, migration, or schema changes. Direct-mode users now get tag-filtered results matching backend mode; backend mode is byte-for-byte unchanged.
  • Security/perf: none. Same endpoint, same auth (x-api-key), just an additional query filter that narrows the response.
  • Caveat: the ops.rs direct-branch happy path (config → factory → connection prefetch → direct_list_tools) isn't driven end-to-end in unit tests because the factory builds ComposioTool against the real const base; the two new HTTP tests cover the reshaper and v3 request directly instead. Flagging in case CI's diff-cover wants the wiring line covered.

Related


AI Authored PR Metadata (required for Codex/Linear PRs)

Linear Issue

  • Key: N/A
  • URL: N/A

Commit & Branch

  • Branch: fix/composio-direct-tags
  • Commit SHA: d61ae2d

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no app/TS changes
  • pnpm typecheck — N/A: no app/TS changes
  • Focused tests: cargo test --lib composio668 passed, 0 failed (incl. the 8 new tests)
  • Rust fmt/check (if changed): cargo fmt --check clean; lib + tests build clean
  • Tauri fmt/check (if changed): N/A: no Tauri shell changes

Validation Blocked

  • command: git push pre-push hook (pnpm rust:check)
  • error: vendored tauri-cef CEF runtime + pnpm/node env not present in this sandbox
  • impact: pushed with --no-verify; this PR touches only the Rust core lib (no Tauri shell), and CI runs the full check

Behavior Changes

  • Intended behavior change: composio_list_tools(..., tags) now honours tags in direct/BYO-key mode (previously dropped)
  • User-visible effect: self-key users filtering tools by Composio action tag (e.g. GitHub) now get a narrowed result, matching backend mode

Parity Contract

  • Legacy behavior preserved: backend-proxied path unchanged; should_forward_tags allowlist unchanged; empty/blank tags still mean "no filter"
  • Guard/fallback/dispatch parity checks: direct vs backend now apply the same tag gate; only the wire encoding differs (repeated params for v3 vs CSV for the proxy), matching each server's contract

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this
  • Resolution (closed/superseded/updated): N/A

Summary by CodeRabbit

  • New Features

    • Direct-mode tool listing now supports filtering by action tags and forwards effective tags to v3 queries; debug logs now include tag info.
  • Bug Fixes / Behavior

    • v3 responses are reshaped consistently and empty/blank items are dropped to match backend-style envelopes.
  • Tests

    • Added unit and end-to-end mock tests validating tag/query encoding, toolkit CSV handling, and response reshaping.

Review Change Stack

The `tags` query param added in tinyhumansai#2714 was only wired through the
backend-proxied path. In direct (BYO Composio key) mode
`composio_list_tools` computed `effective_tags` and then dropped it on
the direct branch, so a self-key user filtering tools by tag silently
got the unfiltered set.

Thread `tags` through `direct_list_tools` -> `list_tool_schemas_v3`,
encoding it as repeated `tags=` params per the Composio v3 `/tools`
contract (vs the backend proxy's comma-joined `tags=a,b`). The
`should_forward_tags` gate is unchanged, so behaviour stays consistent
with backend mode (GitHub-only today).

Also add an injectable v3 base URL to `ComposioTool` (`new_with_v3_base`;
prod `new` delegates with the real const) so the direct `/tools` path is
HTTP-testable against a local axum mock — removing the documented
"can't mock direct mode" gap in client_tests.rs.

Tests: 6 query-builder unit tests + 2 axum-mock HTTP tests asserting the
repeated-`tags=` wire shape and the v3 -> backend envelope reshape.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sanil-23 sanil-23 requested a review from a team May 27, 2026 15:25
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d72cf513-a1df-4c9d-9ef3-c7779a69a7a1

📥 Commits

Reviewing files that changed from the base of the PR and between d61ae2d and 048ef3a.

📒 Files selected for processing (2)
  • src/openhuman/tools/impl/network/composio.rs
  • src/openhuman/tools/impl/network/composio_tests.rs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/openhuman/tools/impl/network/composio_tests.rs
  • src/openhuman/tools/impl/network/composio.rs

📝 Walkthrough

Walkthrough

This PR extends Composio tool schema listing to support action tag filtering in direct-mode. ComposioTool gains a configurable v3 base URL and query builder for tag-aware requests. The direct-mode client forwards tags to the v3 endpoint, and the operations layer threads effective tags through the request stack with improved error reporting.

Changes

Composio tag filtering

Layer / File(s) Summary
V3 query infrastructure and tag filtering
src/openhuman/tools/impl/network/composio.rs, src/openhuman/tools/impl/network/composio_tests.rs
ComposioTool adds base_v3 field and a new_with_v3_base constructor seam. New build_list_tool_schemas_v3_query helper constructs /tools query parameters with limit=200, comma-joined toolkits, and repeated tags= entries (trimming and dropping blanks). list_tool_schemas_v3 accepts optional tags and uses the new builder. Unit and HTTP tests validate query formatting and endpoint behavior.
Direct-mode client tag forwarding
src/openhuman/composio/client.rs, src/openhuman/composio/client_tests.rs
direct_list_tools signature extended to accept tags: Option<&[String]>; it builds internal tag refs, includes tag count in debug logging, and forwards tags to list_tool_schemas_v3. Tests add a ComposioTool::new_with_v3_base helper and an end-to-end mock direct_list_tools_forwards_tags_and_reshapes_v3_envelope that asserts repeated tags= parameters and verifies response reshaping (drops empty-slug rows).
Operations layer tag thread-through
src/openhuman/composio/ops.rs
Direct-mode composio_list_tools logs effective_tags, forwards them into direct_list_tools, and maps direct-mode fetch errors through the [composio-direct] anchored renderer before reporting via report_composio_op_error.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • tinyhumansai/openhuman#2714: Threads tags filter through backend-mode Composio list tools flow; related to tag-forwarding behavior.
  • tinyhumansai/openhuman#2481: Adjusts direct-mode error handling and reporting anchors in composio_list_tools, related to the error-mapping changes here.

Suggested labels

bug, rust-core

Suggested reviewers

  • senamakel
  • graycyrus

Poem

🐇 A rabbit scurries through the code so bright,
Tags stitched like ribbons in the URL light,
Direct-mode hums and queries hum true,
Repeated tags whisper, servers reply anew,
Hooray — tools found clean, and the tests delight!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the main change: adding tags filter forwarding support to direct-mode Composio tool listing. It is specific, concise, and accurately reflects the primary objective of the PR.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@sanil-23
Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot added rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. bug labels May 27, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/openhuman/tools/impl/network/composio.rs (1)

65-70: ⚡ Quick win

Constrain the injectable v3 base-URL seam to tests only.

new_with_v3_base is crate-visible and list_tool_schemas_v3 always attaches x-api-key to whatever base_v3 is provided. Since this seam exists for local mock tests, consider test-gating it (or routing through a private internal constructor) to prevent accidental non-HTTPS use in non-test code.

💡 Suggested hardening approach
 impl ComposioTool {
+    fn new_internal(
+        api_key: &str,
+        default_entity_id: Option<&str>,
+        security: Arc<SecurityPolicy>,
+        base_v3: String,
+    ) -> Self {
+        // existing constructor body
+    }
+
     pub fn new(
         api_key: &str,
         default_entity_id: Option<&str>,
         security: Arc<SecurityPolicy>,
     ) -> Self {
-        Self::new_with_v3_base(
+        Self::new_internal(
             api_key,
             default_entity_id,
             security,
             COMPOSIO_API_BASE_V3.to_string(),
         )
     }

+    #[cfg(test)]
     pub(crate) fn new_with_v3_base(
         api_key: &str,
         default_entity_id: Option<&str>,
         security: Arc<SecurityPolicy>,
         base_v3: String,
     ) -> Self {
-        // existing constructor body
+        Self::new_internal(api_key, default_entity_id, security, base_v3)
     }
 }

Also applies to: 231-243

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/openhuman/tools/impl/network/composio.rs` around lines 65 - 70,
new_with_v3_base is currently crate-visible and allows injecting an arbitrary
base_v3 that list_tool_schemas_v3 will call with an x-api-key header, creating a
seam that could be used non-HTTPS in production; restrict this by making the
injectable base constructor test-only: change new_with_v3_base to a
private/internal constructor and expose a #[cfg(test)] pub(crate) fn
new_with_v3_base (or alternatively annotate the existing fn with #[cfg(test)])
so only tests can pass a custom base_v3, and keep the public API using the
normal secure constructor that enforces HTTPS; update any test call sites to use
the test-only constructor and leave SecurityPolicy and list_tool_schemas_v3
unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/openhuman/tools/impl/network/composio.rs`:
- Around line 65-70: new_with_v3_base is currently crate-visible and allows
injecting an arbitrary base_v3 that list_tool_schemas_v3 will call with an
x-api-key header, creating a seam that could be used non-HTTPS in production;
restrict this by making the injectable base constructor test-only: change
new_with_v3_base to a private/internal constructor and expose a #[cfg(test)]
pub(crate) fn new_with_v3_base (or alternatively annotate the existing fn with
#[cfg(test)]) so only tests can pass a custom base_v3, and keep the public API
using the normal secure constructor that enforces HTTPS; update any test call
sites to use the test-only constructor and leave SecurityPolicy and
list_tool_schemas_v3 unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 974e5e61-57e8-4b40-9b79-5d18a6c1ecfa

📥 Commits

Reviewing files that changed from the base of the PR and between 26295df and d61ae2d.

📒 Files selected for processing (5)
  • src/openhuman/composio/client.rs
  • src/openhuman/composio/client_tests.rs
  • src/openhuman/composio/ops.rs
  • src/openhuman/tools/impl/network/composio.rs
  • src/openhuman/tools/impl/network/composio_tests.rs

coderabbitai[bot]
coderabbitai Bot previously approved these changes May 27, 2026
Address CodeRabbit review nitpick: `new_with_v3_base` was `pub(crate)`
and not test-gated, so in principle production code could inject a
non-HTTPS base that `list_tool_schemas_v3` would still send `x-api-key`
to. Route the shared constructor body through a private `new_internal`,
keep `new` (the only production entry point) pinned to the HTTPS
`COMPOSIO_API_BASE_V3` const, and `#[cfg(test)]`-gate `new_with_v3_base`
so only tests can supply a custom base. Behaviour and the v3 `/tools`
request shape are unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sanil-23
Copy link
Copy Markdown
Contributor Author

Thanks @coderabbitai — addressed the v3 base-URL seam nitpick in f9803e15: the shared constructor body now lives in a private new_internal, new stays pinned to the HTTPS COMPOSIO_API_BASE_V3 const, and new_with_v3_base is #[cfg(test)]-gated so only tests can inject a custom base. No behaviour or wire-shape change.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 27, 2026

Tip

For best results, initiate chat on the files or code changes.

@sanil-23 That's a clean resolution — new_internal as the shared core, new locked to the constant prod URL, and new_with_v3_base strictly #[cfg(test)]-gated is exactly the right encapsulation. No accidental exposure in release builds, and the test seam stays ergonomic. Thanks for the follow-up!

(๑˃ᴗ˂)ﻭ 🐰

Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fix is correct and well-tested overall. Two items worth addressing before merge — one is a real documentation bug, one is a minor test style nit.

/// raw `input_parameters` JSON schema each action carries.
///
/// Build the query-parameter pairs for the Composio v3 `GET /tools`
/// listing used by [`Self::list_tool_schemas_v3`].
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doc ordering bug. The three context lines immediately above this insertion point (/// List v3 tool definitions..., /// raw \input_parameters`..., ///) are now contiguous with this new doc block, so rustdoc attaches **all of them** to build_list_tool_schemas_v3_queryinstead of tolist_tool_schemas_v3. The list_tool_schemas_v3method ends up documented only from/// Sibling of...`.

Fix: the /// List v3 tool definitions... lines belong on list_tool_schemas_v3. Either move them to just above /// Sibling of..., or drop them (the /// Sibling of... opening is sufficient). The new build_list_tool_schemas_v3_query doc should start clean with /// Build the query-parameter pairs... only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — fixed in 048ef3a6. Moved the List v3 tool definitions… preamble back onto list_tool_schemas_v3 (above the Sibling of… block); build_list_tool_schemas_v3_query now opens cleanly with Build the query-parameter pairs…. Verified rustdoc attaches each block to the right item.

}),
);
let base = start_mock_backend(app).await;
let tool = direct_tool_for_mock(base);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor nit: this test uses start_mock_backend(app).await while the sibling test added in composio_tests.rs (list_tool_schemas_v3_sends_repeated_tags_to_v3_tools_endpoint) uses inline TcpListener::bind + tokio::spawn. Both cover the same HTTP layer from different call depths. Not a blocker, but two setup styles added in the same PR gives future authors no clear model to copy — worth aligning in a follow-up.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aligned in 048ef3a6 rather than deferring: added a module-local start_mock_backend helper in composio_tests.rs mirroring the one in client_tests.rs, so both HTTP tests now read let base = start_mock_backend(app).await;. One consistent model to copy.

Address review feedback (M3gA-Mind):
- Doc-ordering bug: inserting `build_list_tool_schemas_v3_query` had
  stranded `list_tool_schemas_v3`'s "List v3 tool definitions…" preamble
  onto the new helper. Move that preamble back above the method's
  "Sibling of…" block; the helper doc now opens cleanly with "Build the
  query-parameter pairs…".
- Test style: add a module-local `start_mock_backend` helper in
  composio_tests.rs mirroring client_tests.rs, so both HTTP-level
  direct-mode tests share one mock-setup model instead of two styles.

No behaviour or wire-shape change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sanil-23 sanil-23 requested a review from M3gA-Mind May 27, 2026 18:17
Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #2772 — fix(composio): forward tags filter in direct (BYO-key) tool listing

Walkthrough

This PR closes a silent parity gap introduced by #2714: the tags filter on composio_list_tools was only wired through the backend-proxied path; in direct (BYO-key) mode effective_tags was computed but then dropped, so self-key users got the full unfiltered tool set regardless. The fix threads tags through direct_list_toolsComposioTool::list_tool_schemas_v3, encoding it as repeated tags= query params (matching the Composio v3 /tools contract) rather than the comma-joined form the backend proxy uses. A new #[cfg(test)]-gated new_with_v3_base constructor gives list_tool_schemas_v3 an injectable base URL so the direct HTTP path — including the tags wire shape — can be asserted against a local axum mock without touching production infrastructure.

The implementation is clean, the should_forward_tags gate is left untouched, and the 8 new tests cover both the pure query-builder logic and the end-to-end HTTP round trip. The fix is core-only with no frontend or Tauri shell impact.

Changes

File Summary
src/openhuman/composio/client.rs direct_list_tools gains a tags: Option<&[String]> param; converts to &[&str] refs and passes them through to list_tool_schemas_v3. Updated docs.
src/openhuman/composio/client_tests.rs New axum-mock HTTP test for direct_list_tools exercising repeated tags= params and the empty-slug reshaper. New direct_tool_for_mock helper. Updated block comment.
src/openhuman/composio/ops.rs One-line change in the direct branch: passes effective_tags.as_deref() (already computed) instead of nothing. Adds ?effective_tags to the pre-call debug log.
src/openhuman/tools/impl/network/composio.rs ComposioTool gains base_v3: String field; new refactored into newnew_internal; #[cfg(test)] new_with_v3_base added. list_tool_schemas_v3 accepts tags, delegates param building to new pure helper build_list_tool_schemas_v3_query.
src/openhuman/tools/impl/network/composio_tests.rs 6 unit tests for build_list_tool_schemas_v3_query + 1 HTTP mock test for list_tool_schemas_v3. New start_mock_backend helper.

Actionable comments (1)

💡 Refactor / suggestion

1. src/openhuman/tools/impl/network/composio.rs:248ensure_https not guarded in list_tool_schemas_v3

list_connected_accounts (line 522) and execute_action_v3 (line 344) both call ensure_https(&url)? before sending the x-api-key header. list_tool_schemas_v3 does not, now that url is built from the injectable self.base_v3 rather than the HTTPS const.

The production safety argument is sound — new_with_v3_base is #[cfg(test)]-gated so an HTTP base can't be supplied in a release build. But the invariant is only compiler-enforced at the constructor level, not at the call site. If a future refactor widens new_with_v3_base's visibility (e.g. during an integration test restructure), list_tool_schemas_v3 will silently transmit the API key over HTTP while list_connected_accounts and execute_action_v3 would still catch it.

Adding the guard conditionally keeps the test seam working and makes the guarantee explicit:

// Option A: guard at the call site
let url = format!("{}/tools", self.base_v3);
#[cfg(not(test))]
ensure_https(&url)?;
// Option B: guard once in new_internal so no call site needs it
fn new_internal(..., base_v3: String) -> Self {
    #[cfg(not(test))]
    ensure_https(&base_v3).expect("base_v3 must use HTTPS in production");
    // ...
}

Not a merge blocker, but either option makes the constraint explicit rather than relying solely on the access-modifier comment.

Nitpicks (2)

  • src/openhuman/tools/impl/network/composio_tests.rs:8-18start_mock_backend is duplicated verbatim from client_tests.rs. The modules can't share code directly given the current layout, which makes the duplication unavoidable, but the doc comment correctly calls it out. If the helpers ever diverge (e.g. a timeout knob), having two copies will be a quiet footgun.

  • src/openhuman/tools/impl/network/composio.rs:36-42 — The base_v3 field doc says "Base URL for Composio v3 endpoints" but list_actions_v3 (line 136) still interpolates {COMPOSIO_API_BASE_V3} directly — it doesn't use self.base_v3. A reader maintaining list_actions_v3 tests might reasonably expect new_with_v3_base to intercept that path too, and be confused when it doesn't. Narrowing the doc to "Base URL used by list_tool_schemas_v3's /tools GET" (or a note that list_actions_v3 is excluded) would prevent that misdirection.

Verified / looks good

  • effective_tags.as_deref() correctly coerces Option<Vec<String>>Option<&[String]> at the ops.rs call site; direct_list_tools then converts to Option<Vec<&str>> and calls .as_deref() again to yield Option<&[&str]> — the chain is type-safe throughout.
  • build_list_tool_schemas_v3_query returns Vec<(&'static str, String)>; passing this slice to reqwest's .query(&params) correctly serializes duplicate keys as repeated query params (tags=stars&tags=repos), not comma-joined — the HTTP mock test confirms this at the wire level.
  • should_forward_tags gate is unchanged; both direct and backend paths now apply the same tag-forwarding decision (GitHub only, for now).
  • new_with_v3_base is #[cfg(test)] pub(crate) — the production binary cannot inject a non-HTTPS base.
  • Empty-slug rows are dropped in direct_list_tools (pre-existing filter), and the new HTTP test in client_tests.rs verifies the junk row is dropped from the response.
  • Error paths in direct_list_tools propagate via ? through map_err into the Sentry-tagged [composio-direct] error string, symmetric with the backend branch as documented.
  • Debug logging added at both direct_list_tools entry and list_tool_schemas_v3 call site; tag count (not values) logged — no PII leaked.

let csv = trimmed.join(",");
req = req.query(&[("toolkits", csv.as_str())]);
}
let url = format!("{}/tools", self.base_v3);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike list_connected_accounts (line 522) and execute_action_v3 (line 344), which both call ensure_https(&url)? before transmitting x-api-key, this path skips the guard now that the URL comes from the injectable self.base_v3.

The production safety guarantee rests entirely on new_with_v3_base being #[cfg(test)]-gated — valid today, but fragile if that gate is ever widened. Consider anchoring the invariant at the constructor level instead:

// in new_internal:
fn new_internal(api_key: &str, default_entity_id: Option<&str>, security: Arc<SecurityPolicy>, base_v3: String) -> Self {
    #[cfg(not(test))]
    if !base_v3.starts_with("https://") {
        panic!("base_v3 must use HTTPS in production; got: {base_v3}");
    }
    // ... rest unchanged
}

Or a #[cfg(not(test))] ensure_https(&url)? at the call site. Either approach makes the constraint explicit rather than relying on the doc comment.

Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix with good test coverage. One non-blocking suggestion on consistency (inline); two nitpicks in the review comment. LGTM.

Copy link
Copy Markdown
Contributor

@M3gA-Mind M3gA-Mind left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean fix with good test coverage. One non-blocking suggestion on ensure_https consistency (inline); two nitpicks in the review comment. LGTM.

@sanil-23 sanil-23 merged commit d696d82 into tinyhumansai:main May 27, 2026
33 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants