Mobile & Webview Entry
Import from praisonai/mobile for a webview-safe bundle (77 kB vs 2.97 MB)
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
- Import the phone-safe Agent
The same
Agent, from the webview-safe entry.typescript import { Agent } from 'praisonai/mobile'; const agent = new Agent({ instructions: 'You are a helpful assistant', llm: 'gpt-4o-mini', apiKey, // supplied by your app at runtime, never bundled }); const answer = await agent.start('Say hello');
Why a Separate Entry
The root praisonai 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 matching src/mobile.ts — everything here is verified loadable in a webview.
| Export | Kind | Notes | ||||
|---|---|---|---|---|---|---|
Agent | class | The agent loop itself. | ||||
AgentEvent | type | Union: text \ | tool_call \ | tool_result \ | finish \ | error. See Stream Events. |
AgentStreamOptions | type | Options for agent.streamEvents(). | ||||
SimpleAgentConfig | type | Full Agent constructor config shape. | ||||
StopReason | type | Run stop reason: completed \ | max_steps \ | cancelled \ | error. | |
randomUUID | function | Webview-safe UUID v4; use instead of Node’s crypto.randomUUID. | ||||
getEnv | function | Safe env accessor; returns undefined when there is no process. |
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.
praisonai/mobile entry, minified: 77.8 kB (target: < 400 kB)
praisonai/mobile lazy chunks: 912.6 kB (budget 1000 kB)
praisonai/mobile headroom: 87.4 kB
praisonai, root entry: 2.97 MBThe 1000 kB budget was re-derived from the measured 912.6 kB bundle after the #4874 and #4882 reclaims — see PR #4903 for the floor/lid derivation.
Why the lazy budget matters
Any static import from a leaf on Agent‘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).
praisonai/mobile bundles for: chrome58 (Android 8.0 WebView floor)
chrome108 (mainstream metafile check)
safari16 (iOS WKWebView floor)The 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.
import { randomUUID } from 'praisonai/mobile';
const runId = randomUUID(); // uses globalThis.crypto, falls back for older runtimesIt calls 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 to src/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-line Agent example, with the runtime-appropriate way to source apiKey.
Tauri
import { Agent } from 'praisonai/mobile';
import { invoke } from '@tauri-apps/api/core';
const apiKey = await invoke<string>('get_api_key'); // from the Rust backend
const agent = new Agent({
instructions: 'You are a helpful assistant',
llm: 'gpt-4o-mini',
apiKey,
});
const answer = await agent.start('Say hello');React Native
import { Agent } from 'praisonai/mobile';
const res = await fetch('https://your-backend.example/token');
const { apiKey } = await res.json(); // ephemeral token from your backend
const agent = new Agent({
instructions: 'You are a helpful assistant',
llm: 'gpt-4o-mini',
apiKey,
});
const answer = await agent.start('Say hello');Browser
import { Agent } from 'praisonai/mobile';
const res = await fetch('/api/token');
const { apiKey } = await res.json(); // proxied through your server
const agent = new Agent({
instructions: 'You are a helpful assistant',
llm: 'gpt-4o-mini',
apiKey,
});
const answer = await agent.start('Say hello');Cloudflare Worker
import { Agent } from 'praisonai/mobile';
export default {
async fetch(request: Request, env: { OPENAI_API_KEY: string }): Promise<Response> {
const agent = new Agent({
instructions: 'You are a helpful assistant',
llm: 'gpt-4o-mini',
apiKey: env.OPENAI_API_KEY, // from the Worker's secret binding
});
const answer = await agent.start('Say hello');
return new Response(answer);
},
};Best Practices
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
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
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
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
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
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.