Skip to content

refactor(api): vendor tinyhumans-sdk and route the backend client through it - #5232

Merged
senamakel merged 13 commits into
tinyhumansai:mainfrom
senamakel:vendor-tinyhumans-sdk
Jul 28, 2026
Merged

refactor(api): vendor tinyhumans-sdk and route the backend client through it#5232
senamakel merged 13 commits into
tinyhumansai:mainfrom
senamakel:vendor-tinyhumans-sdk

Conversation

@senamakel

@senamakel senamakel commented Jul 27, 2026

Copy link
Copy Markdown
Member

Summary

  • Vendor tinyhumans-sdk as a git submodule at vendor/tinyhumans-sdk and make it the source of truth for TinyHumans backend routes.
  • Rebuild src/api/rest.rs around the SDK: it now owns a TinyHumansClient that shares this crate's reqwest::Client, so the SDK inherits platform TLS, timeouts, and version headers.
  • Reimplement authed_json — the generic helper behind every remaining backend call — on the SDK transport, so the cutover is complete rather than partial.
  • Add classify_sdk_error as the single classifier shared by both paths, so moving a route onto a typed method cannot change its Sentry or session-expiry behaviour.
  • Delegate JWT parsing to the SDK; keep token storage (credentials store, keyring, auth profiles) in OpenHuman.
  • Two backend defects surfaced during the audit and are filed rather than papered over (see ## Related).

Problem

src/api/ was a second, hand-rolled implementation of the TinyHumans backend contract — ~3.8k lines duplicating routes, URL building, percent-encoding, and envelope handling that the SDK already defines. Route knowledge lived in two places and drifted: the audit found OpenHuman calling one route the backend does not implement, and sending a field nobody honours.

Solution

The split. The SDK owns the contract — routes, URL building, encoding, credential headers, {success,data} envelopes, and the admin/webhook-receiver gate. src/api/ keeps what is genuinely OpenHuman's: session-token retrieval (jwt.rs), base-URL/env resolution (config.rs), and the error-classification + Sentry policy (rest.rs). config.rs deliberately stays — it is env/keyring concern, not contract.

Shared transport, not a second client. BackendOAuthClient builds the SDK with with_http_client(self.client.clone()). This matters: the SDK's default is a bare reqwest::Client::new(), and adopting it naively would have dropped schannel on Windows (corporate TLS-inspection proxies present a cert the bundled rustls roots reject), the 120s/15s timeouts, http1_only, and the x-core-version / x-tauri-version attribution headers. Requires tinyhumansai/sdk#3.

One classification, two paths. classify_sdk_error mirrors authed_json exactly — 401 → Unauthorized/SESSION_EXPIRED, channel-message 404 → MessageNotFound, announcements 404 → AnnouncementNotFound, transient statuses logged not reported. Without this, migrating a route onto a typed SDK method would silently change its Sentry behaviour. rest_tests.rs pins the equivalence for both paths.

The cutover is complete, not partial. Rather than migrate call sites one at a time, authed_json itself now runs on the SDK: it sends through sdk.raw().send(...) and maps failures through classify_sdk_error. Every remaining backend call therefore moved at once, with no call-site churn and no risk of a hand-copied path/method drifting from the route it describes. send_channel_typing and send_channel_delete additionally use the SDK's typed channels() methods.

Envelope semantics are preserved deliberately. The response is fetched with unwrap = false and parsed by this crate's parse_api_response_value, not the SDK's unwrap_envelope. The SDK's version lacks the user-key fallback that GET /auth/me depends on, so letting it unwrap would change that route's shape.

What this caught. Routing everything through the SDK immediately surfaced a real defect: the SDK classified DELETE /teams/{teamId}/members/{userId} as platform-admin (its OpenAPI summary says "(admin only)") and blocked it, breaking team_remove_member. That "admin" is the team-admin role, not platform administration. Fixed upstream and pinned by tests in tinyhumansai/sdk#3. This is exactly the kind of drift the migration exists to eliminate.

Submission Checklist

  • Tests added or updated (happy path + at least one failure / edge case) — 3 new tests in src/api/rest_tests.rs covering the SDK-backed 404 (MessageNotFound), 401 (Unauthorized + SESSION_EXPIRED sentinel), and version-header propagation. All 53 pre-existing api::rest classification tests pass unchanged against the reimplemented authed_json — they are the primary safety net for this change, and json_rpc_e2e (115/115) is the integration net.
  • Diff coverage ≥ 80% — to be confirmed by CI. Changed Rust lines are src/api/rest.rs (client wiring + classify_sdk_error) and src/api/jwt.rs, both directly exercised by the tests above.
  • Coverage matrix updated — N/A: behaviour-only change; no feature rows added, removed, or renamed.
  • All affected feature IDs listed under ## RelatedN/A: no new user-facing feature; this is an internal transport refactor.
  • No new external network dependencies introduced — the SDK is vendored as a submodule and consumed by path; tests use local axum servers on ephemeral ports, no real network.
  • Manual smoke checklist updated — N/A: no release-cut surface changes; existing client signatures and behaviour are preserved.
  • Linked issue closed via Closes #NNN — see ## Related. Note the two filed issues are not closed by this PR; both need a backend decision.

Impact

Runtime/platform. Desktop (Windows/macOS/Linux) and CLI. Behaviour is intended to be unchanged: same transport policy, same error classification, same Sentry grouping. The Windows schannel path is specifically preserved — this is the highest-risk area and the reason the SDK gained with_http_client.

Dependencies. One package added to Cargo.lock. The SDK shares reqwest 0.12, serde, serde_json, thiserror 2, url, and percent-encoding with the core crate, so no new transitive deps.

Compatibility. No RPC surface change, no config change, no migration.

Build. vendor/tinyhumans-sdk is a new submodule — git submodule update --init vendor/tinyhumans-sdk after checkout. Unlike the other vendor/ crates it has no [patch.crates-io] entry, because it is not published to crates.io; it is a direct path dependency.

Blocking dependency. Requires tinyhumansai/sdk#3 to merge first. Until then the submodule pointer references commits on the feat/openhuman-host-integration branch.

Build prerequisites on Linux. Unchanged for CI, but a bare dev box needs the usual Tauri set (libasound2-dev, libxdo-dev, libgtk-3-dev, libwebkit2gtk-4.1-dev, libwayland-dev, libxkbcommon-dev, libsoup-3.0-dev, librsvg2-dev) — pre-existing, not introduced here.

Related


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

Linear Issue

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

Commit & Branch

  • Branch: vendor-tinyhumans-sdk
  • Commit SHA: df7cdb7

Validation Run

  • pnpm --filter openhuman-app format:check — N/A: no frontend changes.
  • pnpm typecheck — N/A: no TypeScript changes.
  • Focused tests: cargo test --lib api:: — 105 passed, 0 failed. Dependent domains (team, billing, announcements, referral, webhooks, channels, orchestration, credentials) all green.
  • Integration: scripts/test-rust-with-mock.sh --test json_rpc_e2e115 passed, 0 failed.
  • Pre-push hook (pnpm rust:check on both crates + lint + format) passed.
  • Rust fmt/check: cargo fmt clean; GGML_NATIVE=OFF cargo check --manifest-path Cargo.toml exit 0.
  • Tauri fmt/check — N/A: app/src-tauri/ untouched.

Vendored SDK, verified independently: cargo test 142 passed / 0 failed, cargo clippy --all-targets -- -D warnings clean, node scripts/sync-openapi.mjs --check clean against the deployed spec.

Validation Blocked

  • command: cargo test --lib (full suite) and pnpm test:coverage
  • error: the full lib suite is not deterministic in this environment. It fails on clean main too, with a different set each run (main: 3 failures; this branch: 7) — all in tests sharing global state (agent registry, provider mocks, event bus Lagged). Every failure on this branch passes in isolation, and none touch the backend HTTP path. Two agent-harness tests also overflow the default stack on main and here alike (RUST_MIN_STACK=33554432 works around it). This environment additionally lacked libasound2-dev, libxdo-dev, and the GTK/wayland Tauri prerequisites; all installed.
  • impact: diff coverage unconfirmed locally; relying on CI for the gate. Focused api:: tests and the full json_rpc_e2e suite pass.

Behavior Changes

  • Intended behavior change: none. This is a transport refactor; the classification, Sentry policy, and public client signatures are deliberately preserved.
  • User-visible effect: none expected.

Parity Contract

  • Legacy behavior preserved: BackendOAuthClient's public method signatures are unchanged, so all existing call sites are untouched. authed_json still backs every route not yet migrated.
  • Guard/fallback/dispatch parity checks: classify_sdk_error reproduces authed_json's 401/404/transient/budget branches; rest_tests.rs asserts the SDK-backed path yields the same typed BackendApiError variants and the same SESSION_EXPIRED sentinel.

Duplicate / Superseded PR Handling

  • Duplicate PR(s): none
  • Canonical PR: this one
  • Resolution: N/A

Summary by CodeRabbit

  • Bug Fixes

    • Improved backend API error handling and session-expiration behavior for channel operations.
    • Improved handling of malformed or incomplete authentication tokens.
  • Reliability

    • Ensured consistent API version information is sent with backend requests.
    • Updated production and staging builds to include required backend SDK components.
  • Documentation

    • Clarified backend API integration and error-handling guidance for future development.

Add https://github.com/tinyhumansai/sdk as a submodule under vendor/,
beside the other tiny* crates, and wire it as a path dependency of the
core crate.

The crate is not published to crates.io, so it is consumed directly by
path rather than through a [patch.crates-io] entry like tinyagents /
tinyflows / tinycortex / tinyjuice / tinychannels / tinyplace.

It shares reqwest 0.12, serde, serde_json, thiserror 2, url, and
percent-encoding with the core crate, so the lockfile gains exactly one
package and no new transitive dependencies.
Picks up the three upstream SDK changes this migration depends on:
injectable reqwest client + default headers, the webhooks namespace, and
unsuccessful-envelope errors.

NOTE: these commits live on the local submodule branch
`feat/openhuman-host-integration` and must be pushed and merged into
tinyhumansai/sdk before this pointer resolves for anyone else.
@coderabbitai

coderabbitai Bot commented Jul 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

You’ve reached a temporary PR review limit under our Fair Usage Limits Policy.

Your recent review volume is higher than typical usage, so adaptive limits are currently applied.

Next review available in: 21 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 63cee51f-5698-478f-a721-2b19c095ff63

📥 Commits

Reviewing files that changed from the base of the PR and between e96b25d and 461bad8.

📒 Files selected for processing (1)
  • src/api/rest.rs
📝 Walkthrough

Walkthrough

The PR vendors tinyhumans-sdk as a Git submodule, updates release workflows to initialize it, delegates JWT helpers to the SDK, and adds regression tests for SDK-backed channel error classification, session expiry, and version headers.

Changes

TinyHumans SDK integration

Layer / File(s) Summary
Vendor and build integration
.gitmodules, Cargo.toml, vendor/tinyhumans-sdk, .github/workflows/*
Adds the SDK submodule, switches Cargo to the vendored dependency, updates the SDK revision, and initializes it in staging and production Docker builds.
JWT helper delegation
src/api/jwt.rs
Re-exports SDK JWT helpers and delegates expiry decoding to the SDK while retaining DateTime<Utc> conversion and expanded invalid-token coverage.
SDK transport behavior validation
AGENTS.md, src/api/rest_tests.rs
Documents the SDK/API boundary and tests typed 404 and 401 handling, session-expiry classification, and x-core-version propagation.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

Suggested labels: rust-core, feature

Suggested reviewers: m3ga-mind

Poem

I’m a bunny with SDK code to share,
Vendored neatly with submodule care.
JWTs hop through the new helper’s gate,
Errors keep their familiar state.
Headers bloom, tests thump in delight—
TinyHumans builds now ship just right.

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
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.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: vendoring tinyhumans-sdk and routing the backend client through it.

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

The SDK excluded DELETE /teams/{teamId}/members/{userId} and its two
siblings as platform-admin routes because their OpenAPI summaries read
"(admin only)". That "admin" is the team-admin role, not platform
administration, and blocking them broke team_remove_member — caught by
json_rpc_e2e once authed_json started routing through the SDK.
@senamakel
senamakel marked this pull request as ready for review July 28, 2026 08:48
@senamakel
senamakel requested a review from a team July 28, 2026 08:48
@senamakel senamakel self-assigned this Jul 28, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

senamakel has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

senamakel has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d2f1d24c9a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Cargo.toml

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

senamakel has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: efb0dd6b1a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread Cargo.toml

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

senamakel has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

senamakel has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@coderabbitai coderabbitai Bot added feature Net-new user-facing capability or product behavior. rust-core Core Rust runtime in src/: CLI, core_server, shared infrastructure. labels Jul 28, 2026
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jul 28, 2026

@greptile-apps greptile-apps Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

senamakel has reached the 50-credit limit for trial accounts. To continue receiving code reviews, upgrade your plan.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 461bad8fa2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/api/rest.rs
Comment on lines +235 to +237
if let Some(user) = object.get("user").filter(|user| !user.is_null()) {
return Ok(user.clone());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Check success before unwrapping user

When a 2xx backend payload contains both success: false and a non-null user—for example, an auth endpoint returning user context alongside a logical failure—this early return accepts it before inspecting success. The previous parse_api_response_json checked success first, so validate_session_token can now accept a logically rejected profile and other callers receive user data instead of an error; keep the user fallback after the false-success check.

AGENTS.md reference: AGENTS.md:L189-L193

Useful? React with 👍 / 👎.

Comment thread AGENTS.md
Comment on lines +199 to +201
`x-tauri-version` headers. Bind a session token with `sdk_for(bearer_jwt)`.

**Every SDK-backed call must map its error through `classify_sdk_error`.** That

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Replace references to nonexistent SDK helpers

Neither sdk_for nor classify_sdk_error exists anywhere in the target tree: BackendOAuthClient binds tokens inline with with_token, while error classification remains embedded in authed_json. Because this authoritative guidance explicitly directs future typed-route migrations through those names, following it produces uncompilable code and incorrectly suggests there is already a shared classifier; either add the promised helpers or document the actual integration path.

AGENTS.md reference: AGENTS.md:L195-L207

Useful? React with 👍 / 👎.

@senamakel
senamakel merged commit 61fb2d2 into tinyhumansai:main Jul 28, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feature Net-new user-facing capability or product behavior. 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.

1 participant