Building an Agent

Multi-step tool use, human approval, and agent UI with createAgentHandler.

createAgentHandler

Server utility wrapping streamText with tools, stopWhen: stepCountIs(n), and optional needsApproval per tool. Tool parameters use Zod schemas — install with pnpm add zod.

import { createAgentHandler } from 'ai-elements-nuxt/server'
import { openai } from '@ai-sdk/openai'
import { z } from 'zod'

export default createAgentHandler({
  model: openai('gpt-4o'),
  maxSteps: 10,
  tools: {
    getWeather: {
      description: 'Get weather for a city',
      parameters: z.object({ city: z.string() }),
      execute: async ({ city }) => ({ temp: 72, city }),
    },
    deleteFile: {
      description: 'Delete a file',
      parameters: z.object({ path: z.string() }),
      requireConfirmation: true,   // pauses for human approval
      execute: async ({ path }) => ({ deleted: path }),
    },
  },
})

Client: useAiAgent + useAiTools

useAiAgent extends useAiChat with steps, plan, tasks, and a human-in-the-loop approval flow. useAiTools wires per-tool metadata and exposes pendingApprovals — already typed as AiToolCall[], ready for AiToolApproval.

const agent = useAiAgent({ api: '/api/chat' })
const tools = useAiTools([
  { name: 'getWeather', icon: '🌤' },
  { name: 'deleteFile', requireConfirmation: true },
], agent)

const { aiMessages, steps, handleSubmit, input } = agent

UI: wiring tool approval

Use tools.pendingApprovals (from useAiTools) to drive AiToolApproval — it already carries the right AiToolCall shape including approvalId. Pass tools.approveTool / tools.denyTool directly — no manual ID extraction needed.

<AiToolApproval
  v-if="tools.pendingApprovals.value[0]"
  :tool-call="tools.pendingApprovals.value[0]"
  @approve="tools.approveTool"
  @deny="tools.denyTool"
/>

<!-- Step timeline: AiAgent (also available as AiAgentSteps)
     renders the steps[] array from useAiAgent -->
<AiAgent :steps="steps" title="Agent run" />

<AiMessage v-for="(msg, i) in aiMessages" :key="i" v-bind="msg" />
<form @submit="handleSubmit">
  <AiPromptInput v-model="input" />
</form>

Naming note:<AiAgent> is a step-timeline display component — it renders the steps array from useAiAgent. It is also registered as <AiAgentSteps> if you prefer the more descriptive name. The composable useAiAgent manages the full execution state (streaming, tools, plan).

Other UI components

  • AiTool — per-tool lifecycle states (streaming args → result)
  • AiPlan / AiTask — manual plan/task state via agent.setPlan / agent.setTasks

See the Agent playground for a working mock UI.