> ## Documentation Index
> Fetch the complete documentation index at: https://praison.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph LR
    subgraph "Two entry points, one package"
        App[📱 App code] --> Choice{🔍 Where does<br/>it run?}
        Choice -->|Node / server| Root[📦 praisonai<br/>~2.97 MB<br/>CLI + MCP + tools]
        Choice -->|Phone / webview| Mobile[📱 praisonai/mobile<br/>~77 kB<br/>Agent + types]
        Root --> Done[✅ Full stack]
        Mobile --> Done
    end

    classDef input fill:#6366F1,stroke:#7C90A0,color:#fff
    classDef check fill:#189AB4,stroke:#7C90A0,color:#fff
    classDef pkg fill:#F59E0B,stroke:#7C90A0,color:#fff
    classDef output fill:#10B981,stroke:#7C90A0,color:#fff

    class App input
    class Choice check
    class Root,Mobile pkg
    class Done output
```

## Quick Start

**1. Import the phone-safe Agent**

The same `Agent`, from the webview-safe entry.

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
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');
```

> [!WARNING]
>
> **Never ship a raw API key into a phone or webview build.** Route requests through your own backend or an ephemeral-token endpoint. This example assumes `apiKey` was fetched from a trusted source at runtime.

***

## 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.

```mermaid theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
graph TB
    Import[import from 'praisonai'] --> Follow[Bundler follows every re-export]
    Follow --> Pull[Pulls in CLI + MCP + tools + knowledge]
    Pull --> Die[❌ Build dies on Node builtin<br/>nothing was going to call]

    Import2[import from 'praisonai/mobile'] --> Allow[Bundler sees allowlist only]
    Allow --> Small[✅ 77 kB webview-safe bundle]

    classDef bad fill:#8B0000,stroke:#7C90A0,color:#fff
    classDef good fill:#10B981,stroke:#7C90A0,color:#fff
    classDef step fill:#F59E0B,stroke:#7C90A0,color:#fff

    class Import,Import2 step
    class Follow,Pull,Allow step
    class Die bad
    class Small good
```

***

## 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](https://praison.ai/docs/js/stream-events.md). |
| `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 MB
```

The 1000 kB budget was re-derived from the measured 912.6 kB bundle after the #4874 and #4882 reclaims — see [PR #4903](https://github.com/MervinPraison/PraisonAI/pull/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](https://github.com/MervinPraison/PraisonAI/pull/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`).

> [!NOTE]
>
> 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.

```
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.

```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
import { randomUUID } from 'praisonai/mobile';

const runId = randomUUID();  // uses globalThis.crypto, falls back for older runtimes
```

It 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`.

> [tab] Tauri
>
> ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> 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');
> ```

> [tab] React Native
>
> ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> 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');
> ```

> [tab] Browser
>
> ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> 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');
> ```

> [tab] Cloudflare Worker
>
> ```typescript theme={"theme":{"light":"vitesse-light","dark":"vitesse-dark"}}
> 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

> [accordion] 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`.

> [accordion] 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.

> [accordion] 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.

> [accordion] 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.

> [accordion] 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.

> [accordion] 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

> [card] [Stream Events](https://praison.ai/docs/js/stream-events.md)
>
> Structured tool events from streamEvents()

> [card] [TypeScript SDK](https://praison.ai/docs/js/typescript.md)
>
> The full TypeScript framework

> [card] [Streaming](https://praison.ai/docs/js/streaming.md)
>
> Stream text token-by-token

> [card] [Agent](https://praison.ai/docs/js/agent.md)
>
> Full agent configuration


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.