Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
123 commits
Select commit Hold shift + click to select a range
8ec94c1
perf: Improve error handling
aXenDeveloper Aug 2, 2026
9fd84d1
feat: Add content engine stage 1
aXenDeveloper Aug 2, 2026
43b02c2
perf: Improve stage 1
aXenDeveloper Aug 3, 2026
2fbdd3c
perf: Final hardening fixes for Universal Content Engine Stage 1
aXenDeveloper Aug 3, 2026
0ed70ae
perf: Final cleanup for Content Engine Stage 1
aXenDeveloper Aug 3, 2026
e7c0e3e
feat: Add content engine stage 2
aXenDeveloper Aug 3, 2026
236520a
feat: Publication foundation step 1
aXenDeveloper Aug 3, 2026
8afaae4
fix: Narrow publishedCondition publication columns
aXenDeveloper Aug 3, 2026
1c0577e
feat: Add Content Engine slug field
aXenDeveloper Aug 3, 2026
8602656
feat: Add public Content Type configuration and projection
aXenDeveloper Aug 3, 2026
cc5b3df
feat: Add public read-only Content Service
aXenDeveloper Aug 3, 2026
bf64d10
feat: Add generated public content routes
aXenDeveloper Aug 3, 2026
78acabc
feat: Add AdminCP publication actions
aXenDeveloper Aug 3, 2026
b097a3c
feat: Add public content cache invalidation
aXenDeveloper Aug 3, 2026
52cb302
docs: Complete Content Engine Stage 2 documentation
aXenDeveloper Aug 3, 2026
1e4738b
fix: Sort AdminCP tables by every column the API accepts
aXenDeveloper Aug 4, 2026
738838d
fix: Harden the public content surface for review
aXenDeveloper Aug 4, 2026
d33630c
fix: Suffix every backfilled slug, not just the ambiguous ones
aXenDeveloper Aug 4, 2026
fd178ec
feat: Add Content Engine Stage 3
aXenDeveloper Aug 4, 2026
7ffb794
fix: Preserve search document plugin ownership
aXenDeveloper Aug 4, 2026
93ad8ff
fix: Make search rebuild pagination source-aware
aXenDeveloper Aug 4, 2026
c894b74
fix: Report over-indexed search collections as stale
aXenDeveloper Aug 4, 2026
5090364
fix: Preserve literal search enabled types
aXenDeveloper Aug 4, 2026
c2f6e43
fix: Isolate search synchronization logging failures
aXenDeveloper Aug 4, 2026
bb47b4c
docs: Harden Content Engine search documentation
aXenDeveloper Aug 4, 2026
5b74a1e
fix: Keep the array result of SearchIndexer.load working
aXenDeveloper Aug 4, 2026
ebf5bd5
fix: Keep the stored owner of an orphaned search collection
aXenDeveloper Aug 4, 2026
8830b5c
docs: Document both search indexer load results
aXenDeveloper Aug 4, 2026
73d2c2f
fix: Report a search collection with no indexer as orphaned
aXenDeveloper Aug 4, 2026
efbaf05
fix: Refuse a scoped search rebuild with no registered indexer
aXenDeveloper Aug 4, 2026
c491187
feat: Add an explicit cleanup action for orphaned search collections
aXenDeveloper Aug 4, 2026
7f6af03
docs: Document orphaned search collections
aXenDeveloper Aug 4, 2026
15f9bcc
fix: Call a collection with no rebuild indexer unmanaged, not orphaned
aXenDeveloper Aug 4, 2026
90ead27
fix: Make the search cleanup audit log best effort
aXenDeveloper Aug 4, 2026
4917636
docs: Separate live indexing from rebuild support
aXenDeveloper Aug 4, 2026
1a23614
docs: Improve search docs
aXenDeveloper Aug 4, 2026
5ee6b92
feat: Stage 4 — Editorial Workflow
aXenDeveloper Aug 6, 2026
3a83916
fix: Fix final Stage 4 blockers
aXenDeveloper Aug 6, 2026
170a4b0
fix: Improve events and schedule
aXenDeveloper Aug 6, 2026
2682734
fix: schedula action call
aXenDeveloper Aug 6, 2026
3da0cd5
feat(content): add localization configuration and localized field types
aXenDeveloper Aug 6, 2026
3dd01ce
feat(content): partition the generated schemas and add translation co…
aXenDeveloper Aug 6, 2026
8177e7b
feat(content): generate a translation table per localized Content Type
aXenDeveloper Aug 6, 2026
7e8cadd
feat(content): resolve content locales against core_languages
aXenDeveloper Aug 6, 2026
e07a52a
feat(content): add the translation model and per-locale optimistic lo…
aXenDeveloper Aug 6, 2026
11ae724
feat(content): create a localized record and its default translation …
aXenDeveloper Aug 6, 2026
b2f3115
feat(content): add the generated translation routes
aXenDeveloper Aug 6, 2026
9938532
test(content): cover localization at the type, unit and route level
aXenDeveloper Aug 6, 2026
0baeab7
feat(example): add a localized Content Type fixture and its migration
aXenDeveloper Aug 6, 2026
e63b868
test(example): assert the generated localization schema and its Postg…
aXenDeveloper Aug 6, 2026
776aaee
docs(content): document the Stage 5A localization foundation
aXenDeveloper Aug 6, 2026
553f1e1
feat(content): add the localized editorial layer
aXenDeveloper Aug 6, 2026
8b9076e
feat(content): give the atomic create a translation revision
aXenDeveloper Aug 6, 2026
c478e46
test(content): cover the localized editorial layer
aXenDeveloper Aug 6, 2026
8d64da2
docs(content): document the localized editorial workflow
aXenDeveloper Aug 6, 2026
2156005
feat(content): add the locale-aware public read layer
aXenDeveloper Aug 7, 2026
4aac878
feat(content): index one search document per published translation
aXenDeveloper Aug 7, 2026
8d69fe9
fix: review and fix the remaining correctness issues in the stacked C…
aXenDeveloper Aug 8, 2026
8e34c62
feat(content): add advanced relation, group and repeatable definitions
aXenDeveloper Aug 8, 2026
1c3783e
feat(content): generate relational storage for advanced fields
aXenDeveloper Aug 8, 2026
72a874b
feat(content): integrate advanced fields with search, cache and events
aXenDeveloper Aug 8, 2026
996f542
feat(content): add the advanced-modeling example and its PostgreSQL s…
aXenDeveloper Aug 8, 2026
6bfb3d7
feat(content): declare self-relations with `self: true`
aXenDeveloper Aug 8, 2026
5ce0aa4
feat(admin): add the advanced modeling editors
aXenDeveloper Aug 8, 2026
6d8c27f
docs(content): document Advanced Modeling
aXenDeveloper Aug 8, 2026
d25f48e
test(content): cover the Stage 6 projections without a database
aXenDeveloper Aug 8, 2026
d3ca3c6
fix(content): resolve a self-relation's foreign key from the table it…
aXenDeveloper Aug 8, 2026
02c677a
fix(content): repair four Stage 6 correctness bugs
aXenDeveloper Aug 8, 2026
e3e8a26
fix(content): validate generated database identifiers across content …
aXenDeveloper Aug 8, 2026
9cf2072
feat(content): add effect-failure reporting, drift diagnostics and au…
aXenDeveloper Aug 8, 2026
7bbb775
test(content): expand the concurrency matrix against real Postgres
aXenDeveloper Aug 8, 2026
ebd119b
test(content): harden downstream failure, idempotency and search cons…
aXenDeveloper Aug 8, 2026
6a43bbf
fix(content): make cursor pagination's ordering total
aXenDeveloper Aug 8, 2026
04fdf4d
test(content): add integrity, schema-evolution and migration upgrade …
aXenDeveloper Aug 8, 2026
dd173d1
docs(content): document the production hardening guarantees
aXenDeveloper Aug 8, 2026
b1c942c
style(content): apply prettier to the new test suites
aXenDeveloper Aug 8, 2026
6040d9d
docs(content): state the one redundant cache invalidation rather than…
aXenDeveloper Aug 8, 2026
a242ad4
fix(pagination): make the cursor the ordered tuple, not the row id
aXenDeveloper Aug 8, 2026
8181a4e
feat(search): make drift diagnostics provider-aware, and page rebuild…
aXenDeveloper Aug 8, 2026
29ce1a4
fix(pagination): keep the cursor a historical position, and catch pro…
aXenDeveloper Aug 9, 2026
b4d75ae
fix(pagination): mint the cursor from the page query, and reject impo…
aXenDeveloper Aug 9, 2026
1682f8b
docs(pagination): state the temporal cursor domain as a limit, not an…
aXenDeveloper Aug 9, 2026
4af87fb
feat(content): add delivery definitions and validation
aXenDeveloper Aug 8, 2026
f6aec87
feat(content): add canonical URL and sitemap helpers
aXenDeveloper Aug 8, 2026
c34117d
feat(content): add slug history persistence
aXenDeveloper Aug 8, 2026
e8b881b
feat(content): add the delivery service and redirect resolver
aXenDeveloper Aug 8, 2026
8b778aa
feat(content): integrate redirects with editorial mutations
aXenDeveloper Aug 8, 2026
c05e7cc
feat(content): add the delivery events
aXenDeveloper Aug 8, 2026
9ec7171
feat(content): integrate delivery cache invalidation
aXenDeveloper Aug 8, 2026
f876de8
feat(content): add the delivery routes and error contracts
aXenDeveloper Aug 8, 2026
18d158a
feat(content): add the Next.js delivery helpers
aXenDeveloper Aug 8, 2026
de155a8
feat(admin): show content delivery metadata
aXenDeveloper Aug 8, 2026
4721e70
feat(example): add the delivery reference fixtures
aXenDeveloper Aug 8, 2026
ef6ee9c
test(content): cover delivery, redirects and the cache boundary
aXenDeveloper Aug 8, 2026
38f672e
test(content): add the Stage 8 PostgreSQL suite
aXenDeveloper Aug 8, 2026
33b300b
docs(content): document Content Delivery and SEO
aXenDeveloper Aug 8, 2026
59547a8
fix(example): satisfy the plugin's typed lint in the delivery suite
aXenDeveloper Aug 8, 2026
aca76b5
fix(content): resolve two delivery correctness bugs found in review
aXenDeveloper Aug 8, 2026
a74ea04
style(example): satisfy the plugin lint in the new delivery assertions
aXenDeveloper Aug 8, 2026
616b15e
fix(content): make delivery paths a globally unique namespace
aXenDeveloper Aug 8, 2026
c375de4
fix(content): require editorial for delivery.redirects
aXenDeveloper Aug 8, 2026
27a4af1
fix(content): expire the sitemap whenever its lastModified moves
aXenDeveloper Aug 8, 2026
8135c52
fix(content): carry the delivery tags across the revalidation bridge
aXenDeveloper Aug 8, 2026
7b51bf5
test(content): cover the sitemap lastModified invariant
aXenDeveloper Aug 8, 2026
8d75c78
docs(content): correct the sitemap cache and Editorial requirements
aXenDeveloper Aug 8, 2026
6c03810
test(content): cover the restore path in the sitemap audit
aXenDeveloper Aug 8, 2026
3ded615
fix(content): report delivery event failures, and audit the delivery …
aXenDeveloper Aug 9, 2026
549ca5a
fix(content): keep the first moved URL, settle unseen slugs, and agre…
aXenDeveloper Aug 9, 2026
dd43276
feat(content): support page and custom-layout admin forms
aXenDeveloper Aug 10, 2026
293c974
refactor(blog): migrate the blog onto the Content Engine
aXenDeveloper Aug 10, 2026
f5c60ac
docs(content): document admin form presentation and layouts
aXenDeveloper Aug 10, 2026
6063236
fix(blog): name categories in the article's picker
aXenDeveloper Aug 10, 2026
b2c8c84
Revert "fix(blog): name categories in the article's picker"
aXenDeveloper Aug 10, 2026
ae447f3
fix(content): keep native button semantics on the page-mode links
aXenDeveloper Aug 10, 2026
e19dbcd
feat: Move blog plugin to content engine
aXenDeveloper Aug 12, 2026
fdf1d86
refactor: List of items in admincp for content engine
aXenDeveloper Aug 13, 2026
d4e6799
fix: lang united in content system
aXenDeveloper Aug 14, 2026
7817399
docs: Refactor content engine docs
aXenDeveloper Aug 15, 2026
b35f81b
perf: delete lang dialog from content engine
aXenDeveloper Aug 15, 2026
2decd11
test: Remove unnecessary tests
aXenDeveloper Aug 16, 2026
35d6db2
perf: Add path support for content engine
aXenDeveloper Aug 16, 2026
1cfae19
test: Add missing tests
aXenDeveloper Aug 16, 2026
a28cc73
chore: clean up
aXenDeveloper Aug 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
5 changes: 4 additions & 1 deletion .github/workflows/build-lint-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ name: Build, Lint & Test

on:
pull_request:
branches: "*"
types:
- opened
- edited
- synchronize
- reopened

jobs:
build:
Expand Down Expand Up @@ -47,3 +47,6 @@ jobs:

- name: Run tests
run: pnpm test

- name: Run type tests
run: pnpm test:types
21 changes: 12 additions & 9 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ You are VitNode, an expert AI coding assistant. Follow repository conventions an

# React / Next.js

- Arrow functions for components never `React.FC`.
- Arrow functions for components - never `React.FC`.
- No `any`; use `unknown` as rarely as possible.
- Use `AutoForm` for forms instead of hand-built form components.
- `React.lazy` + `Suspense` for content-heavy dialogs (e.g. dialogs in forms).
Expand All @@ -25,16 +25,16 @@ import { Activity } from "react";

### Caching APIs

- `revalidateTag(tag, profile)` profile is required for SWR: `revalidateTag("blog-posts", "max")` (prefer `'max'`; also `'days'`, `'hours'`) or inline `{ revalidate: 3600 }`.
- `updateTag(\`user-${userId}\`)` Server Actions only, read-your-writes semantics.
- `refresh()` Server Actions only, refreshes uncached data, never touches the cache.
- `revalidateTag(tag, profile)` - profile is required for SWR: `revalidateTag("blog-posts", "max")` (prefer `'max'`; also `'days'`, `'hours'`) or inline `{ revalidate: 3600 }`.
- `updateTag(\`user-${userId}\`)` - Server Actions only, read-your-writes semantics.
- `refresh()` - Server Actions only, refreshes uncached data, never touches the cache.

# Coding Guidelines

- Always implement best practices for performance, security, and accessibility.
- Semantic HTML (`main`, `header`) with correct ARIA roles/attributes, `sr-only` for screen-reader-only text, and alt text on all images unless decorative or repetitive.
- Emit events for important actions (create, update, delete) so other components can react; use events instead of prop drilling or context. Document them in `apps/docs/content/docs/dev/events/built-in-events.mdx`.
- AI features use the Vercel AI SDK only resolve models via the `c.get("ai")` registry and call native SDK functions.
- AI features use the Vercel AI SDK only - resolve models via the `c.get("ai")` registry and call native SDK functions.

# Design

Expand All @@ -43,7 +43,7 @@ import { Activity } from "react";
- Exactly 3–5 colors total. Never use purple or violet prominently.
- If you override a background color, you MUST override its text color for contrast.
- Prefer semantic design tokens (`bg-background`, `text-foreground`).
- Use the Tailwind spacing scale (`p-4`, `mx-2`) never arbitrary values (`p-[16px]`).
- Use the Tailwind spacing scale (`p-4`, `mx-2`) - never arbitrary values (`p-[16px]`).
- Use `gap-*` classes for spacing. Never use `space-*`, and never mix margin/padding with gap on the same element.
- Use semantic (`items-center`, `justify-between`) and responsive (`md:grid-cols-2`) classes.
- No floats or absolute positioning unless absolutely necessary.
Expand All @@ -53,7 +53,7 @@ import { Activity } from "react";
# Documentation

- Document every new feature. Keep it simple, SEO-friendly, and understandable at any skill level.
- Friendly and lightly funny tone don't overdo it.
- Friendly and lightly funny tone - don't overdo it.
- Skip big comments; code is self-documenting.
- Request images with a comment: `// Image prompt: {here_prompt_to_generate_image}`
- Put install commands in tabbed code blocks with correct syntax highlighting:
Expand Down Expand Up @@ -81,5 +81,8 @@ npm i x
# Testing

- Admin login: `test@test.com` / `Test123!`
- Write and run vitest unit tests for all new features and bug fixes — skip only if vitest isn't configured.
- Don't write tests for trivial code unless they have complex logic or edge cases.
- Write and run vitest unit tests for all new features and bug fixes - skip only if vitest isn't configured.
- Don't write tests:
- for trivial code unless they have complex logic or edge cases
- for tests where it uses a database or external API
- how UI should be rendered (use playwright for that to write e2e tests)
1 change: 1 addition & 0 deletions apps/api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
"@types/react-dom": "^19.2.3",
"@types/ws": "^8.18.1",
"@vitnode/blog": "workspace:*",
"@vitnode/example": "workspace:*",
"@vitnode/config": "workspace:*",
"@vitnode/nodemailer": "workspace:*",
"dotenv": "^17.4.2",
Expand Down
3 changes: 2 additions & 1 deletion apps/api/src/vitnode.api.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { google } from "@ai-sdk/google";
import { blogApiPlugin } from "@vitnode/blog/config.api";
// import { LocalStorageAdapter } from "@vitnode/core/api/adapters/storage/local";
import { buildApiConfig } from "@vitnode/core/vitnode.config";
import { exampleApiPlugin } from "@vitnode/example/config.api";
import { NodeCronAdapter } from "@vitnode/node-cron";
import { NodemailerEmailAdapter } from "@vitnode/nodemailer";
// import { S3StorageAdapter } from "@vitnode/s3";
Expand All @@ -17,7 +18,7 @@ export const POSTGRES_URL =
process.env.POSTGRES_URL ?? "postgresql://root:root@localhost:5432/vitnode";

export const vitNodeApiConfig = buildApiConfig({
plugins: [blogApiPlugin()],
plugins: [blogApiPlugin(), exampleApiPlugin()],
ai: {
models: [
{
Expand Down
77 changes: 77 additions & 0 deletions apps/docs/content/docs/dev/advanced/queue.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,86 @@ import { TypeTable } from "fumadocs-ui/components/type-table";
type: "Date",
default: "now",
},
pluginId: {
description:
"Who owns the handler, when that is not the plugin handling the request. The worker resolves handlers by `${pluginId}:${name}`, so dispatching a core task from a plugin route needs this - otherwise nothing ever claims the row.",
type: "string",
default: "the requesting plugin, or @vitnode/core",
},
tx: {
description:
"Join an existing transaction instead of using the request handle. Needed whenever the row the task refers to is written in the same unit of work, or the queue row can commit while that row rolls back.",
type: "Transaction",
},
}}
/>

<Callout type="info" title="Both exist because of scheduled publishing">
The [Content Engine](/docs/dev/content-engine/publication-and-editorial#4-scheduled-publishing) books a schedule row
and its queue task in one transaction, from a plugin's route, against a task
core registers. That needs `tx` for atomicity and `pluginId` so the worker can
find the handler - and it is a shape any plugin can reuse.
</Callout>

## Tasks core ships

| Task | Attempts | What it does |
| --- | --- | --- |
| `send-email` | 3 | Delivers a rendered email through the configured provider |
| `rebuild-search-index` | 3 | Clears and rebuilds the [search](/docs/dev/content-engine/public-api-and-caching#2-full-text-search-indexing) index, whole or per collection |
| `content-schedule` | 3 | Runs one [scheduled publication](/docs/dev/content-engine/publication-and-editorial#4-scheduled-publishing). A cancelled, rescheduled or already-executed schedule is a no-op |
| `content-schedule-effects` | 5 | Emits the event, syncs search and expires the cache for a scheduled transition that has **already committed**. Never republishes |

`content-schedule` is worth reading as a pattern: its payload is
`{ scheduleId, generation }` and nothing else. Every real value is re-read from
the row under `FOR UPDATE`, so a task left over from a plan that has since
changed finds a mismatch and quietly does nothing - which is far more reliable
than trying to delete a queued row. It holds that lock from the claim all the
way to the commit, so a cancel arriving mid-flight waits and then honestly
reports that the schedule already ran.

### Why the effects are a second task

The pair is worth reading as a pattern too, because splitting them is the whole
point:

```text
content-schedule claim → publish → revision → settle → enqueue effects
── one transaction ──────────────────────────────────
content-schedule-effects event → search → cache bridge
```

A transition is a database write that either committed or did not. Announcing it
is three calls to systems a transaction cannot reach. Retrying them **together**
would re-run a publish that is idempotent - so the second run finds nothing
changed and skips the announcements entirely, which is how a scheduled unpublish
ends up permanently serving a page it should have expired.

The effects row is written inside the transition's transaction, so it exists if
and only if the transition committed, and it carries everything frozen rather
than re-reading a record that may have moved on.

All three have to land for the task to succeed - a listener that threw, a search
engine that refused the write, or any configured web origin that did not accept
its invalidation each fail the run, and the reasons are combined into one
`effectsError`. `EventsModel.emit()` never throws, so the task reads
`EventEmitResult.failures` rather than waiting for an exception that is not
coming.

Delivery is at-least-once: the search write and the cache expiry are idempotent,
but a listener can see one `published` twice, so a listener that must act once
keys off the `scheduleId` in the payload.

The event's envelope is stamped with the plugin that owns the **content type**,
not with core. Core owns the handler; `content.example.article.published`
belongs to the example plugin however it was triggered.

<Callout type="info" title="An announcement failure is not a publication failure">
The schedule stays `completed` and the reason lands in `effectsError` on the
schedule row. Nothing is ever moved back to `pending` because an event
bounced - the record really did publish.
</Callout>

## Retries and backoff

If a handler throws, the task is retried with an exponential backoff
Expand Down
29 changes: 23 additions & 6 deletions apps/docs/content/docs/dev/advanced/redis.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -88,26 +88,43 @@ export const vitNodeApiConfig = buildApiConfig({

### Options

The `redis` object accepts a `url` plus any
[ioredis](https://github.com/redis/ioredis) `RedisOptions`.
The `redis` object is a
[node-redis](https://github.com/redis/node-redis) `RedisClientOptions`.

import { TypeTable } from "fumadocs-ui/components/type-table";

<TypeTable
type={{
url: {
description:
"The Redis connection string (redis:// or rediss://). When omitted, ioredis connects to localhost:6379 using the other options.",
"The Redis connection string (redis:// or rediss://). When omitted, node-redis connects to localhost:6379 using the other options.",
type: "string",
},
"...RedisOptions": {
password: {
description:
"Any ioredis option, e.g. host, port, password, db, tls. enableOfflineQueue defaults to false so cache commands fail fast (and fall through) when Redis is unreachable - override it if you prefer queuing.",
type: "RedisOptions",
"Password used to authenticate. Pair it with username when your Redis uses ACL users.",
type: "string",
},
socket: {
description:
"Connection-level options: host, port, tls, connectTimeout, reconnectStrategy.",
type: "RedisSocketOptions",
},
"...RedisClientOptions": {
description:
"Any other node-redis option, e.g. database or name. disableOfflineQueue defaults to true so cache commands fail fast (and fall through) when Redis is unreachable - override it if you prefer queuing.",
type: "RedisClientOptions",
},
}}
/>

<Callout type="info" title="Connecting happens in the background">
VitNode opens the connection at boot without blocking startup, and node-redis
keeps retrying with an exponential backoff if Redis is unreachable. Requests
that land before the socket is ready behave exactly like a Redis outage: the
cache reports a miss and the request goes to the database.
</Callout>

## Using the cache

Access the cache in any route or model with `c.get("cache")`.
Expand Down
33 changes: 26 additions & 7 deletions apps/docs/content/docs/dev/ai/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Easily configure and resolve AI models in VitNode using the native

VitNode brings AI powers straight into your backend via the built-in [Vercel AI SDK](https://ai-sdk.dev/). Configure your models once in `buildApiConfig`, grab them in any route handler using `c.get("ai")`, and call native AI SDK functions directly. Zero wrappers, zero hassle! 🚀

AI is completely **optional** if you don't configure any models, VitNode won't touch any external services.
AI is completely **optional** - if you don't configure any models, VitNode won't touch any external services.

## Quick Setup

Expand Down Expand Up @@ -59,11 +59,23 @@ Define your model registry in `buildApiConfig`. The **first model** listed acts
export const vitNodeApiConfig = buildApiConfig({
ai: {
models: [
{ id: "default", name: "Claude Sonnet 5", model: "anthropic/claude-sonnet-5" },
{ id: "fast", name: "Claude Haiku 4.5", model: "anthropic/claude-haiku-4.5" },
{
id: "default",
name: "Claude Sonnet 5",
model: "anthropic/claude-sonnet-5",
},
{
id: "fast",
name: "Claude Haiku 4.5",
model: "anthropic/claude-haiku-4.5",
},
],
embeddingModels: [
{ id: "default", name: "Text Embedding 3 Small", model: "openai/text-embedding-3-small" },
{
id: "default",
name: "Text Embedding 3 Small",
model: "openai/text-embedding-3-small",
},
],
imageModels: [
{ id: "default", name: "GPT Image 1", model: "openai/gpt-image-1" },
Expand All @@ -78,8 +90,16 @@ import { anthropic } from "@ai-sdk/anthropic";
export const vitNodeApiConfig = buildApiConfig({
ai: {
models: [
{ id: "default", name: "Claude Sonnet 5", model: anthropic("claude-sonnet-5") },
{ id: "fast", name: "Claude Haiku 4.5", model: anthropic("claude-haiku-4.5") },
{
id: "default",
name: "Claude Sonnet 5",
model: anthropic("claude-sonnet-5"),
},
{
id: "fast",
name: "Claude Haiku 4.5",
model: anthropic("claude-haiku-4.5"),
},
],
},
});
Expand Down Expand Up @@ -115,4 +135,3 @@ import { TypeTable } from "fumadocs-ui/components/type-table";
/>

Ready to generate text or create embeddings? Check out the [Usage](/docs/dev/ai/usage) guide!

Loading
Loading