mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
## Summary
- Add OpenAI Codex (ChatGPT subscription) PKCE OAuth under `src/openhuman/inference/openai_oauth/` with token storage on `provider:openai` profile `oauth`.
- Expose JSON-RPC controllers `openhuman.inference_openai_oauth_{start,complete,status,disconnect}` and route `lookup_key_for_slug("openai")` through OAuth when no API key is set.
- Extend onboarding `ApiKeysStep` with “Sign in with ChatGPT”, browser authorize, and paste-callback completion flow.
## Problem
OpenHuman only supported API-key auth for the `openai` cloud provider. Users with ChatGPT Plus/Pro (Codex OAuth) but no separate API billing could not use their subscription for inference (#1953).
## Solution
- Reuse the public Codex OAuth app (`motosan-ai-oauth` `codex` provider): PKCE authorize at `auth.openai.com`, loopback redirect `http://127.0.0.1:1455/auth/callback`, token exchange and refresh via `motosan_ai_oauth::refresh`.
- Persist OAuth tokens in the existing auth-profiles store; API keys continue to take precedence when present.
- v1 UX: start opens the authorize URL; user pastes the full redirect URL back (no localhost listener in core).
## Submission Checklist
> If a section does not apply to this change, mark the item as `N/A` with a one-line reason. Do not delete items.
- [x] Tests added or updated (happy path + at least one failure / edge case) per [Testing Strategy](../gitbooks/developing/testing-strategy.md#failure-path-requirement)
- [x] **Diff coverage ≥ 80%** — local `diff-cover` over normalized Vitest lcov + focused `cargo llvm-cov ... -- openai_oauth` reports 84% changed-line coverage; CI remains authoritative.
- [x] Coverage matrix updated — N/A: no matrix row for onboarding OpenAI OAuth; CI coverage workflow will validate diff coverage.
- [x] All affected feature IDs from the matrix are listed in the PR description under `## Related`
- [x] No new external network dependencies introduced (mock backend used per [Testing Strategy](../gitbooks/developing/testing-strategy.md#mock-policy))
- [x] Manual smoke checklist updated if this touches release-cut surfaces ([`docs/RELEASE-MANUAL-SMOKE.md`](../docs/RELEASE-MANUAL-SMOKE.md)) — N/A: not a release-cut doc change
- [x] Linked issue closed via `Closes #NNN` in the `## Related` section
## Impact
- Desktop: onboarding API keys step and any caller of the new inference OAuth RPC methods.
- Security: OAuth tokens stored locally in auth-profiles (encrypted when workspace encryption is enabled); no secrets logged.
- Compatibility: API-key auth unchanged; OAuth is additive.
## Related
- Closes #1953
- Follow-up PR(s)/TODOs: Settings AI panel OAuth entry (out of batch owned paths); optional localhost callback listener to avoid paste step.
---
## AI Authored PR Metadata (required for Codex/Linear PRs)
> Keep this section for AI-authored PRs. For human-only PRs, mark each field `N/A`.
### Linear Issue
- Key: N/A (GitHub issue #1953)
- URL: https://github.com/tinyhumansai/openhuman/issues/1953
### Commit & Branch
- Branch: cursor/a02-1953-openai-oauth-llm-provider
- Commit SHA: 9aa390d6
### Validation Run
- [x] `pnpm --filter openhuman-app format:check` (app Prettier + Rust fmt check passed in pre-push hook)
- [x] `pnpm typecheck`
- [x] Focused tests: `pnpm debug unit ApiKeysStep` (7 passed); `CARGO_INCREMENTAL=0 CARGO_TARGET_DIR=$PWD/target cargo test openai_oauth --lib` (26 passed)
- [x] Rust fmt/check (if changed): `cargo fmt --manifest-path Cargo.toml --all` applied; `cargo test openai_oauth --lib` green; workspace clippy run has no `openai_oauth` diagnostics but is blocked by unrelated pre-existing warnings-as-errors outside owned paths
- [x] Coverage: `pnpm test:coverage` passed; local `diff-cover target/frontend-normalized.lcov target/openai-oauth.lcov --compare-branch=origin/main --fail-under=80` passed at 84%.
- [x] Tauri fmt/check (if changed): N/A — no Tauri shell changes
### Validation Blocked
- `command:` `cargo clippy --manifest-path Cargo.toml --workspace --all-targets -- -D warnings`
- `error:` fails with 535 pre-existing clippy warnings-as-errors across unrelated modules; searched the output and found no `openai_oauth` / `src/openhuman/inference/openai_oauth` diagnostics after the fixes.
- `command:` `pnpm test:rust`
- `error:` fails in 40 unrelated memory tree tests because cloud embeddings require a backend session (`No backend session for cloud embeddings`); focused `openai_oauth` Rust tests pass.
- `command:` `git push` pre-push `pnpm rust:check`
- `error:` isolated worktree lacks vendored `app/src-tauri/vendor/tauri-cef`, so Tauri `cargo check --manifest-path app/src-tauri/Cargo.toml` cannot load the vendored `tauri` dependency.
- `impact:` Push used `--no-verify` only for the isolated-worktree Tauri vendor blocker; CI remains authoritative for full Tauri checks.
### Behavior Changes
- Intended behavior change: Users can connect OpenAI via ChatGPT subscription OAuth (Codex) in addition to API keys.
- User-visible effect: Onboarding “API keys” step shows “Sign in with ChatGPT” and connected state; cloud OpenAI inference can use OAuth bearer when no API key is configured.
### Parity Contract
- Legacy behavior preserved: API keys remain primary; existing `provider:openai` key lookup paths unchanged when a key is present.
- Guard/fallback/dispatch parity checks: New controllers registered in `inference/schemas.rs`; factory delegates only for slug `openai`.
### Duplicate / Superseded PR Handling
- Duplicate PR(s): none
- Canonical PR: this PR
- Resolution (closed/superseded/updated): N/A
<!-- This is an auto-generated comment: release notes by coderabbit.ai -->
## Summary by CodeRabbit
* **New Features**
* Desktop app: "Sign in with ChatGPT" OAuth added to the API Keys onboarding step — status polling, open-auth flow, paste-redirect finish, connected indicator, disconnect, and allow advancing when OAuth is connected without an API key.
* **Tests**
* Expanded unit and integration tests covering OAuth start/complete/status/disconnect and many success/failure edge cases.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/tinyhumansai/openhuman/pull/2265?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Co-authored-by: Ghost Scripter <ghostscripter@zerolend.xyz>
Co-authored-by: Steven Enamakel <enamakel@tinyhumans.ai>
226 lines
9.3 KiB
TOML
226 lines
9.3 KiB
TOML
[package]
|
|
name = "openhuman"
|
|
version = "0.54.4"
|
|
edition = "2021"
|
|
description = "OpenHuman core business logic and RPC server"
|
|
autobins = false
|
|
|
|
[[bin]]
|
|
name = "openhuman-core"
|
|
path = "src/main.rs"
|
|
|
|
[[bin]]
|
|
name = "slack-backfill"
|
|
path = "src/bin/slack_backfill.rs"
|
|
|
|
[[bin]]
|
|
name = "gmail-backfill-3d"
|
|
path = "src/bin/gmail_backfill_3d.rs"
|
|
|
|
[[bin]]
|
|
name = "memory-tree-init-smoke"
|
|
path = "src/bin/memory_tree_init_smoke.rs"
|
|
|
|
[[bin]]
|
|
name = "inference-probe"
|
|
path = "src/bin/inference_probe.rs"
|
|
|
|
[lib]
|
|
name = "openhuman_core"
|
|
crate-type = ["rlib"]
|
|
|
|
[dependencies]
|
|
serde = { version = "1", features = ["derive"] }
|
|
serde_json = "1"
|
|
serde_yaml = "0.9"
|
|
# (Removed `html2md` dep. dhat-rs profiling on real Gmail inboxes
|
|
# showed `html2md::walk` and `html2md::tables::handle` allocating
|
|
# ~894 MB peak heap on a 10 KB HTML input from Otter.ai-style emails
|
|
# (deeply-nested table-as-layout HTML). Cause: recursive walker holding
|
|
# per-frame Vec state across nesting layers + 5 sequential
|
|
# `regex::replace_all` passes in `clean_markdown` each producing a
|
|
# fresh full-size String. We now use a linear-time tag-and-entity
|
|
# stripper (`fast_html_to_text` in
|
|
# providers/gmail/post_process.rs) and prefer the email's
|
|
# `text/plain` MIME part when available.)
|
|
reqwest = { version = "0.12", default-features = false, features = ["json", "blocking", "rustls-tls", "native-tls", "stream", "http2", "multipart", "socks"] }
|
|
tokio = { version = "1", features = ["full", "sync"] }
|
|
once_cell = "1.19"
|
|
parking_lot = "0.12"
|
|
log = "0.4"
|
|
nu-ansi-term = "0.46"
|
|
env_logger = "0.11"
|
|
base64 = "0.22"
|
|
aes-gcm = "0.10"
|
|
argon2 = "0.5"
|
|
rand = "0.10"
|
|
dirs = "5"
|
|
sha2 = "0.10"
|
|
hmac = "0.12"
|
|
# Archive extraction for the Node.js runtime bootstrap. Unix Node
|
|
# distributions ship as .tar.xz, Windows as .zip. `xz2` with `static`
|
|
# bundles liblzma so we don't need it as a system dependency.
|
|
tar = "0.4"
|
|
xz2 = { version = "0.1", features = ["static"] }
|
|
zip = { version = "2", default-features = false, features = ["deflate"] }
|
|
# gzip decoder for the Piper tar.gz binary releases on macOS / Linux. Already
|
|
# pulled in transitively by zip's `deflate` feature; declared directly so
|
|
# the installer module can `use flate2::read::GzDecoder`.
|
|
flate2 = "1"
|
|
# Real timeout for `node --version` probes in the runtime resolver. Guards
|
|
# against a broken shim on PATH hanging the bootstrap forever.
|
|
wait-timeout = "0.2"
|
|
uuid = { version = "1", features = ["v4"] }
|
|
anyhow = "1.0"
|
|
async-trait = "0.1"
|
|
chacha20poly1305 = "0.10"
|
|
hex = "0.4"
|
|
tokio-util = { version = "0.7", features = ["rt", "io"] }
|
|
tokio-tungstenite = { version = "0.24", features = ["rustls-tls-webpki-roots"] }
|
|
futures = "0.3"
|
|
rusqlite = { version = "0.37", features = ["bundled"] }
|
|
chrono = { version = "0.4", features = ["serde"] }
|
|
iana-time-zone = "0.1"
|
|
cron = "0.12"
|
|
futures-util = "0.3"
|
|
directories = "6"
|
|
toml = "1.0"
|
|
shellexpand = "3.1"
|
|
schemars = "1.2"
|
|
tracing = { version = "0.1", default-features = false }
|
|
tracing-log = "0.2"
|
|
tracing-subscriber = { version = "0.3", default-features = false, features = ["fmt", "ansi", "env-filter"] }
|
|
tracing-appender = "0.2"
|
|
prometheus = { version = "0.14", default-features = false }
|
|
urlencoding = "2.1"
|
|
motosan-ai-oauth = { version = "0.2", features = ["codex"] }
|
|
thiserror = "2.0"
|
|
ring = "0.17"
|
|
prost = { version = "0.14", default-features = false }
|
|
postgres = { version = "0.19", features = ["with-chrono-0_4"] }
|
|
chrono-tz = "0.10"
|
|
dialoguer = { version = "0.12", features = ["fuzzy-select"] }
|
|
dotenvy = "0.15"
|
|
console = "0.16"
|
|
regex = "1.10"
|
|
walkdir = "2"
|
|
glob = "0.3"
|
|
unicode-segmentation = "1"
|
|
unicode-width = "0.2"
|
|
hostname = "0.4.2"
|
|
rustls = { version = "0.23", features = ["ring"] }
|
|
rustls-pki-types = "1.14.0"
|
|
tokio-rustls = "0.26.4"
|
|
webpki-roots = "1.0.6"
|
|
sysinfo = { version = "0.33", default-features = false, features = ["system"] }
|
|
clap = { version = "4.5", features = ["derive"] }
|
|
clap_complete = "4.5"
|
|
lettre = { version = "0.11.22", default-features = false, features = ["builder", "smtp-transport", "rustls-tls"] }
|
|
mail-parser = "0.11.2"
|
|
async-imap = { version = "0.11", features = ["runtime-tokio"], default-features = false }
|
|
axum = { version = "0.8", default-features = false, features = ["http1", "json", "tokio", "query", "ws", "macros"] }
|
|
tower = { version = "0.5", default-features = false }
|
|
opentelemetry = { version = "0.32", default-features = false, features = ["trace", "metrics"] }
|
|
opentelemetry_sdk = { version = "0.32", default-features = false, features = ["trace", "metrics"] }
|
|
opentelemetry-otlp = { version = "0.32", default-features = false, features = ["trace", "metrics", "http-proto", "reqwest-client", "reqwest-rustls-webpki-roots"] }
|
|
sentry = { version = "0.47.0", default-features = false, features = ["backtrace", "contexts", "panic", "tracing", "debug-images", "reqwest", "rustls"] }
|
|
tokio-stream = { version = "0.1.18", features = ["full"] }
|
|
url = "2"
|
|
socketioxide = { version = "0.15", features = ["extensions"] }
|
|
whisper-rs = "0.16"
|
|
image = { version = "0.25", default-features = false, features = ["png", "jpeg"] }
|
|
tempfile = "3"
|
|
cpal = "0.15"
|
|
hound = "3.5"
|
|
enigo = "0.3"
|
|
arboard = "3"
|
|
rdev = "0.5"
|
|
fs2 = "0.4"
|
|
# Cross-platform battery probe for the scheduler gate. Maintained fork of
|
|
# the abandoned `battery` crate; same `use battery::*;` API surface. Used
|
|
# only by `openhuman::scheduler_gate::signals` to decide when to throttle
|
|
# background LLM work on laptops.
|
|
starship-battery = "0.10"
|
|
ethers-core = { version = "2.0.14", default-features = false }
|
|
ethers-signers = { version = "2.0.14", default-features = false }
|
|
|
|
matrix-sdk = { version = "0.16", optional = true, default-features = false, features = ["e2e-encryption", "rustls-tls", "markdown"] }
|
|
fantoccini = { version = "0.22.0", optional = true, default-features = false, features = ["rustls-tls"] }
|
|
serde-big-array = { version = "0.5", optional = true }
|
|
pdf-extract = { version = "0.10", optional = true }
|
|
# WhatsApp Web — upstream `whatsapp-rust` 0.5. Replaces the previous `wa-rs`
|
|
# 0.2 fork: upstream now ships its own SqliteStore (so we no longer need the
|
|
# 1.3K-line custom RusqliteStore) and dispatches `Event::Message` for
|
|
# LID-addressed contacts and group sender-key (skmsg) messages — both of
|
|
# which the 0.2 fork silently dropped after decryption.
|
|
whatsapp-rust = { version = "0.5", optional = true, default-features = false, features = ["sqlite-storage", "tokio-runtime"] }
|
|
whatsapp-rust-tokio-transport = { version = "0.5", optional = true, default-features = false }
|
|
whatsapp-rust-ureq-http-client = { version = "0.5", optional = true }
|
|
wacore = { version = "0.5", optional = true, default-features = false }
|
|
|
|
[target.'cfg(target_os = "macos")'.dependencies]
|
|
whisper-rs = { version = "0.16", features = ["metal"] }
|
|
# Contacts framework bindings for address book seeding.
|
|
objc2 = "0.6"
|
|
objc2-foundation = { version = "0.3", features = ["NSArray", "NSError", "NSObject", "NSString", "NSPredicate"] }
|
|
objc2-contacts = { version = "0.3.2", features = ["CNContact", "CNContactFetchRequest", "CNContactStore", "CNLabeledValue", "CNPhoneNumber"] }
|
|
block2 = "0.6"
|
|
|
|
[target.'cfg(target_os = "linux")'.dependencies]
|
|
landlock = { version = "0.4", optional = true }
|
|
rppal = { version = "0.22", optional = true }
|
|
|
|
[dev-dependencies]
|
|
# Enable sentry's TestTransport for runtime smoke of the observability
|
|
# before_send filter (see tests/observability_smoke.rs). `default-features
|
|
# = false` here is load-bearing — sentry's default feature set pulls in
|
|
# actix-web / actix-http / actix-server / sentry-actix and ~13 transitive
|
|
# crates we never use (and that bloat the dev Cargo.lock noticeably).
|
|
# TestTransport only needs the `test` feature.
|
|
sentry = { version = "0.47.0", default-features = false, features = ["test"] }
|
|
# Mock HTTP server for provider E2E tests (inference_provider_e2e).
|
|
wiremock = "0.6"
|
|
|
|
[features]
|
|
sandbox-landlock = ["dep:landlock"]
|
|
sandbox-bubblewrap = []
|
|
channel-matrix = ["dep:matrix-sdk"]
|
|
peripheral-rpi = ["dep:rppal"]
|
|
browser-native = ["dep:fantoccini"]
|
|
fantoccini = ["browser-native"]
|
|
landlock = ["sandbox-landlock"]
|
|
rag-pdf = ["dep:pdf-extract"]
|
|
whatsapp-web = ["dep:whatsapp-rust", "dep:whatsapp-rust-tokio-transport", "dep:whatsapp-rust-ureq-http-client", "dep:wacore", "serde-big-array"]
|
|
# Exposes the destructive `openhuman.test_reset` RPC. Off by default; the E2E
|
|
# build (app/scripts/e2e-build.sh) flips it on. Shipped binaries never have
|
|
# this feature so the wipe RPC isn't even registered, let alone reachable.
|
|
e2e-test-support = []
|
|
|
|
# Fix whisper-rs-sys CRT mismatch on Windows MSVC (LNK2038).
|
|
# Upstream cmake build defaults to /MD but Rust uses /MT.
|
|
# This fork adds config.static_crt(true) to the build script.
|
|
# See: https://github.com/tinyhumansai/openhuman/issues/273
|
|
[patch.crates-io]
|
|
whisper-rs-sys = { git = "https://github.com/tinyhumansai/whisper-rs-sys.git", branch = "main" }
|
|
|
|
# Emit just enough DWARF in release builds for Sentry to symbolicate Rust
|
|
# panics + render surrounding source lines. `line-tables-only` keeps the
|
|
# binary small (only file+line tables, no full type info) while still
|
|
# letting `sentry-cli debug-files upload --include-sources` produce a
|
|
# usable `.src.zip`. `split-debuginfo = "packed"` writes the debug data
|
|
# into a separate `.dSYM` bundle on macOS so the shipped executable
|
|
# itself stays slim.
|
|
[profile.release]
|
|
debug = "line-tables-only"
|
|
split-debuginfo = "packed"
|
|
|
|
# Fast CI builds: trade runtime perf for compile speed
|
|
[profile.ci]
|
|
inherits = "release"
|
|
opt-level = 1
|
|
codegen-units = 16
|
|
lto = false
|
|
incremental = false
|
|
strip = true
|
|
debug = false
|