A developer-friendly library for building AI-integrated Svelte 5 web apps. Annotate your components with @ai decorators ā the library handles registration, context formatting, and tool generation for your AI agent.
Agents can browse the web autonomously, but deep app integration gives you:
SvelteAI sits between your Svelte app and any AI framework: it turns annotated reactive state into prompt context and ready-made tools, while remaining agnostic to which model provider or SDK you use.
graph TD
App["š„ļø **Svelte App**<br>(components, state, actions)"]
SvelteAI["āļø **SvelteAI**<br>(registry, prompt builders, tools)"]
AIFramework["š AI Framework<br>(Vercel AI SDK / aibind)"]
Provider["š¤ Model Provider<br>(OpenAI / Anthropic / Ollama ā¦)"]
LLM["š§ LLM"]
App -->|"@ai annotations<br>ā live registry"| SvelteAI
SvelteAI -->|"prompt context<br>+ tool definitions"| AIFramework
AIFramework -->|"model call"| Provider
Provider --> LLM
LLM -->|"tool calls<br>(setState / callAction / navigate)"| SvelteAI
SvelteAI -->|"writes reactive state<br>invokes actions"| App
SvelteAI runs a Svelte preprocessor (for .svelte files) and a Vite plugin (for .svelte.ts shared state). Both scan for @ai and @component decorator annotations and emit $effect registration blocks that wire your reactive state and functions into a live registry tree.
At runtime, a SvelteAI facade reads the registry and exposes:
promptLocalContext() ā all-in-one: current page + component state + route mappromptCurrentPage() ā current page block onlypromptComponentContext() ā mounted component instances + global statepromptRouteMap(options?) ā available pages listgetState() / getSnapshot() ā flat map or nested tree of current valuessetState() / callAction() ā write back to reactive state or invoke annotated functionstools ā ready-made Vercel AI SDK tool() definitions (callAction, setState, lookupComponent)Prompt builders are called on every turn, not cached ā the model always sees live values.
| Tool | level | Behaviour |
|---|---|---|
| No type for object state writes | High | The model could theoretically assign new attributes in a state object (useless). |
| No type for action calls | High | A proper zod or otherwise typing can clarify flows and prevent model errors |
| VSCode checks | Medium / High | Annotations (decorators) trigger IDE syntax check errors |
| AdHoc solution for routes | Medium | Creating route manifest requires boilerplate (import.meta.glob). |
| Limitation on state writes | Medium | Currently caught by preprocessor. Svelte prevents state re-assignment in foreign files. Use objects instead (consistent with svelte doc but abstract) |
| Explicit connectors for Vercel AI and others | Low | Simple to do |
<!-- ThermostatWidget.svelte -->
<script module>
@component({ description: 'A thermostat control widget for a single room.' })
</script>
<script lang="ts">
let { room }: { room: Room } = $props()
@ai({ access: 'r', description: 'The name of the room this widget controls.' })
let room_name = $derived(room.name)
@ai({ access: 'rw', description: 'Current target temperature in Celsius. Agent may set between 16 and 30.' })
let temperature = $state(room.defaultTemp)
@ai({ description: 'Resets the temperature to the room default.' })
function resetTemperature() {
temperature = room.defaultTemp
}
</script>
.svelte.ts)// energy.svelte.ts
@ai({ access: 'r', description: 'Current total energy consumption in watts.' })
export let total_watts = $state(1240)
svelteAI.promptLocalContext() produces a formatted block injected into the system prompt:
App state:
[ThermostatWidget:a3f2]
A thermostat control widget for a single room.
room_name (r): 'bedroom'
temperature (rw): 22
resetTemperature() ā Resets the temperature to the room default.
[ThermostatWidget:b7e0]
A thermostat control widget for a single room.
room_name (r): 'living room'
temperature (rw): 19
Global state:
total_watts (r): 1240
// svelteai.ts
import { page } from '$app/state'
import { SvelteAI } from 'svelteai'
import { routes } from '$lib/routes.js'
export const svelteAI = new SvelteAI({
routes,
getPath: () => page.url.pathname,
})
// agent.ts
import { ToolLoopAgent } from 'ai'
import { createOpenAI } from '@ai-sdk/openai'
import { svelteAI } from './svelteai'
const openai = createOpenAI({ apiKey })
export const agent = new ToolLoopAgent({
model: openai('gpt-4o'),
tools: {
...svelteAI.tools.lookupRoute,
...svelteAI.tools.navigateByUrl,
// alternatively ...svelteAI.tools.navigateByIndex,
...svelteAI.tools.callAction,
...svelteAI.tools.setState,
...svelteAI.tools.lookupComponent,
},
prepareStep: async ({ messages }) => ({
messages: [
{
role: 'system',
content: `You are a smart home assistant.\n\n${svelteAI.promptLocalContext()}`,
},
...messages.filter((m) => m.role !== 'system'),
],
}),
})
SvelteAI can give the agent awareness of the app's page structure ā what pages exist, what they do, and how to navigate between them.
Each page that should be AI-visible exports a _routeMeta object from its +page.ts:
// src/routes/sverdle/+page.ts
export const _routeMeta = {
short: 'Sverdle word game',
long: 'A Wordle clone where the AI assistant can suggest words and enter guesses.',
}
For dynamic routes, add a params field describing where the model should obtain each value:
// src/routes/sverdle/[word]/+page.ts
export const _routeMeta = {
short: 'Sverdle round for a specific word',
params: {
word: {
description: 'The 5-letter word to use as the answer.',
example: 'crane',
},
},
}
For .md pages (mdsvex), use YAML frontmatter:
---
routeMeta:
short: Why SvelteAI?
long: Explains the motivation and core concepts behind SvelteAI.
---
Create src/lib/routes.ts in your app. The two import.meta.glob calls must live in the app (Vite compile-time constraint); buildRouteRegistry() owns all the logic:
// src/lib/routes.ts
import { buildRouteRegistry } from 'svelteai'
const tsModules = import.meta.glob('/src/routes/**/+page.ts', { eager: true })
const mdModules = import.meta.glob('/src/routes/**/+page.md', { eager: true })
export const routes = buildRouteRegistry({ tsModules, mdModules })
Pass routes and a getPath callback to new SvelteAI(...). getPath is called on every prompt-builder invocation so the agent always sees the live current page:
// svelteai.ts
import { page } from '$app/state'
import { SvelteAI } from 'svelteai'
import { routes } from '$lib/routes.js'
export const svelteAI = new SvelteAI({
routes,
getPath: () => page.url.pathname,
})
promptLocalContext() combines a "Current page" block, component state, and an "Available pages" list. The active route is starred:
Current page: /sverdle/crane
Sverdle round for a specific word
word = crane
App state:
...
Available pages:
[0] / ā Home
[1] /sverdle ā Sverdle word game
* [2] /sverdle/[word] ā Sverdle round for a specific word [params: word]
[3] /demo/local-context ā Smart home thermostat demo
Add the navigation tools to your agent. Three tools are available ā include whichever fits your agent's style:
tools: {
...svelteAI.tools.lookupRoute, // search pages by keyword
...svelteAI.tools.navigateByUrl, // model constructs full URL
...svelteAI.tools.navigateByIndex, // model picks index + params map; library resolves URL
...svelteAI.tools.callAction,
...svelteAI.tools.setState,
...svelteAI.tools.lookupComponent,
}
| Tool | Input | Behaviour |
|---|---|---|
lookupRoute |
{ query } |
Keyword search over path, short, long ā returns matching RouteRecord[] with index and param hints |
navigateByUrl |
{ path } |
Calls goto(path) ā model supplies the fully resolved URL |
navigateByIndex |
{ index, params? } |
Library resolves [param] placeholders from the params map, then calls goto() ā prevents malformed URLs |
Add to svelte.config.js:
import { svelteAIPreprocess } from 'svelteai'
export default {
preprocess: [svelteAIPreprocess()]
}
Add to vite.config.ts:
import { svelteAIVitePlugin } from 'svelteai'
export default defineConfig({
plugins: [sveltekit(), svelteAIVitePlugin()]
})
svelteAI/ # The library (npm: svelteai)
āāā src/lib/
ā āāā index.ts # Public API re-exports
ā āāā registry/
ā ā āāā types.ts # All interfaces: AIEntry, AIAction, AINode, AISnapshot, ā¦
ā ā āāā AIRegistry.ts # Root registry ā component type catalogue + tree root
ā ā āāā AINode.ts # Tree node ā entries, actions, children, lifecycle
ā ā āāā context.ts # AI_REGISTRY_KEY + getAIContext() Svelte context helper
ā āāā facade/
ā ā āāā SvelteAI.ts # User-facing facade over the registry
ā ā āāā tools.ts # Vercel AI SDK tool() definitions
ā āāā preprocessor/
ā ā āāā index.ts # svelteAIPreprocess() ā Svelte preprocessor entry point
ā ā āāā vite.ts # svelteAIVitePlugin() ā Vite plugin entry point
ā ā āāā transform.ts # Pure transform(source, filename): string
ā ā āāā parse.ts # Regex-based decorator + declaration extractor
ā ā āāā emit.ts # Code generation: registration call strings
ā āāā runtime/
ā āāā globalRegistry.ts # Module-level AIRegistry singleton
svelteAI-demo/ # SvelteKit demo app
āāā src/routes/demo/local-context/
āāā +page.svelte # Two-panel layout: live demo + inline dev docs
āāā ThermostatWidget.svelte # @ai-annotated component
āāā ChatPanel.svelte # Chat UI
āāā energy.svelte.ts # @ai-annotated shared state
āāā agent.ts # ToolLoopAgent wiring
āāā svelteai.ts # SvelteAI instance export
This is a Yarn workspaces monorepo. The demo app resolves svelteai directly from source via a Vite alias ā no build step needed during development.
yarn install
yarn dev # starts svelteAI-demo at http://localhost:5173
| Script | Action |
|---|---|
yarn dev |
Run the demo app with HMR |
yarn dev:lib |
Run the library dev server |
yarn build:lib |
Build the library to svelteAI/dist/ |
yarn build:demo |
Build the demo app |
yarn build |
Build both |
| Package | Version |
|---|---|
svelte |
^5.0.0 |
ai (Vercel AI SDK) |
^6.0.0 |
zod |
^4.0.0 |
Design/general_aim.md ā goals and motivationImplementation/plan.md ā full implementation plan, module architecture, transform engine specDocs/how-to-local-context.md ā local-context integration pattern with Vercel AI SDKDocs/how-to-context-digger.md ā lazy-loading pattern for larger apps