Getting Started with Bodhi JS SDK

Integrate your React application with a local LLM server using the Bodhi JS SDK.

Prerequisites

  • Node.js 18+
  • A React project (React 18.3+ or 19+)
  • Bodhi App running locally (default: http://localhost:1135)
  • Register your application at https://developer.getbodhi.app to obtain an authClientId

Install

npm install @bodhiapp/bodhi-js-react

This single package includes everything you need: React bindings, the web SDK, and core types.

Setup BodhiProvider

Wrap your application with BodhiProvider in your entry point:

// App.tsx
import { BodhiProvider } from '@bodhiapp/bodhi-js-react';

function App() {
  return (
    <BodhiProvider authClientId="your-client-id">
      <YourApp />
    </BodhiProvider>
  );
}

BodhiProvider Props

Prop Type Default Description
authClientId string -- Required (unless client is provided). Your OAuth client ID from the developer portal.
basePath string '/' Base path for your app. Affects callback URL and storage isolation.
logLevel LogLevel 'warn' Logging verbosity: 'debug' | 'info' | 'warn' | 'error'
handleCallback boolean true Auto-handle OAuth callbacks on the callback route.
callbackPath string '{basePath}/callback' Custom OAuth callback path.
modalHtmlPath string -- Custom path to setup modal HTML.
client UIClient -- Provide a custom client instance (advanced).
clientConfig WebUIClientParams -- Configuration passed to auto-created WebUIClient.

Check Connection and Login

Use the useBodhi() hook to access SDK state and actions:

import { useBodhi } from '@bodhiapp/bodhi-js-react';

function Dashboard() {
  const {
    isOverallReady,  // true when connection + server are both ready
    isAuthenticated, // true when auth.status === 'authenticated'
    canLogin,        // true when ready and not loading
    login,
    showSetup,
    client,
  } = useBodhi();

  if (!isOverallReady) {
    return (
      <div>
        <p>Not connected to Bodhi server.</p>
        <button onClick={showSetup}>Open Setup</button>
      </div>
    );
  }

  if (!isAuthenticated) {
    return <button onClick={login} disabled={!canLogin}>Login</button>;
  }

  return <ChatInterface />;
}

Key Context Properties

Property Type Description
client UIClient The SDK client for API calls
isOverallReady boolean Connection and server both ready
isReady boolean Client has a connection (extension or direct)
isServerReady boolean Backend server is operational
isAuthenticated boolean User is authenticated
canLogin boolean Ready to login (not loading)
isInitializing boolean Client init in progress
isExtension boolean Using extension connection mode
isDirect boolean Using direct HTTP connection mode
auth AuthState Full auth state object
clientState ClientContextState Full connection state object
login(options?) function Initiate login flow
logout() function Log out
showSetup() function Open the setup modal
hideSetup() function Close the setup modal

Setup Modal for Troubleshooting

When the connection is not ready, call showSetup() to open the guided setup modal. It walks users through:

  • Platform compatibility check
  • Bodhi App installation confirmation
  • Extension installation (if applicable)
  • Direct connection configuration
  • Connection mode selection
const { isOverallReady, showSetup } = useBodhi();

if (!isOverallReady) {
  return <button onClick={showSetup}>Troubleshoot Connection</button>;
}

List Models

Models are returned as an AsyncGenerator. Collect them into an array:

const { client } = useBodhi();

async function fetchModels() {
  const models: string[] = [];
  for await (const model of client.models.list()) {
    models.push(model.id);
  }
  return models;
}

Chat Completions (Streaming)

Streaming returns an AsyncGenerator of chunks:

const { client } = useBodhi();

async function chatWithStreaming(model: string, userMessage: string) {
  const stream = client.chat.completions.create({
    model,
    messages: [{ role: 'user', content: userMessage }],
    stream: true,
  });

  let fullResponse = '';
  for await (const chunk of stream) {
    const content = chunk.choices?.[0]?.delta?.content || '';
    fullResponse += content;
    // Update UI with incremental content
  }
  return fullResponse;
}

Chat Completions (Non-Streaming)

Non-streaming returns a Promise with the complete response:

const { client } = useBodhi();

async function chat(model: string, userMessage: string) {
  const response = await client.chat.completions.create({
    model,
    messages: [{ role: 'user', content: userMessage }],
  });
  const content = response.choices[0].message.content;
  return content;
}

Embeddings

Generate vector embeddings from text:

const { client } = useBodhi();

async function embed(model: string, text: string) {
  const response = await client.embeddings.create({
    model,
    input: text,
  });
  const embedding = response.data[0].embedding; // number[]
  return embedding;
}

MCP Tool Discovery and Execution

List available MCP servers, discover their tools, and execute them:

const { client } = useBodhi();

// List available MCP servers
const { mcps } = await client.mcps.list();

// List tools for a specific MCP server
const { tools } = await client.mcps.listTools(mcp.id);

// Execute a tool
const result = await client.mcps.executeTool(
  mcp.id,
  'tool_name',
  { param: 'value' }
);

Each MCP has a slug identifier and a tools_cache array. Tools have name, description, and input_schema fields. See Advanced: MCP Agentic Patterns for building agentic chat loops with tool calls.

OAuth Callback Handling

By default, BodhiProvider auto-handles OAuth callbacks when handleCallback is true (the default). After login, the auth server redirects to your callback URL ({basePath}/callback), and the provider exchanges the authorization code for tokens automatically.

No additional setup is needed for most applications.

For manual callback handling (e.g., in custom routing), disable auto-handling and call the method directly:

<BodhiProvider authClientId="your-client-id" handleCallback={false}>
  <App />
</BodhiProvider>

Then in your callback route:

import { useBodhi } from '@bodhiapp/bodhi-js-react';
import { isWebUIClient } from '@bodhiapp/bodhi-js-react';

function CallbackPage() {
  const { client } = useBodhi();

  useEffect(() => {
    const params = new URLSearchParams(window.location.search);
    const code = params.get('code');
    const state = params.get('state');

    if (code && state && isWebUIClient(client)) {
      client.handleOAuthCallback(code, state);
    }
  }, [client]);

  return <p>Processing login...</p>;
}

For access request callbacks (when returning from an admin review URL):

if (isWebUIClient(client)) {
  await client.handleAccessRequestCallback(requestId);
}

Next Steps

  • Advanced Patterns -- Login with MCP access requests, agentic tool calling, extension SDK, error handling
  • SDK source and additional documentation: GitHub