@d4y/agent-runtime-nuxt
v0.1.11
Published
Headless Nuxt module that connects a Nuxt app to an agent-runtime server. Ships server-side proxy routes and a single composable, useAgentRuntime(), that exposes a typed chat client.
Readme
@d4y/agent-runtime-nuxt
Headless Nuxt module that connects a Nuxt app to an agent-runtime server.
It ships:
- Server-side proxy routes (auto-mounted under a configurable prefix) that
inject the configured
X-Agent-Runtime-App-Idheader for app scoping. - A single composable,
useAgentRuntime(), that exposes a typed reactive chat client — conversation lifecycle, SSE stream consumption, generic auth/env state, workspace file listing, and a download URL builder. - Optional frontend helpers for common host-app concerns such as rewriting workspace paths to download URLs inside markdown, classifying files for inline preview vs. download, and rendering a minimal artifact preview panel.
It is headless on purpose: the core integration does not require any UI, and the optional frontend exports stay small and app-agnostic. Render whatever you want on top of the reactive state.
The package also exports a small shared helper surface from
@d4y/agent-runtime-nuxt/shared for host apps that need generic agent-runtime config /
header utilities without pulling in app-specific policy.
Install
pnpm add @d4y/agent-runtime-nuxt
# or
bun add @d4y/agent-runtime-nuxt// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@d4y/agent-runtime-nuxt'],
agentRuntime: {
// These options are optional. When omitted they fall back to the
// matching AGENT_RUNTIME_* environment variable, so the same build works
// across dev / staging / prod without rebuilding.
baseUrl: 'http://127.0.0.1:18791', // AGENT_RUNTIME_URL
appId: 'omnisearch', // AGENT_RUNTIME_APP_ID
apiPrefix: '/api/agent-runtime' // Where the proxy routes mount
}
})
AGENT_RUNTIME_APP_IDidentifies the runtime app; it is not authorization.
Quick start
<script setup lang="ts">
const chat = useAgentRuntime()
const input = ref('')
async function onSubmit() {
const text = input.value.trim()
input.value = ''
await chat.send(text)
}
</script>
<template>
<div v-if="!chat.authReady.value">
Fill in <code>{{ Object.keys(chat.app.value?.envSchema ?? {}).join(', ') }}</code>
then call <code>chat.saveAuth({ values: { … } })</code>.
</div>
<UChatMessages
v-else
:messages="chat.messages.value"
:status="chat.status.value"
/>
<UChatPrompt v-model="input" @submit="onSubmit" />
</template>The composable returns AI-SDK / Nuxt UI compatible UIMessage shapes, so
@nuxt/ui's chat components work out of the box.
API
Module options
| Option | Type | Default | Env fallback | Notes |
|-------------|-----------|--------------------------|--------------------------|---------------------------------------|
| baseUrl | string | http://127.0.0.1:18791 | AGENT_RUNTIME_URL | Trailing slashes are stripped. |
| appId | string | omnisearch | AGENT_RUNTIME_APP_ID | Must match a registered agent-runtime app. |
| apiPrefix | string | /api/agent-runtime | - | Where proxy routes mount. |
Server routes (auto-mounted at apiPrefix)
| Route | Method | Purpose |
|------------------------------------------------------|--------|----------------------------------------------------------|
| /app | GET | Returns the active app's manifest summary. |
| /conversations | POST | Open a new conversation. Body: { env?, model? }. |
| /conversations/:id | DELETE | Delete a persisted conversation. |
| /conversations/:id/history | GET | Return the persisted conversation history. |
| /conversations/:id/stream | GET | Pipes the upstream SSE stream verbatim. |
| /conversations/:id/messages | POST | Append a user turn. Body: { content, context?, requestOptions? }. |
| /conversations/:id/abort | POST | Cancel the in-flight agent run. |
| /conversations/:id/ui-actions/:actionId | GET | Read a reliable UI action's durable status. |
| /conversations/:id/ui-actions/:actionId/ack | POST | Acknowledge an action as applied, rejected, or failed. |
| /conversations/:id/env | POST | Patch workspace env. Body: { env, merge? }. |
| /conversations/:id/files | GET | List workspace files. |
| /conversations/:id/files/raw/<path> | GET | Stream a single workspace file. |
useAgentRuntime()
const {
conversationId, // Ref<string | null>
messages, // Ref<UIMessage[]>
status, // Ref<'idle' | 'submitted' | 'streaming' | 'ready' | 'error'>
error, // Ref<Error | null>
uiActions, // Ref<UiAction[]> — newest first, capped at 50
uiActionStatuses, // Ref<HostActionStatusEvent[]> — newest first
acknowledgeUiAction, // report applied/rejected/failed for protocol-v1 actions
getUiActionStatus, // read the durable status after reconnect/reload
app, // Ref<AppInfo | null>
auth, // Ref<AppAuth>
authReady, // ComputedRef<boolean>
files, // Ref<FileEntry[]>
fileUrl, // (relPath: string) => string
refreshFiles, // () => Promise<void>
saveAuth, // (next: AppAuth) => Promise<void>
start, // () => Promise<string>
send, // (text: string, options?: SendOptions) => Promise<void>
abort, // () => Promise<void>
reset // () => Promise<void>
} = useAgentRuntime()Legacy ui_action(type, payload) events remain fire-and-forget. Protocol-v1
actions include an optional envelope on the corresponding UiAction with a
stable action ID, sequence, turn ID, expected context, expiry, and
acknowledgement state. A host that receives an action with
envelope.acknowledgement.required === true executes it through its own
allowlisted handler and reports the observed result:
await chat.acknowledgeUiAction(action.envelope, {
status: 'applied',
observedContext: {
route: '/app/documents/session-1',
documentSessionId: 'session-1',
revision: 12,
},
})See docs/host-action-protocol.md for the
wire contract, idempotency, context checks, and host security rules.
auth and authReady
The active app's manifest declares which env vars are required. The composable
fetches the manifest on mount, then loads any previously-saved values from
localStorage (scoped per appId). authReady is true iff every required
key has a non-empty value.
await chat.saveAuth({
values: {
OMNISEARCH_SESSION_TOKEN: 'abc…',
OMNISEARCH_ORGANIZATION_ID: 'e3db13ed-…'
}
})If a conversation is already open, saveAuth also pushes the new map to its
workspace via POST /conversations/:id/env, which retriggers any matching
bootstrap[] step on the harness side.
send(text, options?)
Lazily calls start() if no conversation exists. The displayed user bubble
always shows text.
Use requestOptions for provider-side controls such as Qwen/vLLM
chat_template_kwargs:
await chat.start({
requestOptions: {
temperature: 0.7,
topP: 0.8,
presencePenalty: 1.5
}
})
await chat.send(input.value, {
requestOptions: {
extraBody: {
top_k: 20,
chat_template_kwargs: { enable_thinking: false }
}
}
})rewriteContent is still available as a fallback when a model expects prompt
text toggles rather than provider payload fields:
await chat.send(input.value, {
rewriteContent: t => `${t}\n\n/no_think` // Qwen3 soft-switch
})fileUrl(relPath)
Returns the fully-qualified URL of a workspace file behind the proxy. Empty string when no conversation is open.
<a :href="chat.fileUrl(file.relPath)" download>{{ file.name }}</a>Optional frontend helpers
The module stays headless, but it exposes a small optional helper surface for host apps that want the same workspace-file ergonomics as the playground.
useAgentRuntimeMarkdown(options)
Auto-imported as a composable. It returns render() / renderInline() helpers
built on markdown-it and rewrites workspace references such as
sandbox:outputs/foo.png or /workspace/outputs/foo.pdf through your
resolveWorkspacePath() callback.
const chat = useAgentRuntime()
const { render } = useAgentRuntimeMarkdown({
resolveWorkspacePath: relPath => chat.fileUrl(relPath)
})Markdown image links that point at workspace files render as clickable images.
Non-image workspace links render as download links instead of broken <img> tags.
Bare /workspace/... paths in plain text are auto-linked as well.
File preview utilities
Available from @d4y/agent-runtime-nuxt/frontend:
import {
AgentRuntimeArtifactPreview,
getAgentRuntimeFilePreviewKind,
canPreviewAgentRuntimeFileInline,
resolveAgentRuntimeWorkspaceUri,
toAgentRuntimeWorkspaceRelativePath
} from '@d4y/agent-runtime-nuxt/frontend'Example:
const kind = getAgentRuntimeFilePreviewKind(file)
// => 'image' | 'pdf' | 'text' | 'download'Use these helpers to decide whether to render an inline image/PDF/text preview or fall back to a plain download affordance.
Minimal artifact preview component
@d4y/agent-runtime-nuxt/frontend also exports AgentRuntimeArtifactPreview, a tiny
optional Vue component for inline image / PDF / text previews:
<script setup lang="ts">
import { AgentRuntimeArtifactPreview } from '@d4y/agent-runtime-nuxt/frontend'
const chat = useAgentRuntime()
</script>
<template>
<AgentRuntimeArtifactPreview
:file="file"
:src="chat.fileUrl(file.relPath)" />
</template>Local development against a workspace copy
To consume the module from a sibling project (for example,
~/projects/d4y/omnisearch-app) before publishing to npm, link it via
your package manager's file: / link: protocol:
// omnisearch-app/package.json
{
"dependencies": {
"@d4y/agent-runtime-nuxt": "file:../../agent-runtime/packages/nuxt-module"
}
}then pnpm install (or bun install).
Shared helpers
For server-side host integrations, the module exposes a small app-agnostic utility surface:
import {
normalizeAgentRuntimeBaseUrl,
createScopeFingerprint,
resolveAgentRuntimeConfig,
createAgentRuntimeRequestHeaders
} from '@d4y/agent-runtime-nuxt/shared'These helpers are intentionally limited to generic concerns such as config normalization, request-header construction, and stable scope hashing. App-level auth, tenancy, and manifest-specific context/env shaping should stay in the host application.
Release
The npm package name is @d4y/agent-runtime-nuxt.
Publishing is handled by .github/workflows/release-nuxt-module.yml using npm
trusted publishing from GitHub Actions. Releases are triggered by pushing a tag
in this format:
git tag nuxt-module-v0.1.0
git push origin nuxt-module-v0.1.0The workflow verifies that the tag version matches
packages/nuxt-module/package.json before running npm publish --provenance.
Before the first release, configure npm trusted publishing for:
- npm package:
@d4y/agent-runtime-nuxt - GitHub repository:
digital4you/agent-runtime - workflow file:
.github/workflows/release-nuxt-module.yml
License
MIT
