diff --git a/.env.example b/.env.example index 091caf89d2f..da8c0fd7331 100644 --- a/.env.example +++ b/.env.example @@ -91,10 +91,11 @@ OPENHUMAN_MODEL= # [optional] Workspace directory (default: ~/.openhuman or ~/.openhuman-staging when OPENHUMAN_APP_ENV=staging) # Leave commented out to use the default. Setting to empty string is treated as unset. # OPENHUMAN_WORKSPACE= -# [optional] Master key for the `encrypted_file` keyring used in staging/production, -# as 64 hex characters (`openssl rand -hex 32`). Headless/container deploys have no -# OS keychain to hold it; set exactly one of these two, injected from your secret -# manager. Losing the key orphans every secret encrypted under it. +# [required without an OS keychain] Master key for the `encrypted_file` keyring, +# which is the default unless OPENHUMAN_APP_ENV=dev/development or an explicit +# backend override is set. Use 64 hex characters (`openssl rand -hex 32`). +# Headless/container deploys should set exactly one of these two through a +# secret manager. Losing the key orphans every secret encrypted under it. # OPENHUMAN_KEYRING_MASTER_KEY= # OPENHUMAN_KEYRING_MASTER_KEY_FILE=/run/secrets/openhuman_master_key # [optional] Default: 0.7 diff --git a/.github/ci-paths-filter.yml b/.github/ci-paths-filter.yml index 5da78fa8e70..44e4f24a086 100644 --- a/.github/ci-paths-filter.yml +++ b/.github/ci-paths-filter.yml @@ -44,6 +44,20 @@ docs: - 'scripts/generate-architecture-docs.mjs' - 'scripts/__tests__/generate-architecture-docs.test.mjs' - 'gitbooks/developing/architecture/frontend.md' + - 'gitbooks/developing/embed/**' + - 'gitbooks/developing/embedding.md' + - 'gitbooks/SUMMARY.md' + - 'docs/gitbooks/en/developing/embed/**' + - 'docs/gitbooks/en/developing/embedding.md' + - 'docs/gitbooks/en/SUMMARY.md' + - 'crates/openhuman-embed/**' + - 'scripts/generate-embed-docs.mjs' + - 'scripts/run-embed-examples.mjs' + - 'scripts/__tests__/generate-embed-docs.test.mjs' + - 'scripts/__tests__/run-embed-examples.test.mjs' + - 'llms.txt' + - 'llms-full.txt' + - 'EMBED.md' - 'package.json' rust-core: - '.github/workflows/ci-*.yml' diff --git a/.github/workflows/test-reusable.yml b/.github/workflows/test-reusable.yml index 93e9ba90159..ab899bfeebb 100644 --- a/.github/workflows/test-reusable.yml +++ b/.github/workflows/test-reusable.yml @@ -244,6 +244,9 @@ jobs: # The core binary and developer bins live in the CLI crate. bash scripts/ci-cancel-aware.sh cargo test -p openhuman-cli --bins --features "${CLI_FEATURES}" bash scripts/ci-cancel-aware.sh cargo test -p openhuman --doc --features "${FEATURES}" + bash scripts/ci-cancel-aware.sh cargo test -p openhuman-embed --doc --features "${FEATURES}" + RUSTDOCFLAGS="-D warnings" bash scripts/ci-cancel-aware.sh cargo doc -p openhuman-embed --no-deps --features "${FEATURES}" + node scripts/run-embed-examples.mjs while IFS= read -r target; do [ -n "${target}" ] || continue diff --git a/.gitignore b/.gitignore index 4b775633a7b..6595c3ad0b8 100644 --- a/.gitignore +++ b/.gitignore @@ -156,6 +156,3 @@ diff-coverage.md ci-out/ ci-out-ex63/ -# Cargo build output inside the in-tree motosan-ai-oauth crate -vendor/motosan-ai-oauth/target/ - diff --git a/AGENTS.md b/AGENTS.md index e2ae8e23466..948fc2e7359 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -405,6 +405,12 @@ Additional rules: ## Tool, harness, and runtime boundaries +Embed public API documentation: [Embedding](gitbooks/developing/embed/README.md), +[concepts](gitbooks/developing/embed/concepts/README.md), and the source-generated +[builder setters](gitbooks/developing/embed/builder-setters.md). Snippets come from +compiled examples; run `pnpm docs:generate` and `pnpm docs:check` after changing them. + + `tinyagents` owns tool-call dialects, parsing, catalog rendering, transcript replay, session identity, and the agent loop. `tinytools` owns the shared `Tool` trait and tool types. OpenHuman owns execution policy, approvals, @@ -553,7 +559,6 @@ Direct rendered submodules under `vendor/`: | `tinyskills` | Host-independent skill/workflow bundle parsing, discovery, scope resolution, resource inventory, and safe reads. OpenHuman owns trust and execution policy. | | `tinyvoice` | Host-agnostic voice primitives such as audio framing, VAD, wake-word gating, routing, and STT hallucination detection. | | `tinywallet` | Multi-chain wallet: `tinywallet-crypto` (address, asset, chain, `rpc::Transport`, tx codec), `tinywallet-x402` (x402 wire, payment, spending ledger, `x402_request` tool), `tinywallet-web3` (wallet engine, per-chain build/sign/broadcast flows, swap/bridge/dapp quotes, agent tools) behind host seams (`WalletSigner`, `PaymentSigner`, `WalletAccounts`, `RpcEndpoints`, `QuoteScope`, `Web3Backend`, `ProxyPolicy`), and the loadable `tinywallet-module` that derives keys and signs. OpenHuman keeps keyring, consent, credentials, config, controllers and the seam impls under `web3/`. | -| `motosan-ai-oauth` | Provider-agnostic PKCE OAuth login and token-refresh primitives. | Some rendered submodules are shared dependencies nested inside those projects, not separate OpenHuman feature implementations. Make changes to them in their diff --git a/Cargo.lock b/Cargo.lock index aa91c7be64f..774291f2032 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,7 +173,7 @@ version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40c48f72fd53cd289104fc64099abca73db4166ad86ea0b4341abe65af83dadc" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -184,7 +184,7 @@ checksum = "291e6a250ff86cd4a820112fb8898808a366d8f9f58ce16d1f538353ad55747d" dependencies = [ "anstyle", "once_cell_polyfill", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -287,19 +287,19 @@ dependencies = [ [[package]] name = "async-imap" -version = "0.11.3" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a6728e0f7931b36d725ac234fcb02539e9f7888dbeaaa8a18d9ea5792181570" +checksum = "f97b87216c9f0ccc63c516263169085fa34bba25633f6d96c6a9b9ca1fd5f70a" dependencies = [ "async-channel 2.5.0", "async-compression", - "base64 0.22.1", + "base64 0.23.1", "bytes", "chrono", - "futures", + "futures-util", "imap-proto", "log", - "nom 7.1.3", + "nom 8.0.0", "pin-project", "pin-utils", "self_cell", @@ -788,7 +788,7 @@ dependencies = [ "cap-primitives", "cap-std", "io-lifetimes 3.0.1", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -805,7 +805,7 @@ dependencies = [ "maybe-owned", "rustix", "rustix-linux-procfs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", "winx", ] @@ -1849,7 +1849,7 @@ dependencies = [ "libc", "option-ext", "redox_users", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2099,7 +2099,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -2873,6 +2873,7 @@ dependencies = [ "hyper", "hyper-util", "rustls", + "rustls-native-certs", "tokio", "tokio-rustls", "tower-service", @@ -2930,7 +2931,7 @@ dependencies = [ "js-sys", "log", "wasm-bindgen", - "windows-core 0.58.0", + "windows-core 0.62.2", ] [[package]] @@ -3054,11 +3055,11 @@ dependencies = [ [[package]] name = "imap-proto" -version = "0.16.7" +version = "0.17.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25f6af35c6a517aea5c72314abe90134980d2ae6a763809b50c208b3e429d71f" +checksum = "5ccf963d57074747b455398a1763d174da80bcaba6f51e3671a82252b531a68b" dependencies = [ - "nom 7.1.3", + "nom 8.0.0", ] [[package]] @@ -4012,7 +4013,7 @@ version = "0.50.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7957b9740744892f114936ab4a57b3f487491bbeafaf8083688b16841a4240e5" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -4508,7 +4509,8 @@ dependencies = [ "proptest", "rand 0.10.2", "regex", - "reqwest", + "reqwest 0.12.28", + "reqwest 0.13.5", "rusqlite", "rustls", "schemars", @@ -4592,6 +4594,7 @@ dependencies = [ "urlencoding", "uuid", "walkdir", + "webpki-roots 1.0.9", "windows-sys 0.61.2", "wiremock", "x25519-dalek 2.0.1", @@ -4623,7 +4626,7 @@ dependencies = [ "openhuman-rpc", "openhuman-tinyhumans", "parking_lot", - "reqwest", + "reqwest 0.12.28", "rusqlite", "sentry", "serde", @@ -4668,9 +4671,10 @@ dependencies = [ "chrono", "dirs 6.0.0", "env_logger", + "futures-core", "log", "openhuman", - "reqwest", + "reqwest 0.12.28", "sentry", "serde", "serde_json", @@ -4684,6 +4688,7 @@ dependencies = [ "tinymemory-tools", "tinytools", "tokio", + "tokio-stream", "tracing-subscriber", "url", "uuid", @@ -4704,7 +4709,7 @@ dependencies = [ "openhuman-tinyhumans", "proptest", "rand 0.10.2", - "reqwest", + "reqwest 0.12.28", "sentry", "serde", "serde_json", @@ -4736,7 +4741,7 @@ dependencies = [ "chrono", "log", "openhuman-embed", - "reqwest", + "reqwest 0.12.28", "sentry", "serde", "serde_json", @@ -5390,7 +5395,7 @@ dependencies = [ "once_cell", "socket2", "tracing", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5745,6 +5750,7 @@ dependencies = [ "pin-project-lite", "quinn", "rustls", + "rustls-native-certs", "rustls-pki-types", "serde", "serde_json", @@ -5765,6 +5771,44 @@ dependencies = [ "webpki-roots 1.0.9", ] +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "h2", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-tls", + "hyper-util", + "js-sys", + "log", + "native-tls", + "percent-encoding", + "pin-project-lite", + "rustls", + "rustls-pki-types", + "rustls-platform-verifier", + "sync_wrapper", + "tokio", + "tokio-native-tls", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "resolv-conf" version = "0.7.6" @@ -5879,7 +5923,7 @@ dependencies = [ "errno", "libc", "linux-raw-sys", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -5947,7 +5991,7 @@ dependencies = [ "security-framework 3.7.0", "security-framework-sys", "webpki-root-certs", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6527,7 +6571,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" dependencies = [ "libc", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6838,7 +6882,7 @@ dependencies = [ "getrandom 0.4.3", "once_cell", "rustix", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -6851,7 +6895,7 @@ dependencies = [ "parking_lot", "rustix", "signal-hook", - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] @@ -7049,7 +7093,7 @@ dependencies = [ "flate2", "futures", "regex", - "reqwest", + "reqwest 0.12.28", "rusqlite", "rustix", "serde", @@ -7245,7 +7289,7 @@ dependencies = [ [[package]] name = "tinychannels" -version = "0.1.12" +version = "0.1.13" dependencies = [ "anyhow", "async-imap", @@ -7263,7 +7307,7 @@ dependencies = [ "parking_lot", "prost", "rand 0.10.2", - "reqwest", + "reqwest 0.12.28", "rusqlite", "rustls", "rustls-pki-types", @@ -7291,7 +7335,7 @@ dependencies = [ [[package]] name = "tinychannels-bus" -version = "0.1.12" +version = "0.1.13" dependencies = [ "anyhow", "async-trait", @@ -7312,7 +7356,7 @@ dependencies = [ [[package]] name = "tinychannels-runtime" -version = "0.1.12" +version = "0.1.13" dependencies = [ "anyhow", "async-trait", @@ -7491,7 +7535,7 @@ dependencies = [ "base64 0.23.1", "hex", "percent-encoding", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha1 0.11.0", @@ -7507,7 +7551,7 @@ dependencies = [ "base64 0.22.1", "futures", "percent-encoding", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7531,7 +7575,7 @@ version = "0.3.1" dependencies = [ "async-trait", "httpdate", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7545,7 +7589,7 @@ dependencies = [ "async-trait", "once_cell", "regex", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7562,7 +7606,7 @@ dependencies = [ "async-trait", "base64 0.23.1", "bytes", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7579,7 +7623,7 @@ dependencies = [ "base64 0.22.1", "bytes", "futures", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7598,7 +7642,7 @@ dependencies = [ "log", "once_cell", "parking_lot", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7616,7 +7660,7 @@ version = "0.3.1" dependencies = [ "base64 0.23.1", "rand 0.10.2", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "sha2 0.11.0", @@ -7641,7 +7685,7 @@ name = "tinyinference-voice" version = "0.3.1" dependencies = [ "base64 0.23.1", - "reqwest", + "reqwest 0.12.28", "schemars", "serde", "serde_json", @@ -7657,7 +7701,7 @@ version = "0.2.1" source = "git+https://github.com/tinyhumansai/tinyjevclient?rev=84b3983c7e1e14f7515658ceafabb6c4e7967c94#84b3983c7e1e14f7515658ceafabb6c4e7967c94" dependencies = [ "httpdate", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7706,7 +7750,7 @@ dependencies = [ "base64 0.22.1", "bytes", "futures-util", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7727,7 +7771,7 @@ dependencies = [ "dirs 7.0.0", "futures-util", "parking_lot", - "reqwest", + "reqwest 0.12.28", "rusqlite", "serde", "serde_json", @@ -7758,7 +7802,7 @@ version = "0.1.0" dependencies = [ "async-trait", "futures", - "reqwest", + "reqwest 0.12.28", "serde", "serde_json", "thiserror 2.0.20", @@ -7769,7 +7813,7 @@ dependencies = [ [[package]] name = "tinymemory-api" -version = "1.27.1" +version = "1.27.2" dependencies = [ "async-trait", "chrono", @@ -7782,7 +7826,7 @@ dependencies = [ [[package]] name = "tinymemory-integrations" -version = "1.27.1" +version = "1.27.2" dependencies = [ "async-trait", "calamine", @@ -7792,7 +7836,7 @@ dependencies = [ "pdf-extract", "quick-xml 0.41.0", "regex", - "reqwest", + "reqwest 0.12.28", "rusqlite", "schemars", "serde", @@ -7809,7 +7853,7 @@ dependencies = [ [[package]] name = "tinymemory-tools" -version = "1.27.1" +version = "1.27.2" dependencies = [ "chrono", "futures", @@ -7986,7 +8030,7 @@ dependencies = [ "log", "parking_lot", "regex", - "reqwest", + "reqwest 0.12.28", "rustix", "serde_json", "sha2 0.11.0", @@ -8025,7 +8069,7 @@ dependencies = [ [[package]] name = "tinywallet" -version = "0.7.4" +version = "0.8.0" dependencies = [ "coins-bip32", "coins-bip39", @@ -8043,7 +8087,7 @@ dependencies = [ [[package]] name = "tinywallet-bus" -version = "0.7.4" +version = "0.8.0" dependencies = [ "serde", "tinywallet-crypto", @@ -8052,7 +8096,7 @@ dependencies = [ [[package]] name = "tinywallet-crypto" -version = "0.7.4" +version = "0.8.0" dependencies = [ "async-trait", "bech32 0.12.0", @@ -8067,13 +8111,13 @@ dependencies = [ [[package]] name = "tinywallet-web3" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", - "base64 0.22.1", + "base64 0.23.1", "bs58", - "curve25519-dalek 4.1.3", + "curve25519-dalek 5.0.0", "hex", "log", "parking_lot", @@ -8088,17 +8132,17 @@ dependencies = [ [[package]] name = "tinywallet-x402" -version = "0.7.4" +version = "0.8.0" dependencies = [ "anyhow", "async-trait", - "base64 0.22.1", + "base64 0.23.1", "bs58", "chrono", - "curve25519-dalek 4.1.3", + "curve25519-dalek 5.0.0", "hex", "log", - "reqwest", + "reqwest 0.13.5", "serde", "serde_json", "sha2 0.11.0", @@ -9276,7 +9320,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.60.2", + "windows-sys 0.61.2", ] [[package]] diff --git a/EMBED.md b/EMBED.md new file mode 100644 index 00000000000..80c81766244 --- /dev/null +++ b/EMBED.md @@ -0,0 +1,54 @@ +# OpenHuman Embed + +> Embed OpenHuman runtimes and independently configured agents in Rust applications. + +- [Embedding OpenHuman](gitbooks/developing/embed/README.md): `openhuman-embed` runs the OpenHuman core inside your Rust program. Your host owns the user interface, server transport and authentication flow; the core owns agent turns, memory, tools, approvals and execution policy. Calling its typed facades does not require a JSON-RPC server or the desktop shell. +- [Embedding API reference](gitbooks/developing/embed/api-reference.md): Generate usable local Rust API documentation with `cargo doc -p openhuman-embed --no-deps --open`. Include `--features mcp,skills,channels,storage-sqlite` for the facade gates used by the offline examples, or select the feature set your host actually builds. For all exported optional surfaces use `--all-features`. +- [Embedding architecture](gitbooks/developing/embed/architecture.md): Embed is the library facade above `openhuman-core`. The TinyHumans layer installs the backend SDK transport and owns product login/session behavior; the RPC layer adds JSON-RPC, server listeners and shipped host boot. A Rust embedder can stay at the facade when it owns those concerns itself. +- [Concepts](gitbooks/developing/embed/concepts/README.md): Start with Runtime and agents to understand which settings are shared. Then follow the lifecycle of a request through provider selection, tools, approvals and its durable session. The scope tables distinguish runtime composition from the state a host must isolate per agent or profile. +- [Access and approvals](gitbooks/developing/embed/concepts/access-approvals.md): `Access` groups permission level, origin, standing grants and trusted roots. Choose an origin and tier together: a trusted local host request and an external channel message are different security contexts. `Access::full()` configures both access fields. A sandbox is an additional execution boundary, configured through `AgentDefinitionSpec`. +- [Agents](gitbooks/developing/embed/concepts/agents.md): Register an `AgentSpec` with `Runtime::agent`. Its ID selects the agent's home, durable session identity and addressed cron/channel work. An `Agent` clone refers to the same instance; it does not copy state or start another core. Runtime-wide credentials remain shared, so two agents are not two authenticated customers. +- [Channels](gitbooks/developing/embed/concepts/channels.md): `Runtime::channels` creates listeners addressed to a live agent ID. A listener owns its transport task; dropping or stopping it ends that listener. The agent supplies the prompt, provider and tools used for every incoming message instead of handing messages to an operator agent. +- [Completer](gitbooks/developing/embed/concepts/completer.md): Use `Completer` for one-shot text or structured generation when you do not need an agent, its tool loop or persistent transcript. It takes an explicit route and a `CompletionRequest`; the caller supplies the model and messages for every request. +- [Cron](gitbooks/developing/embed/concepts/cron.md): The typed `Cron` facade creates named jobs with `JobSpec` and `JobSchedule`. `upsert` preserves a job's identity when the same name is configured again; `runs` exposes execution history. An agent-targeted job resolves the configured agent ID and executes with that agent's provider, prompt, tools and derived context. +- [Errors](gitbooks/developing/embed/concepts/errors.md): Match the error type at its boundary: `RuntimeError` describes boot/configuration, `AgentError` describes registration/layout, and `CoreError` describes typed facade calls and turns. A missing compiled or selected domain is `CoreError::Unavailable`; it is a capability decision the host can reflect in its UI. +- [Hooks and seams](gitbooks/developing/embed/concepts/hooks-seams.md): Runtime builders accept host-owned ports for storage, sessions, memory, tools and controller extensions. These are composition seams: the host supplies an implementation of the owning library's contract, while OpenHuman retains access policy, approvals and lifecycle. +- [MCP](gitbooks/developing/embed/concepts/mcp.md): MCP connects a native agent turn to tools served over an MCP protocol transport. Configure a server on `AgentSpec::mcp`, with its transport, authentication and allowed tool names. Another agent on the same runtime does not inherit that server configuration. +- [Memory](gitbooks/developing/embed/concepts/memory.md): `Runtime::memory(namespace)` returns a facade bound to that namespace. The caller establishes scope before sending record data; namespace identity is not taken from model arguments or record metadata. Learning and deletion use the selected memory engine contract. +- [Observability](gitbooks/developing/embed/concepts/observability.md): Subscribe with `Runtime::events` before starting work you want to observe. Each event has a sequence number and runtime identity, with an agent ID and per-call turn ID where applicable. Repeated calls on the same durable session receive different turn IDs; the session ID is not the observation ID. +- [Profiles and SaaS](gitbooks/developing/embed/concepts/profiles-saas.md): Ordinary runtime agents share an operator's configuration path and credentials. Use `ProfileRuntime` when one server serves different authenticated users who must not share those resources. Provision a profile, install its credential, and retain a `ProfileHandle` while serving that user's requests. +- [Providers](gitbooks/developing/embed/concepts/providers.md): A runtime provider is the default inference choice for its agents. An agent can override it with an OpenAI-compatible route or a native `ChatModel<()>` through `Provider::custom`. The `providers` facade exposes the request/response contracts so custom adapters do not need to implement an HTTP server. +- [Runtime defaults](gitbooks/developing/embed/concepts/runtime-defaults.md): The runtime's default provider, access policy and `ModelDefaults` reduce repeated setup. `AgentSpec` can override those defaults for a reviewer, a coding agent or another workload without changing its siblings. Temperature and token limits are forwarded to the selected native model request, rather than being merely descriptive builder values. +- [Runtime](gitbooks/developing/embed/concepts/runtime.md): A `Runtime` boots the in-process core once. It owns the selected domains, background services, module host and runtime defaults. Its agents share that core while using separately derived contexts. Build one runtime and pass an `Arc` to the parts of your application that register agents. +- [Skills](gitbooks/developing/embed/concepts/skills.md): A skill bundle contains `SKILL.md` plus any referenced resources. `AgentSpec::skills_dir` copies bundles into the agent's `agents//skills/` tree. Copying is intentional: discovery rejects symlinked bundles, so linking a shared directory does not install it. +- [Testing](gitbooks/developing/embed/concepts/testing.md): The runnable examples default to loopback fixtures. They assert request bodies, tool results, file effects, transcript scope and event ordering; a successful Rust compilation alone does not prove those behaviors. Live example execution is explicitly opt-in through the documented environment variables. +- [Tools](gitbooks/developing/embed/concepts/tools.md): An agent's `ToolScopeSpec` chooses built-ins, named tools or `HostOnly`. Host-only mode starts with the host's advertised tools, requires an explicit bare prompt, and does not inherit an orchestrator tool catalog. This is useful when your application already owns read-only data access or tightly scoped actions. +- [Turns and sessions](gitbooks/developing/embed/concepts/turns-sessions.md): `Agent::run` collects a turn. `Agent::turn` lets you set an explicit session, per-turn options, progress sink and cancellation before sending. Reuse the returned session identity for follow-up messages that should see the same transcript. Two calls on the same durable session remain distinct turn executions and receive distinct observation IDs. +- [Embedding FAQ](gitbooks/developing/embed/faq.md): An ordinary embed process supports one `Runtime`; a second build returns `AlreadyRunning`. Add agents to the existing runtime. A multi-user host uses `ProfileRuntime` instead and needs a process dedicated to that mode. See runtime and profiles. +- [Embedding guides](gitbooks/developing/embed/guides/README.md): Choose the guide that matches your host: +- [CLI chatbot](gitbooks/developing/embed/guides/cli-chatbot.md): Keep one `Runtime` alive for the process and create an `Agent` for the conversation. The executable example starts with a single message, checks the reply, and prints it. A CLI input loop can send subsequent messages through the same agent; choose a stable thread identity when your host exposes separate conversations. +- [Deploy a server](gitbooks/developing/embed/guides/deploy-server.md): Your HTTP host can keep a runtime alive and invoke its agents directly. The executable example binds a loopback listener, handles one request, and sends back the agent's reply. It exercises the request path without requiring an RPC server dependency in `openhuman-embed`. +- [Lean and headless hosts](gitbooks/developing/embed/guides/lean-headless.md): `RuntimeBuilder::lean()` selects the agent, memory, and inference families without background services. Runtime module selection controls what is active; Cargo features control what is compiled. A lean preset alone does not shed compiled dependencies. +- [Test with a mock backend](gitbooks/developing/embed/guides/mock-backend-testing.md): Run `node scripts/run-embed-examples.mjs` from the repository root. The runner builds and executes every example with the union of required features, clears inherited live-example credentials and operator workspace settings, and requires each program's `EXAMPLE_OK` marker after its assertions. +- [Multiple agents](gitbooks/developing/embed/guides/multi-agent.md): A runtime owns shared services and credentials. Each `AgentSpec` selects its prompt, model route, access policy, and `action_dir`. Internal agent state belongs to its agent home; the action directory is where acting tools operate. +- [SaaS with multiple tenants](gitbooks/developing/embed/guides/saas-multi-tenant.md): Use `ProfileRuntime` for a process serving multiple users. Provision each profile, install that profile's credential, and open a handle to its workspace. Reusing a thread id in two profiles does not share their conversation history. +- [Installation](gitbooks/developing/embed/installation.md): `openhuman-embed` is currently an unpublished workspace crate. Use a local path dependency when developing in this repository, or a Git dependency on `https://github.com/tinyhumansai/openhuman` with `package = "openhuman-embed"`. Pin a reviewed Git revision for a deployed application; the nested vendored libraries are part of that revision's build inputs. +- [Embedding integrations](gitbooks/developing/embed/integrations/README.md): An embedded runtime composes the capabilities its host supplies. Start with the adapter your application needs: +- [Channels](gitbooks/developing/embed/integrations/channels.md): Enable the `channels` Cargo feature to use channel adapters. Your host configures the channel credential and binds incoming messages to an agent. In the Telegram example, the Bot API endpoint is a loopback stub, so updates and replies exercise the adapter without contacting Telegram. +- [Composio](gitbooks/developing/embed/integrations/composio.md): Composio calls use the host backend transport. Install that transport before booting a managed runtime; see TinyHumans managed inference. An explicit inference provider route supplies model inference, not the connector transport. +- [Local and custom models](gitbooks/developing/embed/integrations/local-models.md): For a local server that implements chat completions, use `Provider::openai_compatible` with its API root and model id. The constructor requires a bearer string even when a particular local server ignores it. Set the endpoint explicitly rather than relying on another user's installed configuration. The OpenAI-compatible example controls can point a live example at that local server. +- [MCP servers](gitbooks/developing/embed/integrations/mcp.md): Enable the `mcp` Cargo feature and declare a server with `AgentSpec::mcp`. `McpServer::http` takes a registered name and endpoint; `allow_tools` narrows the remote tool list. Server configuration and its connections belong to the agent that declared them. +- [OpenAI-compatible providers and BYOK](gitbooks/developing/embed/integrations/openai-compatible.md): Construct a route with `Provider::openai_compatible(base_url, api_key).model(model_id)`, then set it on the runtime or an individual agent. Pass the API root, such as a provider's `/v1` URL; the adapter appends `/chat/completions`. Supply an explicit model id so the route can register that model for its workload roles. +- [Storage drivers](gitbooks/developing/embed/integrations/storage-drivers.md): Enable `storage-sqlite` for SQLite or `storage-mongodb` for MongoDB on your Embed dependency. These Cargo gates make the driver available; choosing a URL selects the runtime backend. Set `RuntimeConfig.storage.url`, pass a URL through `RuntimeBuilder::storage`, or set the `url` field in the `[storage]` section of the host's configuration. `OPENHUMAN_STORAGE_URL` is the environment override used by host configuration resolution. +- [TinyHumans managed inference](gitbooks/developing/embed/integrations/tinyhumans-managed.md): Embed wraps the core; it does not contain a TinyHumans SDK backend client. A managed host must supply a `BackendTransport` through `RuntimeBuilder::backend_transport`, or boot through the SDK-backed builder in `openhuman-tinyhumans`. The connected host layer owns transport and session login. `RuntimeBuilder::backend_url` selects a URL; it does not install a transport. +- [Quickstart](gitbooks/developing/embed/quickstart.md): Start from a checkout of the repository and run `cargo run -p openhuman-embed --example run_turn`. The example boots an ephemeral runtime, creates its own local provider/backend fixtures and prints `hello from the stub`. It needs no inference account and does not contact a real backend. +- [Embedding troubleshooting](gitbooks/developing/embed/troubleshooting.md): Use the typed error and the effective capability report together. A method that was compiled out is a different problem from a configured method with a provider failure. Keep credentials, user prompts and raw provider payloads out of routine diagnostics. +- [OpenHuman Embed](crates/openhuman-embed/README.md): A typed Rust facade for running the OpenHuman core inside your application: one Runtime per process, with independently configured Agents. +- [Embedding OpenHuman](gitbooks/developing/embedding.md): The embedding documentation now lives in the Embedding section. +- [Cookbook](gitbooks/developing/embed/cookbook.md): Executable recipes are indexed from their source headers. Each entry records the declared run mode and any named Embed feature required by the example. Full source includes the surrounding setup and assertions. +- [Compiled feature matrix](gitbooks/developing/embed/capability-matrix.md): This matrix comes from the default-feature compiled capability-report example. Runtime presets can narrow enabled domains and services; core Cargo features determine which implementations are available. Named Embed features also gate facade exports: enable `mcp` or `skills` explicitly for their public setters even when core defaults compile those implementations. Embed defaults enable the named `channels` feature. See the installation guide for these separate layers. +- [RuntimeBuilder setters](gitbooks/developing/embed/builder-setters.md): Public consuming methods that return `Self`, extracted from the RuntimeBuilder implementations. Static host and weight presets, inspection methods and build/run methods are excluded. +- [API index](gitbooks/developing/embed/api-index.md): Generate the complete local Rust API reference with `cargo doc -p openhuman-embed --all-features --no-deps --open`. This crate is currently unpublished; docs.rs is a future publication target. Names below are source-listed exports, including conditional Cargo-gated items; local rustdoc with your selected features describes availability. Canonical facade source. +- [Embed examples](crates/openhuman-embed/examples/README.md): Cookbook + +See the generated [API index](gitbooks/developing/embed/api-index.md) for build capabilities. diff --git a/app/src/components/InitProgressScreen/InitProgressScreen.test.tsx b/app/src/components/InitProgressScreen/InitProgressScreen.test.tsx index 24168698b8d..fa1abd7151c 100644 --- a/app/src/components/InitProgressScreen/InitProgressScreen.test.tsx +++ b/app/src/components/InitProgressScreen/InitProgressScreen.test.tsx @@ -65,6 +65,8 @@ describe('InitProgressScreen', () => { ); + expect(screen.getByTestId('harness-init-background')).toBeInTheDocument(); + expect(screen.queryByTestId('harness-init-continue-anyway')).not.toBeInTheDocument(); fireEvent.click(screen.getByText('Run in background')); expect(onContinue).toHaveBeenCalledTimes(1); }); @@ -92,6 +94,8 @@ describe('InitProgressScreen', () => { ); expect(screen.getByText('pip install timed out')).toBeInTheDocument(); + expect(screen.getByTestId('harness-init-continue-anyway')).toBeInTheDocument(); + expect(screen.queryByTestId('harness-init-background')).not.toBeInTheDocument(); fireEvent.click(screen.getByText('Retry')); expect(onRetry).toHaveBeenCalledTimes(1); diff --git a/app/src/components/InitProgressScreen/InitProgressScreen.tsx b/app/src/components/InitProgressScreen/InitProgressScreen.tsx index 26bc1cea8ee..f9aed711db0 100644 --- a/app/src/components/InitProgressScreen/InitProgressScreen.tsx +++ b/app/src/components/InitProgressScreen/InitProgressScreen.tsx @@ -82,6 +82,7 @@ export default function InitProgressScreen({
@@ -101,6 +102,7 @@ export default function InitProgressScreen({

{t('harnessInit.backgroundHint')}

@@ -72,10 +72,10 @@ export function ConnectionState({ {phase === 'reconnecting' && ( <> - + {reconnectingLabel} {attempt !== undefined && ( - + {attemptLabel(attempt)} )} @@ -87,7 +87,7 @@ export function ConnectionState({ {resumedLabel} {resumedTokens !== undefined && ( - + {resumedTokensLabel(resumedTokens)} )} diff --git a/app/src/components/assistant-ui/elements/context-breakdown.tsx b/app/src/components/assistant-ui/elements/context-breakdown.tsx index 980e78cfda9..baa169bbfbb 100644 --- a/app/src/components/assistant-ui/elements/context-breakdown.tsx +++ b/app/src/components/assistant-ui/elements/context-breakdown.tsx @@ -17,6 +17,7 @@ import { cn } from '@/components/assistant-ui/lib/utils'; import type { ComponentProps } from 'react'; import { announced, pct } from '../utils/range'; +import { formatTokenCount } from './context-display'; import { mono, paper } from './surfaces'; const fmt = (n: number) => n.toLocaleString('en-US'); @@ -59,7 +60,7 @@ export function ContextBreakdown({ return (
{title} @@ -67,13 +68,13 @@ export function ContextBreakdown({ className={cn( mono, 'tabular-nums', - pressure > 0.85 ? 'text-amber-600 dark:text-amber-400' : 'text-foreground/35' + pressure > 0.85 ? 'text-amber-600 dark:text-amber-400' : 'text-muted-foreground' )}> - {fmt(used)} / {fmt(limit)} + {formatTokenCount(used)} / {formatTokenCount(limit)}
-
+
{segments.map(segment => { const width = share(segment.tokens); if (announced(width) === 0) return null; @@ -87,7 +88,7 @@ export function ContextBreakdown({ aria-valuenow={announced(width)} aria-valuetext={meterValueText(fmt(segment.tokens), fmt(limit))} className={cn( - 'h-full transition-[width] duration-500 ease-out motion-reduce:transition-none', + 'h-full transition-[width] duration-500 ease-out forced-color-adjust-none motion-reduce:transition-none', segment.tint )} style={{ width: `${width}%` }} @@ -99,29 +100,35 @@ export function ContextBreakdown({
{segments.map(segment => (
- - + + {segment.label} - + {fmt(segment.tokens)}
))}
- - + + {headroomLabel} - + {fmt(Math.max(0, limit - used))}
{stats.length > 0 &&
} {stats.map(stat => (
- {stat.label} - + {stat.label} + {stat.value}
diff --git a/app/src/components/assistant-ui/elements/context-display.tsx b/app/src/components/assistant-ui/elements/context-display.tsx index dff0b017bee..d06d34a3b6d 100644 --- a/app/src/components/assistant-ui/elements/context-display.tsx +++ b/app/src/components/assistant-ui/elements/context-display.tsx @@ -44,8 +44,8 @@ export type TokenUsage = { reasoningTokens?: number | undefined; }; -const formatTokenCount = (tokens: number): string => { - if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1).replace(/\.0$/, '')}M`; +export const formatTokenCount = (tokens: number): string => { + if (tokens >= 1_000_000) return `${(tokens / 1_000_000).toFixed(1)}mn`; if (tokens >= 1_000) return `${(tokens / 1_000).toFixed(1).replace(/\.0$/, '')}k`; return `${tokens}`; }; @@ -123,6 +123,7 @@ export type PresetProps = Omit, 'children' | 'className usage?: TokenUsage | undefined; resetKey?: string | undefined; labels?: ContextDisplayLabels | undefined; + showTooltip?: boolean; }; export type ContextDisplayRootProps = { @@ -189,20 +190,22 @@ function ContextDisplayRoot({ ); } -function ContextDisplayTrigger({ className, children, ...props }: React.ComponentProps<'button'>) { - return ( - - }> +function ContextDisplayTrigger({ + className, + children, + showTooltip = true, + ...props +}: React.ComponentProps<'button'> & { showTooltip?: boolean }) { + const trigger = ( + ); + return showTooltip ? : trigger; } type ContextSegment = { label: string; tokens: number }; @@ -254,10 +257,10 @@ function ContextDisplayContent({ {formatTokenCount(modelContextWindow)}
-
+
0 && 'min-w-1', getBarColor(percent) )} @@ -331,6 +334,7 @@ const ContextDisplayRing: FC = ({ usage, resetKey, labels, + showTooltip = true, ...triggerProps }) => { const { t } = useT(); @@ -341,6 +345,7 @@ const ContextDisplayRing: FC = ({ resetKey={resetKey} labels={labels}> = ({ - + {showTooltip && } ); }; diff --git a/app/src/components/assistant-ui/elements/conversation-map.tsx b/app/src/components/assistant-ui/elements/conversation-map.tsx index 50513c2aede..d283f9b53c5 100644 --- a/app/src/components/assistant-ui/elements/conversation-map.tsx +++ b/app/src/components/assistant-ui/elements/conversation-map.tsx @@ -142,7 +142,7 @@ export function ConversationMap({ )}>

{previewEntry.title}

{previewEntry.preview && ( -

+

{previewEntry.preview}

)} diff --git a/app/src/components/assistant-ui/elements/conversation-search.tsx b/app/src/components/assistant-ui/elements/conversation-search.tsx index 9eadabec47a..77ffd86018f 100644 --- a/app/src/components/assistant-ui/elements/conversation-search.tsx +++ b/app/src/components/assistant-ui/elements/conversation-search.tsx @@ -51,21 +51,18 @@ export function ConversationSearch({ const active = index === -1 ? undefined : hits[index]; return ( -
+
- + onQueryChange?.(event.target.value)} placeholder={placeholder} aria-label={placeholder} - className="text-foreground/85 placeholder:text-foreground/30 min-w-0 flex-1 bg-transparent text-[13px] outline-none" + className="text-foreground/85 placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent text-[13px] outline-none" /> - + {hits.length === 0 ? '0' : `${index + 1}/${hits.length}`} {onStep && ( @@ -94,23 +91,25 @@ export function ConversationSearch({ field, 'fade-in animate-in rounded-xl px-3 py-2 text-xs leading-relaxed duration-200' )}> - {active.before} + {active.before} {active.match} - {active.after} + {active.after}
)}
-
+
{hits.map((hit, i) => ( diff --git a/app/src/components/assistant-ui/elements/data-table.tsx b/app/src/components/assistant-ui/elements/data-table.tsx index 13afcb2c2b1..6bdeebc7ffc 100644 --- a/app/src/components/assistant-ui/elements/data-table.tsx +++ b/app/src/components/assistant-ui/elements/data-table.tsx @@ -57,7 +57,7 @@ export function DataTable({ return (
{columns.map(column => ( @@ -65,7 +65,7 @@ export function DataTable({ key={column.key} className={cn( mono, - 'text-foreground/35', + 'text-muted-foreground', column.align === 'end' ? 'w-16 text-end' : 'flex-1' )}> {column.header} @@ -87,7 +87,7 @@ export function DataTable({ key={key} className="fade-in slide-in-from-bottom-1 animate-in fill-mode-both hover:bg-foreground/[0.03] flex items-center gap-2.5 px-4 py-2.5 transition-colors duration-300" style={{ animationDelay: `${index * 80}ms` }}> - + {avatarLetter} {columns.map(column => ( @@ -96,7 +96,7 @@ export function DataTable({ className={cn( column === avatarColumn ? 'text-foreground/90 truncate' - : cn(mono, 'text-foreground/55 tabular-nums'), + : cn(mono, 'text-muted-foreground tabular-nums'), column.align === 'end' ? 'w-16 text-end' : 'flex-1' )}> {column.cell(row, index)} diff --git a/app/src/components/assistant-ui/elements/edit-message.tsx b/app/src/components/assistant-ui/elements/edit-message.tsx index d2862987e08..511fab0736f 100644 --- a/app/src/components/assistant-ui/elements/edit-message.tsx +++ b/app/src/components/assistant-ui/elements/edit-message.tsx @@ -60,10 +60,7 @@ export function EditMessage({ }) { if (!editing) { return ( -
+