Skip to content

Integration guide

This guide covers a complete @agentic-ui integration for a Vue app: declaring capabilities, connecting them to an LLM through a proxy, and requesting user approval for risky actions. Runnable versions of the integration are available in playground/ and examples/.

How the pieces connect

 Vue component ──useAgentCapability──▶ registry (core)

 server proxy ──streamText(tools)──▶ LLM  │  the model returns tool-calls
        ▲                                 ▼
        └──────── bindRegistryToChat ── registry.execute (validate→permission
                                          →confirmation→execute) ──▶ result

The key never enters the browser. The client sends {messages, tools} to your proxy, including only the tool schemas. The proxy runs streamText and streams tool-calls back to the client. The registry executes those calls and returns the results to the stream.

1. Install

bash
pnpm add @agentic-ui/vue @agentic-ui/client ai @ai-sdk/openai

@agentic-ui/core, /schema, and /protocol are installed as dependencies. The ai SDK is a peer dependency only of @agentic-ui/client.

2. Register the plugin

ts
// main.ts
import { createApp } from 'vue'
import { createAgenticUi } from '@agentic-ui/vue'

createApp(App).use(createAgenticUi()).mount('#app')

This provides a single registry to the entire app. Access it from any component with useAgent().

3. Declare capabilities

A capability is a typed, described, risk-tagged action. When declared in a component, it registers on mount and is disposed on unmount.

ts
import { useAgentCapability } from '@agentic-ui/vue'

useAgentCapability({
  name: 'todos.add',                         // dotted namespaces are fine
  description: 'Add a new todo item.',        // the model reads this — be clear
  inputSchema: {
    type: 'object',
    properties: { text: { type: 'string' } },
    required: ['text'],
  },
  risk: 'mutating',                           // 'read' | 'mutating' | 'destructive'
  execute: ({ text }: { text: string }) => { todos.push({ text, done: false }) },
})
  • risk controls confirmation: read runs immediately, while mutating and destructive require approval (see step 6). To change this behavior for an individual capability, provide a confirmation policy.
  • inputSchema is JSON Schema (draft-07 subset). Every call is validated against it before execute runs. An invalid payload returns invalid, along with information the model can use to correct it. You can also pass a zod schema, which the adapter converts.
  • The definition can be a ref/getter — update the schema as the UI changes (for example, an enum of the fields currently on screen), and the catalog revision increments.

To make current values available on demand without placing all app state in the model's context, expose a read-risk capability. The model can call it when it needs the data.

4. The server proxy (holds the key)

The server proxy is a ~50-line Node handler. It uses catalogToToolSet to map incoming tool schemas to an AI SDK ToolSet, then runs streamText:

ts
import { convertToModelMessages, streamText } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import { catalogToToolSet } from '@agentic-ui/client'

const openai = createOpenAI({
  apiKey: process.env.OPENAI_API_KEY,          // stays server-side
  baseURL: process.env.OPENAI_BASE_URL,        // optional: an OpenAI-compatible gateway
})
const model = openai.chat(process.env.OPENAI_MODEL ?? 'gpt-4o-mini')

// per request: { messages, tools } from the client
const result = streamText({
  model,
  messages: convertToModelMessages(messages),
  tools: catalogToToolSet(tools),              // schema-only; execution stays client-side
})
return result.toUIMessageStreamResponse()

See playground/server/index.ts for the complete handler, including a permission stub.

5. The client transport

Bind the chat to the registry so that every streamed tool-call runs through the execution pipeline. On each outgoing request, use the binding to serialize the catalog; its revision snapshot enables the stale check:

ts
import { bindRegistryToChat, sendAutomaticallyWhen } from '@agentic-ui/client'
import { DefaultChatTransport } from 'ai'

let binding
const chat = new YourChat({
  transport: new DefaultChatTransport({
    api: '/agent/chat',
    prepareSendMessagesRequest: ({ messages }) => ({
      body: { messages, tools: binding.buildRequestTools().tools },
    }),
  }),
  sendAutomaticallyWhen,   // resend automatically once tool results are in
})
binding = bindRegistryToChat(chat, registry)

YourChat can be any AI SDK chat whose state is backed by Vue refs, allowing templates to re-render as the stream progresses. The reusable implementation is in playground/src/agent-chat.ts.

6. Approvals

mutating and destructive calls remain blocked until the user approves the exact payload. Display the pending queue with useConfirmationQueue:

vue
<script setup lang="ts">
import { useConfirmationQueue } from '@agentic-ui/vue'
const queue = useConfirmationQueue()
</script>

<template>
  <div v-for="req in queue.pending.value" :key="req.request.callId">
    {{ req.request.descriptor.name }} — digest {{ req.request.digest.slice(0, 8) }}
    <button @click="req.approve('digest')">Approve</button>
    <button @click="req.deny('Not now')">Deny</button>
  </div>
</template>

Approving with 'digest' remembers that exact payload. An identical call will not prompt again, but any byte-level change will. If the user denies the request, the call resolves as unconfirmed, the model receives that result, and execute does not run.

7. Inspect the live catalog

useAgent().entries is reactive and reflects the currently mounted capabilities. A capability inspector or debug panel therefore stays in sync as views mount and unmount:

ts
const { entries, registry } = useAgent()
// registry.onAudit(e => timeline.push(e))   // received→validated→…→executed

8. Scaling the catalog

For a small tool set, send every tool by using direct, the default mode. For more than a few dozen tools, select a mode with buildRequestTools(registry, { mode }):

  • { mode: 'scoped', scopes } — sends only capabilities in the specified scopes.
  • { mode: 'meta-tools' } — send just three tools (agent.search_capabilities, agent.describe, agent.execute). The model searches and retrieves descriptions on demand, and agent.execute runs the underlying tool through the standard pipeline. Confirmation still uses the real tool's digest.

Gateways & providers

  • Point the proxy at any OpenAI-compatible gateway (9Router, LiteLLM, one-api) with OPENAI_BASE_URL; the key remains server-side.
  • Use openai.chat(...) (Chat Completions). The default Responses API mis-threads tool-call ids through such gateways.
  • Tool names sent to the model are sanitized to ^[a-zA-Z0-9_-]+$ (toWireName) because providers reject dotted names. Before execution, the client maps them back to the original capability names, so you can continue writing todos.add.

Testing

You can test a capability without an LLM; see Test your capability. Each example app also includes a scripted mock-LLM end-to-end test that you can reuse.