Releases: VoltAgent/voltagent
@voltagent/[email protected]
@voltagent/[email protected]
Patch Changes
- #724
efe4be6Thanks @marinoska! - Temporary fix for providerMetadata bug: vercel/ai#9756
@voltagent/[email protected]
Patch Changes
- #719
3a1d214Thanks @marinoska! - Strip providerMetadata from text parts before calling convertToModelMessages to prevent invalid providerOptions in the resulting ModelMessage[].
@voltagent/[email protected]
Patch Changes
-
907cc30Thanks @omeraplak! - fix: @voltagent/core dependency -
Updated dependencies [
907cc30]:- @voltagent/[email protected]
@voltagent/[email protected]
Patch Changes
-
#714
f20cdf1Thanks @{...}! - fix: auth middleware now preserves conversationId and all client optionsThe Problem
When using custom auth providers with VoltAgent, the auth middleware was completely replacing the
body.optionsobject instead of merging with it. This caused critical client-provided options to be lost, including:conversationId- essential for conversation continuity and hookstemperature,maxSteps,topP- LLM configuration parameters- Any other options sent by the client in the request body
This happened because the middleware created a brand new
optionsobject containing only auth-related fields (context.useranduserId), completely discarding the originalbody.options.Example of the bug:
// Client sends: { input: "Hello", options: { conversationId: "conv-abc-123", temperature: 0.7 } } // After auth middleware (BEFORE FIX): { input: "Hello", options: { // ❌ conversationId LOST! // ❌ temperature LOST! context: { user: {...} }, userId: "user-123" } } // Result: conversationId missing in onStart hook's context
This was especially problematic when:
- Using hooks that depend on
conversationId(likeonStart,onEnd) - Configuring LLM parameters from the client side
- Tracking conversations across multiple agent calls
The Solution
Changed the auth middleware to merge auth data into the existing
body.optionsinstead of replacing it. Now all client options are preserved while auth context is properly added.After the fix:
// Client sends: { input: "Hello", options: { conversationId: "conv-abc-123", temperature: 0.7 } } // After auth middleware (AFTER FIX): { input: "Hello", options: { ...body.options, // ✅ All original options preserved conversationId: "conv-abc-123", // ✅ Preserved temperature: 0.7, // ✅ Preserved context: { ...body.options?.context, // ✅ Existing context merged // ✅ Auth user added }, userId: "user-123" // ✅ Auth userId added } } // Result: conversationId properly available in hooks!
Technical Changes
Before (packages/server-hono/src/auth/middleware.ts:82-90):
options: { context: { ...body.context, user, }, userId: user.id || user.sub, } // ❌ Creates NEW options object, loses body.options
After:
options: { ...body.options, // ✅ Preserve all existing options context: { ...body.options?.context, // ✅ Merge existing context ...body.context, user, }, userId: user.id || user.sub, } // ✅ Merges auth data into existing options
Impact
- ✅ Fixes missing conversationId in hooks:
onStart,onEnd, and other hooks now receive the correctconversationIdfrom client - ✅ Preserves LLM configuration: Client-side
temperature,maxSteps,topP, etc. are no longer lost - ✅ Context merging works correctly: Both custom context and auth user context coexist
- ✅ Backward compatible: Existing code continues to work, only fixes the broken behavior
- ✅ Proper fallback chain:
userIdusesuser.id→user.sub→body.options.userId
Testing
Added comprehensive test suite (
packages/server-hono/src/auth/middleware.spec.ts) with 12 test cases covering:- conversationId preservation
- Multiple options preservation
- Context merging
- userId priority logic
- Empty options handling
- Public routes
- Authentication failures
All tests passing ✅
@voltagent/[email protected]
Patch Changes
907cc30Thanks @omeraplak! - fix: @voltagent/core dependency
@voltagent/[email protected]
Patch Changes
- Updated dependencies [
461ecec]:- @voltagent/[email protected]
- @voltagent/[email protected]
@voltagent/[email protected]
Patch Changes
-
#709
8b838ecThanks @omeraplak! - feat: add defaultPrivate option to AuthProvider for protecting all routes by defaultThe Problem
When using VoltAgent with third-party auth providers (like Clerk, Auth0, or custom providers), custom routes added via
configureAppwere public by default. This meant:- Only routes explicitly in
PROTECTED_ROUTESrequired authentication - Custom endpoints needed manual middleware to be protected
- The
publicRoutesproperty couldn't make all routes private by default
This was especially problematic when integrating with enterprise auth systems where security-by-default is expected.
The Solution
Added
defaultPrivateoption toAuthProviderinterface, enabling two authentication modes:- Opt-In Mode (default,
defaultPrivate: false): Only specific routes require auth - Opt-Out Mode (
defaultPrivate: true): All routes require auth unless explicitly listed inpublicRoutes
Usage Example
Protecting All Routes with Clerk
import { VoltAgent } from "@voltagent/core"; import { honoServer, jwtAuth } from "@voltagent/server-hono"; new VoltAgent({ agents: { myAgent }, server: honoServer({ auth: jwtAuth({ secret: process.env.CLERK_JWT_KEY, defaultPrivate: true, // 🔒 Protect all routes by default publicRoutes: ["GET /health", "POST /webhooks/clerk"], mapUser: (payload) => ({ id: payload.sub, email: payload.email, }), }), configureApp: (app) => { // ✅ Public (in publicRoutes) app.get("/health", (c) => c.json({ status: "ok" })); // 🔒 Protected automatically (defaultPrivate: true) app.get("/api/user/data", (c) => { const user = c.get("authenticatedUser"); return c.json({ user }); }); }, }), });
Default Behavior (Backward Compatible)
// Without defaultPrivate, behavior is unchanged auth: jwtAuth({ secret: process.env.JWT_SECRET, // defaultPrivate: false (default) }); // Custom routes are public unless you add your own middleware configureApp: (app) => { app.get("/api/data", (c) => { // This is PUBLIC by default return c.json({ data: "anyone can access" }); }); };
Benefits
- ✅ Fail-safe security: Routes are protected by default when enabled
- ✅ No manual middleware: Custom endpoints automatically protected
- ✅ Perfect for third-party auth: Ideal for Clerk, Auth0, Supabase
- ✅ Backward compatible: No breaking changes, opt-in feature
- ✅ Fine-grained control: Use
publicRoutesto selectively allow access
- Only routes explicitly in
-
5a0728dThanks @omeraplak! - fix: correct CORS middleware detection to use actual function name 'cors2'Fixed a critical bug where custom CORS middleware was not being properly detected, causing both custom and default CORS to be applied simultaneously. This resulted in the default CORS (
origin: "*") overwriting custom CORS headers on actual POST/GET requests, while OPTIONS (preflight) requests worked correctly.The Problem
The middleware detection logic was checking for
middleware.name === "cors", but Hono's cors middleware function is actually named"cors2". This caused:- Detection to always fail →
userConfiguredCorsstayedfalse - Default CORS (
app.use("*", cors())) was applied even when users configured custom CORS - Both middlewares executed: custom CORS on specific paths + default CORS on
"*" - OPTIONS requests returned correct custom CORS headers ✅
- POST/GET requests had custom headers overwritten by default CORS (
*) ❌
The Solution
Updated the detection logic to check for the actual function name:
// Before: middleware.name === "cors" // After: middleware.name === "cors2"
Now when users configure custom CORS in
configureApp, it's properly detected and default CORS is skipped entirely.Impact
- Custom CORS configurations now work correctly for all request types (OPTIONS, POST, GET, etc.)
- No more default CORS overwriting custom CORS headers
- Fixes browser CORS errors when using custom origins with credentials
- Maintains backward compatibility - default CORS still applies when no custom CORS is configured
Example
This now works as expected:
import { VoltAgent } from "@voltagent/core"; import { honoServer } from "@voltagent/server-hono"; import { cors } from "hono/cors"; new VoltAgent({ agents: { myAgent }, server: honoServer({ configureApp: (app) => { app.use( "/agents/*", cors({ origin: "http://localhost:3001", credentials: true, }) ); }, }), });
Both OPTIONS and POST requests now return:
Access-Control-Allow-Origin: http://localhost:3001✅Access-Control-Allow-Credentials: true✅
- Detection to always fail →
-
Updated dependencies [
8b838ec]:- @voltagent/[email protected]
@voltagent/[email protected]
Patch Changes
- Updated dependencies [
461ecec]:- @voltagent/[email protected]
@voltagent/[email protected]
Patch Changes
-
#709
8b838ecThanks @omeraplak! - feat: add defaultPrivate option to AuthProvider for protecting all routes by defaultThe Problem
When using VoltAgent with third-party auth providers (like Clerk, Auth0, or custom providers), custom routes added via
configureAppwere public by default. This meant:- Only routes explicitly in
PROTECTED_ROUTESrequired authentication - Custom endpoints needed manual middleware to be protected
- The
publicRoutesproperty couldn't make all routes private by default
This was especially problematic when integrating with enterprise auth systems where security-by-default is expected.
The Solution
Added
defaultPrivateoption toAuthProviderinterface, enabling two authentication modes:- Opt-In Mode (default,
defaultPrivate: false): Only specific routes require auth - Opt-Out Mode (
defaultPrivate: true): All routes require auth unless explicitly listed inpublicRoutes
Usage Example
Protecting All Routes with Clerk
import { VoltAgent } from "@voltagent/core"; import { honoServer, jwtAuth } from "@voltagent/server-hono"; new VoltAgent({ agents: { myAgent }, server: honoServer({ auth: jwtAuth({ secret: process.env.CLERK_JWT_KEY, defaultPrivate: true, // 🔒 Protect all routes by default publicRoutes: ["GET /health", "POST /webhooks/clerk"], mapUser: (payload) => ({ id: payload.sub, email: payload.email, }), }), configureApp: (app) => { // ✅ Public (in publicRoutes) app.get("/health", (c) => c.json({ status: "ok" })); // 🔒 Protected automatically (defaultPrivate: true) app.get("/api/user/data", (c) => { const user = c.get("authenticatedUser"); return c.json({ user }); }); }, }), });
Default Behavior (Backward Compatible)
// Without defaultPrivate, behavior is unchanged auth: jwtAuth({ secret: process.env.JWT_SECRET, // defaultPrivate: false (default) }); // Custom routes are public unless you add your own middleware configureApp: (app) => { app.get("/api/data", (c) => { // This is PUBLIC by default return c.json({ data: "anyone can access" }); }); };
Benefits
- ✅ Fail-safe security: Routes are protected by default when enabled
- ✅ No manual middleware: Custom endpoints automatically protected
- ✅ Perfect for third-party auth: Ideal for Clerk, Auth0, Supabase
- ✅ Backward compatible: No breaking changes, opt-in feature
- ✅ Fine-grained control: Use
publicRoutesto selectively allow access
- Only routes explicitly in