Skip to content

Releases: VoltAgent/voltagent

@voltagent/[email protected]

23 Oct 00:41
91405dd

Choose a tag to compare

Minor Changes

Patch Changes

@voltagent/[email protected]

23 Oct 18:56
aa2037c

Choose a tag to compare

Patch Changes

@voltagent/[email protected]

23 Oct 00:41
91405dd

Choose a tag to compare

Patch Changes

  • #719 3a1d214 Thanks @marinoska! - Strip providerMetadata from text parts before calling convertToModelMessages to prevent invalid providerOptions in the resulting ModelMessage[].

@voltagent/[email protected]

22 Oct 22:19
0966464

Choose a tag to compare

Patch Changes

@voltagent/[email protected]

22 Oct 16:41
0d164b6

Choose a tag to compare

Patch Changes

  • #714 f20cdf1 Thanks @{...}! - fix: auth middleware now preserves conversationId and all client options

    The Problem

    When using custom auth providers with VoltAgent, the auth middleware was completely replacing the body.options object instead of merging with it. This caused critical client-provided options to be lost, including:

    • conversationId - essential for conversation continuity and hooks
    • temperature, 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 options object containing only auth-related fields (context.user and userId), completely discarding the original body.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 (like onStart, 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.options instead 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 correct conversationId from 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: userId uses user.iduser.subbody.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]

22 Oct 22:19
0966464

Choose a tag to compare

Patch Changes

@voltagent/[email protected]

21 Oct 19:40
4cacb1b

Choose a tag to compare

Patch Changes

@voltagent/[email protected]

21 Oct 16:59
2f74b54

Choose a tag to compare

Patch Changes

  • #709 8b838ec Thanks @omeraplak! - feat: add defaultPrivate option to AuthProvider for protecting all routes by default

    The Problem

    When using VoltAgent with third-party auth providers (like Clerk, Auth0, or custom providers), custom routes added via configureApp were public by default. This meant:

    • Only routes explicitly in PROTECTED_ROUTES required authentication
    • Custom endpoints needed manual middleware to be protected
    • The publicRoutes property 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 defaultPrivate option to AuthProvider interface, 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 in publicRoutes

    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 publicRoutes to selectively allow access
  • 5a0728d Thanks @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 → userConfiguredCors stayed false
    • 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
  • Updated dependencies [8b838ec]:

@voltagent/[email protected]

21 Oct 19:40
4cacb1b

Choose a tag to compare

Patch Changes

@voltagent/[email protected]

21 Oct 16:59
2f74b54

Choose a tag to compare

Patch Changes

  • #709 8b838ec Thanks @omeraplak! - feat: add defaultPrivate option to AuthProvider for protecting all routes by default

    The Problem

    When using VoltAgent with third-party auth providers (like Clerk, Auth0, or custom providers), custom routes added via configureApp were public by default. This meant:

    • Only routes explicitly in PROTECTED_ROUTES required authentication
    • Custom endpoints needed manual middleware to be protected
    • The publicRoutes property 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 defaultPrivate option to AuthProvider interface, 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 in publicRoutes

    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 publicRoutes to selectively allow access