@d4y/agent-runtime-nuxt
v0.1.8
Published
Headless Nuxt module that connects a Nuxt app to an agent-runtime server. Ships server-side proxy routes (so your X-Agent-Runtime-App-Key never leaves the server) 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
X-Agent-Runtime-App-Keyheader so your secret never reaches the browser. - 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: {
// All four keys 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
appKey: process.env.AGENT_RUNTIME_APP_KEY, // **secret** — server-only
appId: 'omnisearch', // AGENT_RUNTIME_APP_ID
apiPrefix: '/api/agent-runtime' // Where the proxy routes mount
}
})
AGENT_RUNTIME_APP_KEYis mandatory at runtime. The proxy routes will return500until it is set.
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. |
| appKey | string | (none) | AGENT_RUNTIME_APP_KEY | Server-only secret. Required. |
| 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/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
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()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
