npm package: @osirus/agent
Description: Embeddable multi-mode AI agent widget for web apps (chat, code, search, image, video, speech).
Author: Osirus AI Agent
Latest version: 1.8.1

Osirus Agent is a web widget you can embed in any site or app. It supports multiple interaction modes: `chat`, `code`, `search`, `image`, `video`, and `speech`.

## Quickstart (60 Seconds)

### Fastest (NPM)

```bash
npm i @osirus/agent
```

```javascript
import OsirusAgent from '@osirus/agent'

OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'search', // chat | code | search | image | video | speech
})
```

### Fastest (CDN)

```html
<script src="https://agent.osirus.ai/lib/umd/osirusai.min.js" type="text/javascript"></script>
<script type="text/javascript">
  OsirusAgent.init({
    agentId: 'YOUR_AGENT_ID',
    mode: 'chat'
  })
</script>
```

## Installation

You install one package/script for all widget types, then choose the widget behavior with `mode`.

## Local Test Harness (Before Push)

Run the local dev harness page:

```bash
npm run dev
```

Then open:

- `http://localhost:4000`

Optional URL presets:

- `http://localhost:4000/?agentId=YOUR_AGENT_ID&mode=chat`
- `http://localhost:4000/?agentId=YOUR_AGENT_ID&mode=search&searchPlaceholder=Search%20docs`
- `http://localhost:4000/?agentId=YOUR_AGENT_ID&mode=speech`
- `http://localhost:4000/?agentId=YOUR_AGENT_ID&baseApiUrl=https://osirus.ai/api`

The page includes controls to:
- Switch `mode` (`chat`, `code`, `search`, `image`, `video`, `speech`)
- Override `agentId`
- Override `baseApiUrl`
- Set `searchPlaceholder`
- Apply/reload or unmount/mount the widget

### NPM

```bash
npm i @osirus/agent
```

```javascript
import OsirusAgent from '@osirus/agent'

OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
})
```

### CDN

```html
<script src="https://agent.osirus.ai/lib/umd/osirusai.min.js" type="text/javascript"></script>
<script type="text/javascript">
  OsirusAgent.init({
    agentId: 'YOUR_AGENT_ID'
  })
</script>
```

## Multi-Agent Init (One Call)

You can mount many agents at once by passing an array to `init`.

```javascript
import OsirusAgent from '@osirus/agent'

OsirusAgent.init([
  { agentId: 'AGENT_CHAT_ID', mode: 'chat' },
  { agentId: 'AGENT_CODE_ID', mode: 'code' },
  { agentId: 'AGENT_IMAGE_ID', mode: 'image' },
])
```

`instanceId` is optional in array mode. If omitted, it is auto-generated from `agentId`/`name`.

## Mount Into Tab/Div Containers

Use `mountTo` to render inside a specific host element (for CMS tab systems, side panels, modals, etc.).

```html
<div id="tab-code"></div>
<div id="tab-media"></div>
```

```javascript
OsirusAgent.init([
  {
    agentId: 'AGENT_CODE_ID',
    mode: 'code',
    mountTo: '#tab-code',
    placement: { position: 'absolute', top: '0', right: '0', left: '0', bottom: '0' },
  },
  {
    agentId: 'AGENT_IMAGE_ID',
    mode: 'image',
    mountTo: '#tab-media',
    placement: { position: 'absolute', top: '0', right: '0', left: '0', bottom: '0' },
  },
])
```

`mountTo` accepts either a CSS selector string or an `HTMLElement`.

## Host Callbacks (`onOutput`, `onClose`)

Use callbacks to receive generated output in the host app and react when users close the widget.

```ts
import type { AgentOutputEvent, AgentCloseEvent } from '@osirus/agent'

OsirusAgent.init({
  agentId: 'AGENT_IMAGE_ID',
  mode: 'image',
  mountTo: '#tab-media',
  onOutput: (event: AgentOutputEvent) => {
    // event.type: 'code' | 'image' | 'video' | ...
    // event.output contains payload from the mode panel
    // Example image payload: { urls, primaryUrl, generationId, prompt, source }
    console.log('output', event)
  },
  onClose: (event: AgentCloseEvent) => {
    // Example: hide active tab or persist draft state
    console.log('closed', event.reason, event.instanceId)
  },
})
```

`agentId` is always included in `onOutput`/`onClose` payloads for app-backed widgets and should be used as the host-side auth token when handling callback actions.

Current mode outputs:
- `code`: emits updated HTML after apply.
- `image`: emits generated media URLs when create/status returns outputs.
- `video`: emits generated media URLs when create/status returns outputs.

## React Host Tab Example

```tsx
import { useEffect } from 'react'
import OsirusAgent from '@osirus/agent'

export function CmsAiTabs() {
  useEffect(() => {
    OsirusAgent.init([
      {
        agentId: 'AGENT_CODE_ID',
        mode: 'code',
        mountTo: '#tab-code',
        onOutput: (event) => console.log('code output', event.output),
      },
      {
        agentId: 'AGENT_IMAGE_ID',
        mode: 'image',
        mountTo: '#tab-media-generate',
        onOutput: (event) => console.log('media output', event.output),
      },
    ])

    return () => {
      OsirusAgent.destroy()
    }
  }, [])

  return (
    <>
      <div id="tab-code" />
      <div id="tab-media-generate" />
    </>
  )
}
```

## Lifecycle Methods

Use these methods when your host controls tab lifecycle explicitly.

```javascript
// Update an existing mounted widget instance
OsirusAgent.update({
  instanceId: 'AGENT_CODE_ID',
  searchPlaceholder: 'Search CMS docs...',
})

// Destroy one instance
OsirusAgent.destroy('AGENT_CODE_ID')

// Destroy all instances
OsirusAgent.destroy()
```

## Install Each Widget Type

Use `mode` in your init config to force widget behavior per embed.

If your agent already has a type configured in the app, you can omit `mode`.
If both are present, `mode` in `OsirusAgent.init(...)` overrides the agent default.

Supported values:
- `chat`
- `code`
- `search`
- `image`
- `video`
- `speech`

Legacy alias:
- `voice` is normalized to `speech`

### 1. Chat Widget

```javascript
OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'chat',
})
```

### 2. Code Widget

`code` mode provides an HTML/code editor panel with prompt + attachment apply flow.

```javascript
OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'code',
})
```

### 3. Search Widget

`search` mode replaces the floating chat icon with a floating search bar launcher.

```javascript
OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'search',
  searchPlaceholder: 'Search docs, API, pricing...',
})
```

### 4. Image Widget

`image` mode includes a `Generate` tab with a canvas preview and prompt input.

```javascript
OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'image',
})
```

### 5. Video Widget

`video` mode includes a `Generate` tab with video-focused prompting and preview.

```javascript
OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'video',
})
```

### 6. Speech Widget

`speech` runs inside chat and supports a fullscreen speech view.

```javascript
OsirusAgent.init({
  agentId: 'YOUR_AGENT_ID',
  mode: 'speech',
})
```

## Local Configuration Example

Use local configuration when you are not loading agent settings by `agentId`.

```javascript
OsirusAgent.init({
  name: 'OsirusAgent',
  mode: 'chat',
  searchPlaceholder: 'Search...',
  companyName: 'Osirus',
  companyIconUrl: 'https://agent.osirus.ai/images/Osirus-Icon.svg',
  customColor: '#006FE5',
  description: 'Embedded support and AI agent widget.',
  phoneNumber: '123-456-7890',
  email: 'info@osirus.ai',
  docUrl: 'https://osirus.ai/docs',
  apiDocUrl: 'https://osirus.ai/docs/api',
  support: {
    type: 'page',
    value: 'https://osirus.ai/support',
  },
  schedule: 'M - F, 8:30AM - 5:30PM EDT',
  address: {
    streetAddress: '123 Orange Ave',
    city: 'Orlando',
    state: 'FL',
    postalCode: '38235',
    country: 'USA',
  },
  showMap: true,
  googleMapApiKey: 'AIzaS...x2E_k',
  greeting: 'Hi, how can I help you today?',
  customOptions: [
    {
      icon: 'people',
      label: 'Careers',
      link: 'https://www.solodev.com/careers',
      openNewTab: true,
    },
  ],
})
```

## React Export (`@osirus/agent/react`)

For React apps, you can import reusable UI components directly (in addition to `OsirusAgentProvider`).

```tsx
import {
  CodeHtmlEditor,
  FieldAssistantBar,
  HTMLEditor,
  MediaGeneratePanel,
} from '@osirus/agent/react'
import { useRef, useState } from 'react'

export function Example() {
  const inputRef = useRef<HTMLInputElement | null>(null)
  const [visible, setVisible] = useState(true)

  return (
    <>
      <CodeHtmlEditor
        value="<p>Hello</p>"
        onApply={async ({ prompt, html }) => {
          // Call your API here and return updated HTML.
          return html
        }}
      />

      <HTMLEditor
        name="content"
        value="<p>Hello world</p>"
        onChange={(nextHtml) => console.log(nextHtml)}
        onApplyPrompt={async ({ prompt, html, files }) => {
          // Call your API and return updated html.
          return { html }
        }}
      />

      <div style={{ position: 'relative', marginTop: 24 }}>
        <input
          ref={inputRef}
          onFocus={() => setVisible(true)}
          placeholder="Edit this text with AI"
        />
        <FieldAssistantBar
          targetRef={inputRef}
          visible={visible}
          onAssist={async ({ mode, text, prompt }) => {
            // Call your API here and return updated text.
            return { text: mode === 'prompt' ? `${text} ${prompt || ''}` : text }
          }}
        />
      </div>

      <MediaGeneratePanel
        agentId="YOUR_IMAGE_AGENT_ID"
        prompt="cinematic sunset mountain"
        onPromptChange={(value) => console.log(value)}
        onGenerate={({ prompt, aspectRatio, agentId }) => {
          console.log('generate', prompt, aspectRatio, agentId)
        }}
        onAddToLibrary={({ url, agentId }) => {
          console.log('add-to-library', url, agentId)
        }}
        generatedUrls={['https://example.com/generated.png']}
      />
    </>
  )
}
```

`MediaGeneratePanel` callback payloads always include `agentId` so host systems can enforce agent-scoped authorization.
For `HTMLEditor` and `FieldAssistantBar`, pass `aiContext/context.agentId` so callback requests include the same token.

## Configuration Reference

### App-Backed Config (`agentId`)

| Property | Type | Required | Description | Example |
|---|---|---|---|---|
| agentId | string | Yes | Agent ID created in the Osirus app. | `"abc123"` |
| baseApiUrl | string | No | API base URL override. | `"https://osirus.ai/api"` |
| mode | `'chat' \| 'code' \| 'search' \| 'image' \| 'video' \| 'speech'` | No | Overrides widget mode for this embed. | `"search"` |
| searchPlaceholder | string | No | Search input placeholder for `search` mode. | `"Search docs..."` |
| instanceId | string | No | Unique widget instance key used by `update`/`destroy`. | `"cms-code-tab"` |
| mountTo | `string \| HTMLElement` | No | Mount target selector or element instead of default floating root. | `"#tab-code"` |
| placement | object | No | Inline placement style for widget overlay. | `{ position: 'absolute', top: '0', left: '0' }` |
| onOutput | `(event) => void` | No | Receives generated outputs (`code`, `image`, `video`, etc.). | `(e) => console.log(e.output)` |
| onClose | `(event) => void` | No | Called when widget closes. | `(e) => console.log(e.reason)` |

### Local Config

| Property | Type | Required | Description | Example |
|---|---|---|---|---|
| name | string | Yes | Agent display name. | `"OsirusAgent"` |
| mode | `'chat' \| 'code' \| 'search' \| 'image' \| 'video' \| 'speech'` | No | Widget mode. | `"speech"` |
| searchPlaceholder | string | No | Search input placeholder for `search` mode. | `"Search..."` |
| instanceId | string | No | Unique widget instance key used by `update`/`destroy`. | `"cms-local-widget"` |
| mountTo | `string \| HTMLElement` | No | Mount target selector or element instead of default floating root. | `"#assistant-tab"` |
| placement | object | No | Inline placement style for widget overlay. | `{ position: 'absolute', top: '0', left: '0' }` |
| onOutput | `(event) => void` | No | Receives generated outputs (`code`, `image`, `video`, etc.). | `(e) => console.log(e.output)` |
| onClose | `(event) => void` | No | Called when widget closes. | `(e) => console.log(e.reason)` |
| companyName | string | No | Replaces agent name in UI. | `"Osirus"` |
| companyIconUrl | string | No | Company logo URL shown in widget header. | `"https://agent.osirus.ai/images/Osirus-Icon.svg"` |
| customColor | string | No | Hex color for widget actions. | `"#006FE5"` |
| description | string | No | Company/agent description text. | `"Embedded support and AI agent widget."` |
| phoneNumber | string | No | Enables call button. | `"123-456-7890"` |
| email | string | No | Support email. | `"info@osirus.ai"` |
| docUrl | string | No | Enables docs panel button. | `"https://osirus.ai/docs"` |
| apiDocUrl | string | No | Enables API docs panel button. | `"https://osirus.ai/docs/api"` |
| support | `{ type: 'page' \| 'email', value: string }` | No | Support button behavior and target. | `{ type: 'page', value: 'https://osirus.ai/support' }` |
| schedule | string | No | Operating hours. | `"M - F, 8:30AM - 5:30PM EDT"` |
| address | object | No | Business address object. | `{ streetAddress, city, state, postalCode, country }` |
| showMap | boolean | No | Shows map preview (requires `googleMapApiKey`). | `true` |
| googleMapApiKey | string | No | Google Maps API key for map rendering. | `"AIzaS...x2E_k"` |
| greeting | string | No | Initial greeting and popup message text. | `"Hi, how can I help you today?"` |
| customOptions | `Array<{ icon, label, link, openNewTab }>` | No | Custom action buttons. | `[{ icon: 'telephone-plus', label: 'Sales', link: 'https://example.com', openNewTab: true }]` |

### Callback Event Shapes

`onOutput(event)`:
- `event.type`: `'chat' | 'code' | 'image' | 'video' | 'speech' | 'search'`
- `event.output`: mode-specific payload (for example `html` for code, `urls` for image/video)
- `event.agentId`, `event.instanceId`, `event.mode`, `event.timestamp`

`onClose(event)`:
- `event.reason`: close reason string (for example `'widget-close'`)
- `event.agentId`, `event.instanceId`, `event.mode`, `event.timestamp`

## Icon Notes

`customOptions[].icon` uses Bootstrap Icons names (without the `bi-` prefix).
Example: `"telephone-plus"` renders `bi bi-telephone-plus`.