| title | Connecting plugin-chatbot to a framework AI backend |
|---|---|
| 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. |
@object-ui/plugin-chatbot (a React component shipped from the objectui
monorepo) is the canonical chat UI for ObjectStack Console. It speaks the
Vercel AI Data Stream protocol, so it pairs natively with the AI routes
exposed by @objectstack/service-ai once the framework dev server is run
with the AI tier enabled.
This guide maps every chatbot configuration knob to its framework endpoint so you can drop the plugin into an app without reverse-engineering the contract.
The default and full plugin tier presets both include the ai
capability, so objectstack dev boots the AI services unless you opt out
with --preset minimal. Provide a Vercel AI Gateway model and key via env
vars:
AI_GATEWAY_API_KEY=vck_*** \
AI_GATEWAY_MODEL=openai/gpt-4.1-mini \
OS_CORS_ORIGIN=http://localhost:5173 \
pnpm devYou should see the AI Service plugin (com.objectstack.service-ai) in the
plugin list and the endpoints below should answer (the streaming one is
verified in step 3):
curl http://localhost:3000/api/v1/ai/agents
# → { "agents": [{ "name": "ask", ... }, ...] }
curl http://localhost:3000/api/v1/ai/models
# → { "models": [...] } ← may be empty until the adapter lists models| Chatbot input | Framework route | Notes |
|---|---|---|
useAgents({ apiBase }) → ${apiBase}/agents |
GET /api/v1/ai/agents |
Response is { agents: [...] } (also accepts a bare array). |
<ChatbotEnhanced models={...} /> source |
GET /api/v1/ai/models |
Returns { models: [...] } (a list of model-id strings). |
useObjectChat({ api }) / <ChatbotEnhanced api={...}> |
POST /api/v1/ai/assistant/chat |
Vercel AI data-stream SSE. Body: { messages, agent?, conversationId?, ... }. |
| HITL pending inbox (custom UI) | GET /api/v1/ai/pending-actions |
Filter by ?status=pending. |
| Approve a proposed action | POST /api/v1/ai/pending-actions/:id/approve |
No body required; the actor is the authenticated user. Requires ai:approve. |
| Reject a proposed action | POST /api/v1/ai/pending-actions/:id/reject |
Body: { reason?: string }; actor is the authenticated user. Requires ai:approve. |
All endpoints accept the standard tenancy header X-Environment-Id. The
default Hono adapter reflects CORS origins from the OS_CORS_ORIGIN env var
and already exposes X-Environment-Id in the allow-list, so cross-origin
calls from a Vite dev server (http://localhost:5173) work out of the box.
POST /api/v1/ai/assistant/chat returns:
HTTP/1.1 200 OK
content-type: text/event-stream
x-vercel-ai-ui-message-stream: v1
transfer-encoding: chunked
data: {"type":"start"}
data: {"type":"text-delta","id":"0","delta":"Hello"}
data: {"type":"finish","finishReason":"stop"}
data: [DONE]
This is the wire format @ai-sdk/react's useChat consumes natively — no
client-side parsing is required. Wire it up like:
import { useObjectChat } from '@object-ui/plugin-chatbot';
const chat = useObjectChat({
api: 'http://localhost:3000/api/v1/ai/assistant/chat',
headers: { 'X-Environment-Id': 'env_local' },
body: { agent: 'ask' },
});Heads up: If you ever see the response come back as a JSON envelope like
{"type":"stream","events":{},"vercelDataStream":true,...}your HTTP adapter is not encoding the SSE stream. Make sure you are on a current@objectstack/runtimeand Hono adapter, then restart the dev server.
When the agent picks a dangerous action (e.g. delete_task) the tool
handler enqueues an ai_pending_action row and the chat returns a
pending_approval tool result. Your Console UI then:
- Polls
GET /api/v1/ai/pending-actions?status=pending. - Renders an inbox item per row (
tool_name,tool_input,conversation_id,proposed_at). - On approve:
POST /api/v1/ai/pending-actions/:id/approve(no body; the actor is the authenticated user, recorded ondecided_by) → row transitions toexecutedwithresultpopulated. - On reject:
POST /api/v1/ai/pending-actions/:id/rejectwith{ reason }→ row transitions torejected.
The ai_pending_actions object ships built-in approve/reject actions
targeting the same REST endpoints, so the Console inbox and a custom widget
share one contract — no additional plumbing.
/api/v1/ai/*returns 404 → the AI tier is not loaded. Don't run with--preset minimal; use thedefault(orfull) tier.- CORS blocked → set
OS_CORS_ORIGIN=http://your-frontend-originbefore launching the dev server. - Chat returns JSON instead of streaming → the HTTP adapter isn't
encoding the SSE stream; update
@objectstack/runtimeand the Hono adapter, then restart. /api/v1/ai/modelsis empty → register models on theAIService.modelRegistryor skip the picker entirely; chat works without an explicit model whenAI_GATEWAY_MODELis set.