praisonai/mobile is a purpose-built entry point that bundles only the webview-safe surface of the SDK, so phones and browsers pull ~77 kB instead of 2.97 MB.
Quick Start
1
Import the phone-safe Agent
The same
Agent, from the webview-safe entry.Why a Separate Entry
The rootpraisonai entry re-exports the CLI, MCP server, tool registry, and knowledge store — a bundler cannot tell you don’t call them, so it follows every re-export and dies on a Node builtin.
What’s Exported
The mobile entry is an allowlist matchingsrc/mobile.ts — everything here is verified loadable in a webview.
Not exported (and why): CLI (spawns processes), MCP server (opens sockets), tool registry (reads the filesystem), knowledge store (reads/writes files). None of them can run in a webview, so importing them from a phone build was always a build-time crash waiting to happen.
Bundle-Size and Parse Floor
The mobile entry bundles well under the 400 kB target for a phone build, and its lazy-chunk footprint sits under the 1000 kB budget with a deliberately tight 87.4 kB margin.Why the lazy budget matters
Any static import from a leaf onAgent’s graph pulls into the initial parse. A literal import('@ai-sdk/…') — even in a code path the webview never reaches — is a bundler instruction, so esbuild ships the package as a chunk regardless. PraisonAI PR #4874 removed 326.7 kB of @ai-sdk/* provider packages (which dragged in ~678 kB, mostly zod) by moving auth to a leaf module and routing embeddings through a computed specifier. The regression test in src/praisonai-mobile/tools/bundle.test.mjs now enforces this: no @ai-sdk/* provider package may appear in the mobile bundle, aside from ai’s own internals (@ai-sdk/provider, @ai-sdk/provider-utils, @ai-sdk/gateway).
Verify these numbers against a fresh
npm run build && node src/praisonai-mobile/tools/bundle.mjs before relying on them — they are ground truth as of the #4874 merge but can drift as other PRs land.chrome58 floor is the WebView Android’s minSdkVersion: 26 (Android 8.0) ships with. scripts/webview-gate.mjs runs the esbuild build at that target on every CI run; if a maintainer adds a top-level await — directly, or by adding a require() that earns the esm-shim createRequire banner — the build fails with esbuild’s own file:line diagnosis. Anything higher than chrome58 is fine.
The entry is also verified on every CI run for Node builtins by scripts/webview-gate.mjs; if a maintainer adds an import that pulls a Node builtin into either entry, the build fails.
Webview-Safe UUIDs
randomUUID from praisonai/mobile replaces import { randomUUID } from 'crypto', a static Node builtin import that kills a webview bundle at load.
globalThis.crypto.randomUUID where available (every supported webview and Node ≥ 19), falls back to getRandomValues, and as a last resort uses Math.random — those ids are run and message identifiers, not secrets.
Adding an Import to the Mobile Entry
For contributors: an export belongs in the mobile entry only if it can run in a webview. If a piece of the SDK can run in a webview and would be useful on mobile, add it tosrc/mobile.ts and to the WEBVIEW_ENTRIES list in scripts/webview-gate.mjs. If it cannot run in a webview, it does not belong there — even if it would be convenient. A consumer reaching for something absent gets a clear build-time resolution error, which is a far better outcome than a blank screen on a device at import time.
Framework Examples
The same 5-lineAgent example, with the runtime-appropriate way to source apiKey.
- Tauri
- React Native
- Browser
- Cloudflare Worker
Best Practices
Never bundle an API key
Never bundle an API key
A key shipped in a phone or webview build is extractable by anyone with the app. Fetch an ephemeral token from your own backend at runtime and pass it as
apiKey.Import types from the mobile entry too
Import types from the mobile entry too
AgentEvent, AgentStreamOptions, SimpleAgentConfig, and StopReason are all re-exported from praisonai/mobile. Import them from there so a mobile build never reaches into the root entry by accident.Use randomUUID instead of Node's crypto
Use randomUUID instead of Node's crypto
import { randomUUID } from 'crypto' is a static Node builtin import that kills a webview bundle at load. Use randomUUID from praisonai/mobile instead.Add to the gate, not just the entry
Add to the gate, not just the entry
If you add an export to
src/mobile.ts, add its entry to WEBVIEW_ENTRIES in scripts/webview-gate.mjs so CI keeps verifying it stays webview-safe.Never add a top-level await to anything on the mobile graph
Never add a top-level await to anything on the mobile graph
esbuild refuses to lower top-level await for
chrome58. This includes indirect top-level await — a require() in one of the files on the mobile import graph causes scripts/esm-shim.js to prepend a createRequire banner that IS a top-level await. If you need a runtime lookup (e.g. reading a package’s version), use await import(pkg, { with: { type: 'json' } }) inside a function, not a bare require(pkg) at module scope.Never add a literal import('@ai-sdk/…') on Agent's static graph
Never add a literal import('@ai-sdk/…') on Agent's static graph
A literal specifier like
import('@ai-sdk/openai') is a bundler instruction, not just a runtime one — esbuild emits the package as a chunk even if no reachable code path calls it, and a webview cannot resolve it anyway. Reach provider packages through createAISDKProvider(providerInfo.package, …), a computed specifier the bundler cannot follow, which is how every chat provider already works. src/praisonai-mobile/tools/bundle.test.mjs fails the build if any @ai-sdk/* provider package (openai/google/cohere/mistral/groq/…) ships in the mobile bundle.Related
Stream Events
Structured tool events from streamEvents()
TypeScript SDK
The full TypeScript framework
Streaming
Stream text token-by-token
Agent
Full agent configuration

