> ## Documentation Index
> Fetch the complete documentation index at: https://docs.standardagentbuilder.com/llms.txt
> Use this file to discover all available pages before exploring further.

# @standardagents/vue

> Vue 3 composables and plugin for AgentBuilder - connect to AI agent threads with real-time updates

## Overview

The `@standardagents/vue` package provides Vue 3 composables and a plugin for building UIs that connect to AgentBuilder threads. It handles real-time message streaming, custom event listening, and workblock transformation for tool visualization.

<Card title="Key Features" icon="vuejs">
  * Real-time WebSocket updates for live message streaming
  * Vue plugin with provide/inject architecture
  * Workblock transformation for UI-friendly tool displays
  * Custom event system for backend-to-frontend communication
  * File uploads and attachments for sending images to the LLM
  * TypeScript support with full type definitions
  * Works with any Vue 3 framework (Nuxt, Vite, etc.)
</Card>

## Installation

```bash npm theme={null}
npm install @standardagents/vue
```

```bash pnpm theme={null}
pnpm add @standardagents/vue
```

```bash yarn theme={null}
yarn add @standardagents/vue
```

**Requirements:** Vue 3.3+

## Quick Start

```typescript theme={null}
// main.ts
import { createApp } from 'vue'
import { StandardAgentsPlugin } from '@standardagents/vue'
import App from './App.vue'

const app = createApp(App)
app.use(StandardAgentsPlugin, { endpoint: 'https://your-api.com' })
app.mount('#app')
```

```vue theme={null}
<!-- App.vue -->
<script setup lang="ts">
import { ThreadProvider } from '@standardagents/vue'
import ChatInterface from './ChatInterface.vue'
</script>

<template>
  <ThreadProvider threadId="123e4567-e89b-12d3-a456-426614174000">
    <ChatInterface />
  </ThreadProvider>
</template>
```

```vue theme={null}
<!-- ChatInterface.vue -->
<script setup lang="ts">
import { ref } from 'vue'
import { useThread } from '@standardagents/vue'

const { messages, sendMessage, status } = useThread()
const input = ref('')

const handleSend = async () => {
  if (input.value.trim()) {
    await sendMessage({ role: 'user', content: input.value })
    input.value = ''
  }
}
</script>

<template>
  <div>
    <p>Status: {{ status }}</p>
    <div v-for="msg in messages" :key="msg.id">
      <strong>{{ msg.role }}:</strong> {{ msg.content }}
    </div>
    <input v-model="input" @keyup.enter="handleSend" />
    <button @click="handleSend">Send</button>
  </div>
</template>
```

## Plugin Setup

### StandardAgentsPlugin

Install the plugin when creating your Vue application to configure the API endpoint.

```typescript theme={null}
import { createApp } from 'vue'
import { StandardAgentsPlugin } from '@standardagents/vue'

const app = createApp(App)
app.use(StandardAgentsPlugin, { endpoint: 'https://api.example.com' })
```

**Options:**

| Option     | Type     | Required | Description          |
| ---------- | -------- | -------- | -------------------- |
| `endpoint` | `string` | Yes      | The API endpoint URL |

## Components

### ThreadProvider

Component that establishes a WebSocket connection to a thread and provides context to child components.

```vue theme={null}
<script setup lang="ts">
import { ThreadProvider } from '@standardagents/vue'
</script>

<template>
  <ThreadProvider
    threadId="123e4567-e89b-12d3-a456-426614174000"
    :preload="true"
    :live="true"
    :useWorkblocks="false"
    :depth="0"
    :includeSilent="false"
  >
    <ChatInterface />
  </ThreadProvider>
</template>
```

**Props:**

| Prop            | Type      | Default  | Description                            |
| --------------- | --------- | -------- | -------------------------------------- |
| `threadId`      | `string`  | Required | The thread ID to connect to            |
| `preload`       | `boolean` | `true`   | Fetch existing messages on mount       |
| `live`          | `boolean` | `true`   | Enable WebSocket for live updates      |
| `useWorkblocks` | `boolean` | `false`  | Transform tool calls into workblocks   |
| `depth`         | `number`  | `0`      | Max message depth (0 = top-level only) |
| `includeSilent` | `boolean` | `false`  | Include silent messages                |
| `endpoint`      | `string`  | -        | Override endpoint from plugin          |

## Composables

### useStandardAgents

Access the StandardAgents client directly for advanced operations.

```vue theme={null}
<script setup lang="ts">
import { useStandardAgents } from '@standardagents/vue'

const { client, endpoint } = useStandardAgents()

// Create a new thread
const createNewThread = async () => {
  const thread = await client.createThread({ agent_id: 'my-agent' })
  console.log('Created:', thread.id)
}
</script>
```

**Returns:**

| Property   | Type                 | Description                        |
| ---------- | -------------------- | ---------------------------------- |
| `client`   | `AgentBuilderClient` | The HTTP/WebSocket client instance |
| `endpoint` | `string`             | The configured endpoint URL        |

### useThread

Composable to access the full thread context. Must be used within a `ThreadProvider`.

```vue theme={null}
<script setup lang="ts">
import { useThread } from '@standardagents/vue'

const {
  // Messages & State
  threadId,
  messages,
  workblocks,
  status,
  loading,
  error,
  options,

  // Actions
  sendMessage,
  stopExecution,
  deleteMessage,

  // Custom Events
  onEvent,

  // File Management (uploads to filesystem)
  files,
  addFiles,
  removeFile,
  getFileUrl,
  getPreviewUrl,

  // Attachment Management (sent to LLM)
  attachments,
  addAttachment,
  removeAttachment,
  clearAttachments,
} = useThread()
</script>
```

## Method Reference

### sendMessage

Send a message to the thread. Automatically includes any pending attachments and clears them after sending.

```vue theme={null}
<script setup lang="ts">
const { sendMessage, attachments } = useThread()

async function handleSend() {
  // Simple text message
  await sendMessage({ role: 'user', content: 'Hello!' })

  // With attachments (call addAttachment first)
  // Attachments are auto-included and cleared after send
  await sendMessage({ role: 'user', content: 'What is in this image?' })
}
</script>
```

**Signature:**

```typescript theme={null}
sendMessage(payload: Omit<SendMessagePayload, 'attachments'>): Promise<Message>

interface SendMessagePayload {
  role: 'user' | 'assistant' | 'system'
  content: string
  silent?: boolean
}
```

**Optimistic UI:** When you call `sendMessage()`, an optimistic message with attachment previews is shown immediately. The real message replaces it when confirmed by the server via WebSocket.

***

### addAttachment

Queue file(s) to be sent with the next message. Files are NOT uploaded immediately - they're held locally with preview URLs and sent inline (base64) when `sendMessage()` is called.

**Use this for images/files that should be sent directly to the LLM.**

```vue theme={null}
<script setup lang="ts">
const { addAttachment, attachments, removeAttachment } = useThread()

function handleFileSelect(event: Event) {
  const input = event.target as HTMLInputElement
  if (input.files?.length) {
    addAttachment(input.files)
  }
}
</script>

<template>
  <input type="file" @change="handleFileSelect" multiple />

  <!-- Show previews for pending attachments -->
  <div v-for="a in attachments" :key="a.id">
    <img v-if="a.isImage && a.previewUrl" :src="a.previewUrl" :alt="a.name" />
    <span>{{ a.name }}</span>
    <button @click="removeAttachment(a.id)">Remove</button>
  </div>
</template>
```

**Signature:**

```typescript theme={null}
addAttachment(files: File | File[] | FileList): void
```

**PendingAttachment Type:**

```typescript theme={null}
interface PendingAttachment {
  id: string              // Temporary ID
  file: File              // Original file object
  name: string
  mimeType: string
  size: number
  isImage: boolean
  previewUrl: string | null  // Object URL for image preview
  width?: number
  height?: number
}
```

***

### removeAttachment

Remove a pending attachment before sending.

```vue theme={null}
<script setup lang="ts">
const { removeAttachment, attachments } = useThread()
</script>

<template>
  <div v-for="a in attachments" :key="a.id">
    <span>{{ a.name }}</span>
    <button @click="removeAttachment(a.id)">Remove</button>
  </div>
</template>
```

**Signature:**

```typescript theme={null}
removeAttachment(id: string): void
```

***

### clearAttachments

Remove all pending attachments.

```vue theme={null}
<script setup lang="ts">
const { clearAttachments, attachments } = useThread()
</script>

<template>
  <button v-if="attachments.length" @click="clearAttachments">
    Clear All
  </button>
</template>
```

**Signature:**

```typescript theme={null}
clearAttachments(): void
```

***

### addFiles

Upload files to the thread's filesystem. Files are uploaded immediately and stored persistently.

**Use this for documents/files that should be stored on the thread but NOT sent directly to the LLM.**

```vue theme={null}
<script setup lang="ts">
const { addFiles, files, getPreviewUrl } = useThread()

function handleUpload(event: Event) {
  const input = event.target as HTMLInputElement
  if (input.files?.length) {
    addFiles(input.files)
  }
}
</script>

<template>
  <input type="file" @change="handleUpload" multiple />

  <div v-for="f in files" :key="f.id">
    <img v-if="f.isImage" :src="getPreviewUrl(f)" :alt="f.name" />
    <span>{{ f.name }}</span>
    <span>{{ f.status }}</span> <!-- 'uploading' | 'ready' | 'committed' | 'error' -->
  </div>
</template>
```

**Signature:**

```typescript theme={null}
addFiles(files: File[] | FileList): void
```

**ThreadFile Type:**

```typescript theme={null}
interface ThreadFile {
  id: string
  name: string
  mimeType: string
  size: number
  isImage: boolean
  localPreviewUrl: string | null
  status: 'uploading' | 'ready' | 'committed' | 'error'
  error?: string
  path?: string
  width?: number
  height?: number
  messageId?: string
}
```

***

### removeFile

Remove a pending file upload (cannot remove already committed files).

```vue theme={null}
<script setup lang="ts">
const { removeFile } = useThread()
</script>

<template>
  <button @click="removeFile(file.id)">Cancel Upload</button>
</template>
```

***

### File URL Helpers

```vue theme={null}
<script setup lang="ts">
const { getFileUrl, getThumbnailUrl, getPreviewUrl } = useThread()

// Full file URL (for download/viewing)
const url = getFileUrl(file)

// Thumbnail URL (for images)
const thumb = getThumbnailUrl(file)

// Preview URL (smart - uses local preview for pending, thumbnail for committed)
const preview = getPreviewUrl(file)
</script>
```

***

### stopExecution

Stop the current agent execution.

```vue theme={null}
<script setup lang="ts">
const { stopExecution } = useThread()
</script>

<template>
  <button @click="stopExecution">Stop</button>
</template>
```

***

### deleteMessage

Delete a message from the thread. The message is optimistically removed from the UI immediately. If the server request fails, the message is restored.

```vue theme={null}
<script setup lang="ts">
const { deleteMessage, messages } = useThread()

async function handleDelete(messageId: string) {
  try {
    await deleteMessage(messageId)
  } catch (err) {
    console.error('Failed to delete:', err)
  }
}
</script>

<template>
  <div v-for="msg in messages" :key="msg.id">
    <span>{{ msg.content }}</span>
    <button @click="handleDelete(msg.id)">Delete</button>
  </div>
</template>
```

**Signature:**

```typescript theme={null}
deleteMessage(messageId: string): Promise<void>
```

**Note:** Server-side attachment files are automatically cleaned up when a message is deleted.

***

### onEvent / subscribeToEvent

Subscribe to custom events emitted by the agent.

```vue theme={null}
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'

const { onEvent } = useThread()

let unsubscribe: (() => void) | null = null

onMounted(() => {
  unsubscribe = onEvent<{ status: string }>('progress', (data) => {
    console.log('Progress:', data.status)
  })
})

onUnmounted(() => {
  unsubscribe?.()
})
</script>
```

## addFiles vs addAttachment

| Feature              | `addFiles()`                   | `addAttachment()`           |
| -------------------- | ------------------------------ | --------------------------- |
| **Purpose**          | Filesystem storage             | Send to LLM                 |
| **Upload timing**    | Immediate                      | On `sendMessage()`          |
| **Storage**          | Thread filesystem (persistent) | Inline base64               |
| **Image processing** | None (raw storage)             | Server-side resize/optimize |
| **Use case**         | Documents, persistent files    | Chat images to LLM          |

## Real-time File Synchronization

The `files` computed ref from `useThread()` automatically stays synchronized with the server. When files are added, modified, or deleted on the thread (by the agent, tools, or other clients), the `files` array updates in real-time via WebSocket.

### File Events

The SDK listens for three file-related events:

| Event          | Description                                   |
| -------------- | --------------------------------------------- |
| `file_created` | A new file was added to the thread filesystem |
| `file_updated` | An existing file was modified                 |
| `file_deleted` | A file was removed from the thread filesystem |

### Custom File Event Handling

You can also subscribe to these events directly for custom handling:

```vue theme={null}
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue'
import { useThread } from '@standardagents/vue'

const { onEvent } = useThread()

let unsubscribe: (() => void) | null = null

onMounted(() => {
  unsubscribe = onEvent<{ path: string; file: { name: string; mimeType: string; size: number } }>('file_created', (data) => {
    console.log('File created:', data.path)
    // Custom notification, analytics, etc.
  })
})

onUnmounted(() => unsubscribe?.())
</script>
```

### How It Works

1. **Initial load**: When `ThreadProvider` mounts, it fetches the current file list from the server
2. **Live updates**: WebSocket events update the `files` array as changes occur
3. **Optimistic UI**: Local uploads via `addFiles()` appear immediately as "uploading" then transition to "committed"
4. **Deduplication**: Server files take priority over local pending files with the same path

## useThread Return Value

**Full ThreadContext:**

| Property           | Type                                   | Description                                                     |
| ------------------ | -------------------------------------- | --------------------------------------------------------------- |
| `threadId`         | `string`                               | The current thread ID                                           |
| `messages`         | `Ref<Message[]>`                       | Reactive array of raw messages                                  |
| `workblocks`       | `ComputedRef<ThreadMessage[]>`         | Messages transformed to workblocks (if `useWorkblocks` is true) |
| `status`           | `Ref<ConnectionStatus>`                | Connection status (alias: `connectionStatus`)                   |
| `loading`          | `Ref<boolean>`                         | Loading state (alias: `isLoading`)                              |
| `error`            | `Ref<Error \| null>`                   | Error state                                                     |
| `options`          | `ThreadProviderOptions`                | Options passed to the provider                                  |
| `sendMessage`      | `(payload) => Promise<Message>`        | Send a message (auto-includes attachments)                      |
| `stopExecution`    | `() => Promise<void>`                  | Cancel execution                                                |
| `deleteMessage`    | `(messageId: string) => Promise<void>` | Delete a message (optimistic UI)                                |
| `onEvent`          | `<T>(type, callback) => () => void`    | Subscribe to events                                             |
| `files`            | `ComputedRef<ThreadFile[]>`            | All files (pending uploads + committed)                         |
| `addFiles`         | `(files) => void`                      | Upload files to filesystem                                      |
| `removeFile`       | `(id) => void`                         | Remove a pending file                                           |
| `getFileUrl`       | `(file) => string`                     | Get file URL                                                    |
| `getThumbnailUrl`  | `(file) => string`                     | Get thumbnail URL                                               |
| `getPreviewUrl`    | `(file) => string \| null`             | Get preview URL                                                 |
| `attachments`      | `Ref<PendingAttachment[]>`             | Pending attachments for next message                            |
| `addAttachment`    | `(files) => void`                      | Add attachment(s) for next message                              |
| `removeAttachment` | `(id) => void`                         | Remove a pending attachment                                     |
| `clearAttachments` | `() => void`                           | Clear all pending attachments                                   |

**ConnectionStatus:**

```typescript theme={null}
type ConnectionStatus = 'connecting' | 'connected' | 'reconnecting' | 'disconnected'
```

### Custom Events

Listen for custom events emitted by the backend using `onEvent` from `useThread()`:

```vue theme={null}
<script setup lang="ts">
import { ref, onMounted, onUnmounted } from 'vue'
import { useThread } from '@standardagents/vue'

const { onEvent } = useThread()
const gameStatus = ref<{ success: boolean } | null>(null)

let unsubscribe: (() => void) | null = null

onMounted(() => {
  unsubscribe = onEvent<{ success: boolean }>('game_built', (data) => {
    gameStatus.value = data
  })
})

onUnmounted(() => unsubscribe?.())
</script>

<template>
  <div v-if="gameStatus">
    Game ready: {{ gameStatus.success }}
  </div>
</template>
```

**Backend Integration:**

Events are emitted from the backend using `emitThreadEvent()`:

```typescript theme={null}
// In a tool or hook on the backend
import { emitThreadEvent } from "@standardagents/builder"

emitThreadEvent(flow, "game_built", { success: true })
```

## Workblocks

Workblocks are a UI-friendly transformation of tool calls. When `useWorkblocks: true`, consecutive assistant messages with tool calls are grouped into workblocks.

<Tabs>
  <Tab title="Raw Messages">
    ```
    assistant (with tool_calls) → tool result → tool result → assistant (final)
    ```
  </Tab>

  <Tab title="Transformed Workblocks">
    ```
    workblock {
      workItems: [tool_call, tool_result, tool_call, tool_result]
      status: 'completed' | 'pending' | 'failed'
    } → assistant (final)
    ```
  </Tab>
</Tabs>

### WorkMessage Type

```typescript theme={null}
interface WorkMessage {
  id: string
  type: "workblock"
  content: string | null
  reasoning_content?: string | null
  workItems: WorkItem[]
  status: "pending" | "completed" | "failed"
  created_at: number
  depth?: number
}

interface WorkItem {
  id: string
  type: "tool_call" | "tool_result"
  name?: string
  content: string | null
  status?: "pending" | "success" | "error" | null
  tool_call_id?: string
}
```

### Rendering Workblocks

Use `ThreadProvider` with `:useWorkblocks="true"` and access via `useThread()`:

```vue theme={null}
<!-- Parent component sets up ThreadProvider with useWorkblocks -->
<template>
  <ThreadProvider threadId="thread-id" :useWorkblocks="true">
    <MessageList />
  </ThreadProvider>
</template>
```

```vue theme={null}
<!-- MessageList.vue -->
<script setup lang="ts">
import { useThread } from '@standardagents/vue'

const { workblocks } = useThread()
</script>

<template>
  <div v-for="msg in workblocks" :key="msg.id">
    <template v-if="msg.type === 'workblock'">
      <div :class="['workblock', msg.status]">
        <div v-for="item in msg.workItems" :key="item.id">
          <span v-if="item.type === 'tool_call'">Calling {{ item.name }}...</span>
          <span v-else>{{ item.content }}</span>
        </div>
      </div>
    </template>
    <template v-else>
      <div :class="msg.role">{{ msg.content }}</div>
    </template>
  </div>
</template>
```

## TypeScript Support

The package is written in TypeScript and includes full type definitions.

```typescript theme={null}
import type {
  // Core types
  Message,
  WorkMessage,
  WorkItem,
  ThreadMessage,
  Thread,

  // Configuration
  StandardAgentsConfig,
  UseThreadOptions,
  ThreadContext,

  // Attachment types
  PendingAttachment,
  AttachmentPayload,
  AttachmentRef,
  ThreadFile,

  // Connection
  ConnectionStatus,
} from "@standardagents/vue"
```

### Message Type

```typescript theme={null}
interface Message {
  id: string
  role: "system" | "user" | "assistant" | "tool"
  content: string | null
  name?: string | null
  tool_calls?: string | null          // JSON array
  tool_call_id?: string | null
  created_at: number                  // microseconds
  status?: "pending" | "completed" | "failed"
  silent?: boolean
  tool_status?: "success" | "error" | null
  reasoning_content?: string | null
  parent_id?: string | null
  depth?: number
  attachments?: string | null         // JSON array of AttachmentRef
}
```

## Complete Example

<Accordion title="View Complete Chat Interface Example with Image Attachments">
  ```vue theme={null}
  <!-- App.vue -->
  <script setup lang="ts">
  import { ref, onMounted } from 'vue'
  import { ThreadProvider } from '@standardagents/vue'
  import ChatInterface from './ChatInterface.vue'

  // Set auth token
  onMounted(() => {
    localStorage.setItem('agentbuilder_auth_token', 'your-token')
  })

  const threadId = ref('123e4567-e89b-12d3-a456-426614174000')
  </script>

  <template>
    <ThreadProvider :threadId="threadId" :useWorkblocks="true">
      <ChatInterface />
    </ThreadProvider>
  </template>
  ```

  ```vue theme={null}
  <!-- ChatInterface.vue -->
  <script setup lang="ts">
  import { ref, computed } from 'vue'
  import { useThread } from '@standardagents/vue'

  const input = ref('')

  const {
    workblocks,
    status,
    loading,
    sendMessage,
    stopExecution,
    // Attachments for sending to LLM
    attachments,
    addAttachment,
    removeAttachment,
  } = useThread()

  const isRunning = computed(() => workblocks.value.some((m) => m.status === 'pending'))

  const handleSend = async () => {
    if (!input.value.trim() || isRunning.value) return
    await sendMessage({ role: 'user', content: input.value })
    input.value = ''
  }

  const handleFileSelect = (event: Event) => {
    const target = event.target as HTMLInputElement
    if (target.files?.length) {
      addAttachment(target.files)
    }
  }
  </script>

  <template>
    <div class="chat-container">
      <p>Status: {{ status }} <span v-if="loading">(loading...)</span></p>

      <!-- Message list -->
      <div class="messages">
        <div v-for="item in workblocks" :key="item.id">
          <template v-if="item.type === 'workblock'">
            <div class="workblock" :class="item.status">
              <p v-if="item.content">{{ item.content }}</p>
              <div v-for="work in item.workItems" :key="work.id" class="work-item">
                <span v-if="work.type === 'tool_call'">Tool: {{ work.name }}</span>
                <span v-if="work.type === 'tool_result'">{{ work.status }}</span>
              </div>
            </div>
          </template>
          <template v-else>
            <div class="message" :class="item.role">
              <strong>{{ item.role }}:</strong> {{ item.content }}
            </div>
          </template>
        </div>
      </div>

      <!-- Pending attachments preview -->
      <div v-if="attachments.length" class="attachment-preview">
        <div v-for="a in attachments" :key="a.id" class="attachment-item">
          <img v-if="a.isImage && a.previewUrl" :src="a.previewUrl" :alt="a.name" width="100" />
          <span>{{ a.name }}</span>
          <button @click="removeAttachment(a.id)">×</button>
        </div>
      </div>

      <!-- Input area -->
      <div class="input-area">
        <input
          type="file"
          accept="image/*"
          multiple
          @change="handleFileSelect"
        />
        <input
          v-model="input"
          @keyup.enter="handleSend"
          :disabled="isRunning"
          placeholder="Type a message..."
        />
        <button @click="handleSend" :disabled="isRunning">Send</button>
        <button v-if="isRunning" @click="stopExecution">Stop</button>
      </div>
    </div>
  </template>

  <style scoped>
  .chat-container {
    display: flex;
    flex-direction: column;
    gap: 1rem;
    padding: 1rem;
  }

  .messages {
    display: flex;
    flex-direction: column;
    gap: 0.5rem;
  }

  .message.user {
    align-self: flex-end;
    background: #e3f2fd;
    padding: 0.5rem 1rem;
    border-radius: 1rem;
  }

  .message.assistant {
    align-self: flex-start;
    background: #f5f5f5;
    padding: 0.5rem 1rem;
    border-radius: 1rem;
  }

  .workblock {
    background: #fff3e0;
    padding: 1rem;
    border-radius: 0.5rem;
  }

  .workblock.pending {
    opacity: 0.7;
  }

  .attachment-preview {
    display: flex;
    gap: 0.5rem;
    flex-wrap: wrap;
  }

  .attachment-item {
    display: flex;
    align-items: center;
    gap: 0.5rem;
    padding: 0.5rem;
    background: #f0f0f0;
    border-radius: 0.25rem;
  }

  .input-area {
    display: flex;
    gap: 0.5rem;
  }

  .input-area input[type="text"] {
    flex: 1;
    padding: 0.5rem;
  }
  </style>
  ```
</Accordion>

## Next Steps

<CardGroup cols={2}>
  <Card title="Core Concepts" icon="book" href="/core/concepts">
    Learn about agents, prompts, and tools
  </Card>

  <Card title="React SDK" icon="react" href="/user-interfaces/react">
    React hooks and components
  </Card>

  <Card title="Builder Package" icon="cube" href="/packages/builder/overview">
    Backend framework documentation
  </Card>

  <Card title="API Reference" icon="code" href="/api-reference">
    REST and WebSocket APIs
  </Card>
</CardGroup>
