|
| 1 | +--- |
| 2 | +title: Connecting plugin-chatbot to a framework AI backend |
| 3 | +description: Wire @object-ui/plugin-chatbot in Console (or any frontend) to the framework's /api/v1/ai/* REST surface — agents picker, SSE chat, models, HITL pending-action inbox. |
| 4 | +--- |
| 5 | + |
| 6 | +# Connecting plugin-chatbot to a framework AI backend |
| 7 | + |
| 8 | +`@object-ui/plugin-chatbot` (a React component shipped from the `objectui` |
| 9 | +monorepo) is the canonical chat UI for ObjectStack Console. It speaks the |
| 10 | +Vercel AI Data Stream protocol, so it pairs natively with the AI routes |
| 11 | +exposed by `@objectstack/service-ai` once the framework dev server is run |
| 12 | +with the AI tier enabled. |
| 13 | + |
| 14 | +This guide maps every chatbot configuration knob to its framework endpoint |
| 15 | +so you can drop the plugin into an app without reverse-engineering the |
| 16 | +contract. |
| 17 | + |
| 18 | +## 1. Enable the AI tier on the framework side |
| 19 | + |
| 20 | +The default `objectstack dev` preset does **not** include AI services. Pass |
| 21 | +`--preset full` (or any preset that opts into `ai`) and provide an OpenAI- |
| 22 | +compatible LLM gateway via env vars: |
| 23 | + |
| 24 | +```bash |
| 25 | +OPENAI_BASE_URL=https://ai-gateway.vercel.sh/v1 \ |
| 26 | +AI_GATEWAY_API_KEY=vck_*** \ |
| 27 | +AI_GATEWAY_MODEL=openai/gpt-4.1-mini \ |
| 28 | +CORS_ORIGIN=http://localhost:5173 \ |
| 29 | +pnpm dev --preset full |
| 30 | +``` |
| 31 | + |
| 32 | +You should see `AIService` in the plugin list and three of the endpoints |
| 33 | +below should answer (the streaming one is verified in step 3): |
| 34 | + |
| 35 | +```bash |
| 36 | +curl http://localhost:3002/api/v1/ai/agents |
| 37 | +# → { "agents": [{ "name": "data_chat", ... }, ...] } |
| 38 | + |
| 39 | +curl http://localhost:3002/api/v1/ai/models |
| 40 | +# → { "models": [...] } ← may be empty until you register some |
| 41 | +``` |
| 42 | + |
| 43 | +## 2. Endpoint map |
| 44 | + |
| 45 | +| Chatbot input | Framework route | Notes | |
| 46 | +|----------------|---------------------------------------------------|-------| |
| 47 | +| `useAgents({ apiBase })` → `${apiBase}/agents` | `GET /api/v1/ai/agents` | Response is `{ agents: [...] }` (also accepts a bare array). | |
| 48 | +| `<ChatbotEnhanced models={...} />` source | `GET /api/v1/ai/models` | Returns `{ models: [{ id, label }] }`. | |
| 49 | +| `useObjectChat({ api })` / `<ChatbotEnhanced api={...}>` | `POST /api/v1/ai/assistant/chat` | Vercel AI data-stream SSE. Body: `{ messages, agent?, conversationId?, ... }`. | |
| 50 | +| HITL pending inbox (custom UI) | `GET /api/v1/ai/pending-actions` | Filter by `?status=pending`. | |
| 51 | +| Approve a proposed action | `POST /api/v1/ai/pending-actions/:id/approve` | Body: `{ actor: string }`. | |
| 52 | +| Reject a proposed action | `POST /api/v1/ai/pending-actions/:id/reject` | Body: `{ actor: string, reason?: string }`. | |
| 53 | + |
| 54 | +All endpoints accept the standard tenancy header `X-Environment-Id`. The |
| 55 | +default Hono adapter reflects CORS origins from the `CORS_ORIGIN` env var |
| 56 | +and already exposes `X-Environment-Id` in the allow-list, so cross-origin |
| 57 | +calls from a Vite dev server (`http://localhost:5173`) work out of the box. |
| 58 | + |
| 59 | +## 3. Streaming contract |
| 60 | + |
| 61 | +`POST /api/v1/ai/assistant/chat` returns: |
| 62 | + |
| 63 | +``` |
| 64 | +HTTP/1.1 200 OK |
| 65 | +content-type: text/plain; charset=utf-8 |
| 66 | +x-vercel-ai-ui-message-stream: v1 |
| 67 | +transfer-encoding: chunked |
| 68 | +
|
| 69 | +data: {"type":"start"} |
| 70 | +data: {"type":"text-delta","id":"0","delta":"Hello"} |
| 71 | +data: {"type":"finish","finishReason":"stop"} |
| 72 | +data: [DONE] |
| 73 | +``` |
| 74 | + |
| 75 | +This is the wire format `@ai-sdk/react`'s `useChat` consumes natively — no |
| 76 | +client-side parsing is required. Wire it up like: |
| 77 | + |
| 78 | +```ts |
| 79 | +import { useObjectChat } from '@object-ui/plugin-chatbot'; |
| 80 | + |
| 81 | +const chat = useObjectChat({ |
| 82 | + api: 'http://localhost:3002/api/v1/ai/assistant/chat', |
| 83 | + headers: { 'X-Environment-Id': 'env_local' }, |
| 84 | + body: { agent: 'data_chat' }, |
| 85 | +}); |
| 86 | +``` |
| 87 | + |
| 88 | +> **Heads up:** If you ever see the response come back as a JSON envelope |
| 89 | +> like `{"type":"stream","events":{},"vercelDataStream":true,...}` you are |
| 90 | +> running an old `@objectstack/runtime` (< the patch tagged |
| 91 | +> `v5-runtime-ai-sse-stream-fix`). Upgrade and restart the dev server. |
| 92 | +
|
| 93 | +## 4. HITL (Human-in-the-Loop) flow |
| 94 | + |
| 95 | +When the agent picks a dangerous action (e.g. `delete_task`) the tool |
| 96 | +handler enqueues an `ai_pending_action` row and the chat returns a |
| 97 | +`pending_approval` tool result. Your Console UI then: |
| 98 | + |
| 99 | +1. Polls `GET /api/v1/ai/pending-actions?status=pending`. |
| 100 | +2. Renders an inbox item per row (`tool_name`, `tool_input`, |
| 101 | + `conversation_id`, `proposed_at`). |
| 102 | +3. On approve: `POST /api/v1/ai/pending-actions/:id/approve` with |
| 103 | + `{ actor: '<current user id>' }` → row transitions to `executed` with |
| 104 | + `result` populated. |
| 105 | +4. On reject: `POST /api/v1/ai/pending-actions/:id/reject` with |
| 106 | + `{ actor, reason }` → row transitions to `rejected`. |
| 107 | + |
| 108 | +The Studio ships an `ai-pending-action` view with the same actions baked |
| 109 | +in; the same REST endpoints back both surfaces, so a custom Console widget |
| 110 | +needs no additional plumbing. |
| 111 | + |
| 112 | +## Troubleshooting |
| 113 | + |
| 114 | +- **`/api/v1/ai/*` returns 404** → the AI tier is not loaded. Re-run dev |
| 115 | + with `--preset full`. |
| 116 | +- **CORS blocked** → set `CORS_ORIGIN=http://your-frontend-origin` before |
| 117 | + launching the dev server. |
| 118 | +- **Chat returns JSON instead of streaming** → upgrade |
| 119 | + `@objectstack/runtime` to the patch above. |
| 120 | +- **`/api/v1/ai/models` is empty** → register models on the |
| 121 | + `AIService.modelRegistry` or skip the picker entirely; chat works |
| 122 | + without an explicit model when `AI_GATEWAY_MODEL` is set. |
0 commit comments