diff --git a/api/app/clients/AnthropicClient.js b/api/app/clients/AnthropicClient.js index 2b832f2e0c..0da331ced5 100644 --- a/api/app/clients/AnthropicClient.js +++ b/api/app/clients/AnthropicClient.js @@ -74,9 +74,6 @@ class AnthropicClient extends BaseClient { /** Whether to use Messages API or Completions API * @type {boolean} */ this.useMessages; - /** Whether or not the model is limited to the legacy amount of output tokens - * @type {boolean} */ - this.isLegacyOutput; /** Whether or not the model supports Prompt Caching * @type {boolean} */ this.supportsCacheControl; @@ -118,13 +115,16 @@ class AnthropicClient extends BaseClient { const modelMatch = matchModelName(this.modelOptions.model, EModelEndpoint.anthropic); this.isClaudeLatest = /claude-[3-9]/.test(modelMatch) || /claude-(?:sonnet|opus|haiku)-[4-9]/.test(modelMatch); - this.isLegacyOutput = !( - /claude-3[-.]5-sonnet/.test(modelMatch) || /claude-3[-.]7/.test(modelMatch) + const isLegacyOutput = !( + /claude-3[-.]5-sonnet/.test(modelMatch) || + /claude-3[-.]7/.test(modelMatch) || + /claude-(?:sonnet|opus|haiku)-[4-9]/.test(modelMatch) || + /claude-[4-9]/.test(modelMatch) ); this.supportsCacheControl = this.options.promptCache && checkPromptCacheSupport(modelMatch); if ( - this.isLegacyOutput && + isLegacyOutput && this.modelOptions.maxOutputTokens && this.modelOptions.maxOutputTokens > legacy.maxOutputTokens.default ) { diff --git a/api/app/clients/specs/AnthropicClient.test.js b/api/app/clients/specs/AnthropicClient.test.js index 5f331e8d3c..9867859087 100644 --- a/api/app/clients/specs/AnthropicClient.test.js +++ b/api/app/clients/specs/AnthropicClient.test.js @@ -514,6 +514,34 @@ describe('AnthropicClient', () => { expect(client.modelOptions.maxOutputTokens).toBe(highTokenValue); }); + it('should not cap maxOutputTokens for Claude 4 Sonnet models', () => { + const client = new AnthropicClient('test-api-key'); + const highTokenValue = anthropicSettings.legacy.maxOutputTokens.default * 10; // 40,960 tokens + + client.setOptions({ + modelOptions: { + model: 'claude-sonnet-4-20250514', + maxOutputTokens: highTokenValue, + }, + }); + + expect(client.modelOptions.maxOutputTokens).toBe(highTokenValue); + }); + + it('should not cap maxOutputTokens for Claude 4 Opus models', () => { + const client = new AnthropicClient('test-api-key'); + const highTokenValue = anthropicSettings.legacy.maxOutputTokens.default * 6; // 24,576 tokens (under 32K limit) + + client.setOptions({ + modelOptions: { + model: 'claude-opus-4-20250514', + maxOutputTokens: highTokenValue, + }, + }); + + expect(client.modelOptions.maxOutputTokens).toBe(highTokenValue); + }); + it('should cap maxOutputTokens for Claude 3.5 Haiku models', () => { const client = new AnthropicClient('test-api-key'); const highTokenValue = anthropicSettings.legacy.maxOutputTokens.default * 2; diff --git a/api/server/cleanup.js b/api/server/cleanup.js index 93ae8d805e..5bf336eed5 100644 --- a/api/server/cleanup.js +++ b/api/server/cleanup.js @@ -140,9 +140,6 @@ function disposeClient(client) { if (client.useMessages !== undefined) { client.useMessages = null; } - if (client.isLegacyOutput !== undefined) { - client.isLegacyOutput = null; - } if (client.supportsCacheControl !== undefined) { client.supportsCacheControl = null; } diff --git a/api/server/index.spec.js b/api/server/index.spec.js index 493229c2f4..25b5ab9f03 100644 --- a/api/server/index.spec.js +++ b/api/server/index.spec.js @@ -4,6 +4,10 @@ const request = require('supertest'); const { MongoMemoryServer } = require('mongodb-memory-server'); const mongoose = require('mongoose'); +jest.mock('~/server/services/Config/loadCustomConfig', () => { + return jest.fn(() => Promise.resolve({})); +}); + describe('Server Configuration', () => { // Increase the default timeout to allow for Mongo cleanup jest.setTimeout(30_000); diff --git a/api/server/routes/oauth.js b/api/server/routes/oauth.js index 2336ac023d..9915390a5d 100644 --- a/api/server/routes/oauth.js +++ b/api/server/routes/oauth.js @@ -1,6 +1,7 @@ // file deepcode ignore NoRateLimitingForLogin: Rate limiting is handled by the `loginLimiter` middleware const express = require('express'); const passport = require('passport'); +const { randomState } = require('openid-client'); const { checkBan, logHeaders, @@ -9,8 +10,8 @@ const { checkDomainAllowed, } = require('~/server/middleware'); const { setAuthTokens, setOpenIDAuthTokens } = require('~/server/services/AuthService'); -const { logger } = require('~/config'); const { isEnabled } = require('~/server/utils'); +const { logger } = require('~/config'); const router = express.Router(); @@ -103,12 +104,12 @@ router.get( /** * OpenID Routes */ -router.get( - '/openid', - passport.authenticate('openid', { +router.get('/openid', (req, res, next) => { + return passport.authenticate('openid', { session: false, - }), -); + state: randomState(), + })(req, res, next); +}); router.get( '/openid/callback', diff --git a/api/strategies/openidStrategy.js b/api/strategies/openidStrategy.js index 92b225b20a..ea109358d7 100644 --- a/api/strategies/openidStrategy.js +++ b/api/strategies/openidStrategy.js @@ -28,6 +28,13 @@ class CustomOpenIDStrategy extends OpenIDStrategy { const hostAndProtocol = process.env.DOMAIN_SERVER; return new URL(`${hostAndProtocol}${req.originalUrl ?? req.url}`); } + authorizationRequestParams(req, options) { + const params = super.authorizationRequestParams(req, options); + if (options?.state && !params.has('state')) { + params.set('state', options.state); + } + return params; + } } /** diff --git a/client/src/components/SidePanel/Agents/ModelPanel.tsx b/client/src/components/SidePanel/Agents/ModelPanel.tsx index 234cccbaad..9b4b12cf67 100644 --- a/client/src/components/SidePanel/Agents/ModelPanel.tsx +++ b/client/src/components/SidePanel/Agents/ModelPanel.tsx @@ -2,10 +2,10 @@ import React, { useMemo, useEffect } from 'react'; import { ChevronLeft, RotateCcw } from 'lucide-react'; import { useFormContext, useWatch, Controller } from 'react-hook-form'; import { - getSettingsKeys, alternateName, - agentParamSettings, + getSettingsKeys, SettingDefinition, + agentParamSettings, } from 'librechat-data-provider'; import type * as t from 'librechat-data-provider'; import type { AgentForm, AgentModelPanelProps, StringOption } from '~/common'; @@ -211,7 +211,7 @@ export default function ModelPanel({ {/* Model Parameters */} {parameters && (