mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-04-07 00:15:23 +02:00
Merge branch 'main' into style/clean-copied-text
This commit is contained in:
commit
d8de1fcd4f
12 changed files with 530 additions and 24 deletions
|
|
@ -136,6 +136,7 @@ const tokenValues = Object.assign(
|
|||
'claude-3.7-sonnet': { prompt: 3, completion: 15 },
|
||||
'claude-haiku-4-5': { prompt: 1, completion: 5 },
|
||||
'claude-opus-4': { prompt: 15, completion: 75 },
|
||||
'claude-opus-4-5': { prompt: 5, completion: 25 },
|
||||
'claude-sonnet-4': { prompt: 3, completion: 15 },
|
||||
'command-r': { prompt: 0.5, completion: 1.5 },
|
||||
'command-r-plus': { prompt: 3, completion: 15 },
|
||||
|
|
@ -238,8 +239,10 @@ const cacheTokenValues = {
|
|||
'claude-3.5-haiku': { write: 1, read: 0.08 },
|
||||
'claude-3-5-haiku': { write: 1, read: 0.08 },
|
||||
'claude-3-haiku': { write: 0.3, read: 0.03 },
|
||||
'claude-haiku-4-5': { write: 1.25, read: 0.1 },
|
||||
'claude-sonnet-4': { write: 3.75, read: 0.3 },
|
||||
'claude-opus-4': { write: 18.75, read: 1.5 },
|
||||
'claude-opus-4-5': { write: 6.25, read: 0.5 },
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1372,6 +1372,15 @@ describe('Claude Model Tests', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should return correct prompt and completion rates for Claude Opus 4.5', () => {
|
||||
expect(getMultiplier({ model: 'claude-opus-4-5', tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-opus-4-5'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model: 'claude-opus-4-5', tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-opus-4-5'].completion,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Claude Haiku 4.5 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-haiku-4-5',
|
||||
|
|
@ -1394,6 +1403,28 @@ describe('Claude Model Tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle Claude Opus 4.5 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-opus-4-5',
|
||||
'claude-opus-4-5-20250420',
|
||||
'claude-opus-4-5-latest',
|
||||
'anthropic/claude-opus-4-5',
|
||||
'claude-opus-4-5/anthropic',
|
||||
'claude-opus-4-5-preview',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
const valueKey = getValueKey(model);
|
||||
expect(valueKey).toBe('claude-opus-4-5');
|
||||
expect(getMultiplier({ model, tokenType: 'prompt' })).toBe(
|
||||
tokenValues['claude-opus-4-5'].prompt,
|
||||
);
|
||||
expect(getMultiplier({ model, tokenType: 'completion' })).toBe(
|
||||
tokenValues['claude-opus-4-5'].completion,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle Claude 4 model name variations with different prefixes and suffixes', () => {
|
||||
const modelVariations = [
|
||||
'claude-sonnet-4',
|
||||
|
|
@ -1440,6 +1471,15 @@ describe('Claude Model Tests', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should return correct cache rates for Claude Opus 4.5', () => {
|
||||
expect(getCacheMultiplier({ model: 'claude-opus-4-5', cacheType: 'write' })).toBe(
|
||||
cacheTokenValues['claude-opus-4-5'].write,
|
||||
);
|
||||
expect(getCacheMultiplier({ model: 'claude-opus-4-5', cacheType: 'read' })).toBe(
|
||||
cacheTokenValues['claude-opus-4-5'].read,
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Claude 4 model cache rates with different prefixes and suffixes', () => {
|
||||
const modelVariations = [
|
||||
'claude-sonnet-4',
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
"@langchain/google-genai": "^0.2.13",
|
||||
"@langchain/google-vertexai": "^0.2.13",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^3.0.30",
|
||||
"@librechat/agents": "^3.0.32",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
|
|
|
|||
|
|
@ -864,6 +864,15 @@ describe('Claude Model Tests', () => {
|
|||
);
|
||||
});
|
||||
|
||||
it('should return correct context length for Claude Opus 4.5', () => {
|
||||
expect(getModelMaxTokens('claude-opus-4-5', EModelEndpoint.anthropic)).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-opus-4-5'],
|
||||
);
|
||||
expect(getModelMaxTokens('claude-opus-4-5')).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-opus-4-5'],
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle Claude Haiku 4.5 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-haiku-4-5',
|
||||
|
|
@ -883,6 +892,25 @@ describe('Claude Model Tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should handle Claude Opus 4.5 model name variations', () => {
|
||||
const modelVariations = [
|
||||
'claude-opus-4-5',
|
||||
'claude-opus-4-5-20250420',
|
||||
'claude-opus-4-5-latest',
|
||||
'anthropic/claude-opus-4-5',
|
||||
'claude-opus-4-5/anthropic',
|
||||
'claude-opus-4-5-preview',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
const modelKey = findMatchingPattern(model, maxTokensMap[EModelEndpoint.anthropic]);
|
||||
expect(modelKey).toBe('claude-opus-4-5');
|
||||
expect(getModelMaxTokens(model, EModelEndpoint.anthropic)).toBe(
|
||||
maxTokensMap[EModelEndpoint.anthropic]['claude-opus-4-5'],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('should match model names correctly for Claude Haiku 4.5', () => {
|
||||
const modelVariations = [
|
||||
'claude-haiku-4-5',
|
||||
|
|
@ -898,6 +926,21 @@ describe('Claude Model Tests', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should match model names correctly for Claude Opus 4.5', () => {
|
||||
const modelVariations = [
|
||||
'claude-opus-4-5',
|
||||
'claude-opus-4-5-20250420',
|
||||
'claude-opus-4-5-latest',
|
||||
'anthropic/claude-opus-4-5',
|
||||
'claude-opus-4-5/anthropic',
|
||||
'claude-opus-4-5-preview',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
expect(matchModelName(model, EModelEndpoint.anthropic)).toBe('claude-opus-4-5');
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle Claude 4 model name variations with different prefixes and suffixes', () => {
|
||||
const modelVariations = [
|
||||
'claude-sonnet-4',
|
||||
|
|
|
|||
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -61,7 +61,7 @@
|
|||
"@langchain/google-genai": "^0.2.13",
|
||||
"@langchain/google-vertexai": "^0.2.13",
|
||||
"@langchain/textsplitters": "^0.1.0",
|
||||
"@librechat/agents": "^3.0.30",
|
||||
"@librechat/agents": "^3.0.32",
|
||||
"@librechat/api": "*",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@microsoft/microsoft-graph-client": "^3.0.7",
|
||||
|
|
@ -16263,9 +16263,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/@librechat/agents": {
|
||||
"version": "3.0.30",
|
||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.30.tgz",
|
||||
"integrity": "sha512-EJLo6GqXH0tefpdrhMnoiVwhFupS3K9I0xXAKCn+6greIhdZV1IlsYRxu+NXoK+xaFJBJeMkAb0E9Oih61NxoA==",
|
||||
"version": "3.0.32",
|
||||
"resolved": "https://registry.npmjs.org/@librechat/agents/-/agents-3.0.32.tgz",
|
||||
"integrity": "sha512-6g7lLuEpot3vrHqgDZzPyrISvUshyWRINdnvkQsQf951QjdcFZuqmQ9mpUcaNQbczHBS4k5DLYIBUK+qzG/eiw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@langchain/anthropic": "^0.3.26",
|
||||
|
|
@ -46180,7 +46180,7 @@
|
|||
"@azure/storage-blob": "^12.27.0",
|
||||
"@keyv/redis": "^4.3.3",
|
||||
"@langchain/core": "^0.3.79",
|
||||
"@librechat/agents": "^3.0.30",
|
||||
"@librechat/agents": "^3.0.32",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.21.0",
|
||||
"axios": "^1.12.1",
|
||||
|
|
|
|||
|
|
@ -84,7 +84,7 @@
|
|||
"@azure/storage-blob": "^12.27.0",
|
||||
"@keyv/redis": "^4.3.3",
|
||||
"@langchain/core": "^0.3.79",
|
||||
"@librechat/agents": "^3.0.30",
|
||||
"@librechat/agents": "^3.0.32",
|
||||
"@librechat/data-schemas": "*",
|
||||
"@modelcontextprotocol/sdk": "^1.21.0",
|
||||
"axios": "^1.12.1",
|
||||
|
|
|
|||
|
|
@ -122,6 +122,38 @@ describe('getLLMConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should add "prompt-caching" beta header for claude-opus-4-5 model', () => {
|
||||
const modelOptions = {
|
||||
model: 'claude-opus-4-5',
|
||||
promptCache: true,
|
||||
};
|
||||
const result = getLLMConfig('test-key', { modelOptions });
|
||||
const clientOptions = result.llmConfig.clientOptions;
|
||||
expect(clientOptions?.defaultHeaders).toBeDefined();
|
||||
expect(clientOptions?.defaultHeaders).toHaveProperty('anthropic-beta');
|
||||
const defaultHeaders = clientOptions?.defaultHeaders as Record<string, string>;
|
||||
expect(defaultHeaders['anthropic-beta']).toBe('prompt-caching-2024-07-31');
|
||||
});
|
||||
|
||||
it('should add "prompt-caching" beta header for claude-opus-4-5 model formats', () => {
|
||||
const modelVariations = [
|
||||
'claude-opus-4-5',
|
||||
'claude-opus-4-5-20250420',
|
||||
'claude-opus-4.5',
|
||||
'anthropic/claude-opus-4-5',
|
||||
];
|
||||
|
||||
modelVariations.forEach((model) => {
|
||||
const modelOptions = { model, promptCache: true };
|
||||
const result = getLLMConfig('test-key', { modelOptions });
|
||||
const clientOptions = result.llmConfig.clientOptions;
|
||||
expect(clientOptions?.defaultHeaders).toBeDefined();
|
||||
expect(clientOptions?.defaultHeaders).toHaveProperty('anthropic-beta');
|
||||
const defaultHeaders = clientOptions?.defaultHeaders as Record<string, string>;
|
||||
expect(defaultHeaders['anthropic-beta']).toBe('prompt-caching-2024-07-31');
|
||||
});
|
||||
});
|
||||
|
||||
it('should NOT include topK and topP for Claude-3.7 models with thinking enabled (decimal notation)', () => {
|
||||
const result = getLLMConfig('test-api-key', {
|
||||
modelOptions: {
|
||||
|
|
@ -707,6 +739,7 @@ describe('getLLMConfig', () => {
|
|||
{ model: 'claude-haiku-4-5-20251001', expectedMaxTokens: 64000 },
|
||||
{ model: 'claude-opus-4-1', expectedMaxTokens: 32000 },
|
||||
{ model: 'claude-opus-4-1-20250805', expectedMaxTokens: 32000 },
|
||||
{ model: 'claude-opus-4-5', expectedMaxTokens: 64000 },
|
||||
{ model: 'claude-sonnet-4-20250514', expectedMaxTokens: 64000 },
|
||||
{ model: 'claude-opus-4-0', expectedMaxTokens: 32000 },
|
||||
];
|
||||
|
|
@ -771,6 +804,17 @@ describe('getLLMConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should default Claude Opus 4.5 model to 64K tokens', () => {
|
||||
const testCases = ['claude-opus-4-5', 'claude-opus-4-5-20250420', 'claude-opus-4.5'];
|
||||
|
||||
testCases.forEach((model) => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model },
|
||||
});
|
||||
expect(result.llmConfig.maxTokens).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
it('should default future Claude 4.x Sonnet/Haiku models to 64K (future-proofing)', () => {
|
||||
const testCases = ['claude-sonnet-4-20250514', 'claude-sonnet-4-9', 'claude-haiku-4-8'];
|
||||
|
||||
|
|
@ -782,15 +826,24 @@ describe('getLLMConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should default future Claude 4.x Opus models to 32K (future-proofing)', () => {
|
||||
const testCases = ['claude-opus-4-0', 'claude-opus-4-7'];
|
||||
|
||||
testCases.forEach((model) => {
|
||||
it('should default future Claude 4.x Opus models (future-proofing)', () => {
|
||||
// opus-4-0 through opus-4-4 get 32K
|
||||
const opus32kModels = ['claude-opus-4-0', 'claude-opus-4-1', 'claude-opus-4-4'];
|
||||
opus32kModels.forEach((model) => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model },
|
||||
});
|
||||
expect(result.llmConfig.maxTokens).toBe(32000);
|
||||
});
|
||||
|
||||
// opus-4-5+ get 64K
|
||||
const opus64kModels = ['claude-opus-4-5', 'claude-opus-4-7', 'claude-opus-4-10'];
|
||||
opus64kModels.forEach((model) => {
|
||||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model },
|
||||
});
|
||||
expect(result.llmConfig.maxTokens).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle explicit maxOutputTokens override for Claude 4.x models', () => {
|
||||
|
|
@ -908,7 +961,7 @@ describe('getLLMConfig', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('should future-proof Claude 5.x Opus models with 32K default', () => {
|
||||
it('should future-proof Claude 5.x Opus models with 64K default', () => {
|
||||
const testCases = [
|
||||
'claude-opus-5',
|
||||
'claude-opus-5-0',
|
||||
|
|
@ -920,28 +973,28 @@ describe('getLLMConfig', () => {
|
|||
const result = getLLMConfig('test-key', {
|
||||
modelOptions: { model },
|
||||
});
|
||||
expect(result.llmConfig.maxTokens).toBe(32000);
|
||||
expect(result.llmConfig.maxTokens).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
it('should future-proof Claude 6-9.x models with correct defaults', () => {
|
||||
const testCases = [
|
||||
// Claude 6.x
|
||||
// Claude 6.x - All get 64K since they're version 5+
|
||||
{ model: 'claude-sonnet-6', expected: 64000 },
|
||||
{ model: 'claude-haiku-6-0', expected: 64000 },
|
||||
{ model: 'claude-opus-6-1', expected: 32000 },
|
||||
{ model: 'claude-opus-6-1', expected: 64000 }, // opus 6+ gets 64K
|
||||
// Claude 7.x
|
||||
{ model: 'claude-sonnet-7-20270101', expected: 64000 },
|
||||
{ model: 'claude-haiku-7.5', expected: 64000 },
|
||||
{ model: 'claude-opus-7', expected: 32000 },
|
||||
{ model: 'claude-opus-7', expected: 64000 }, // opus 7+ gets 64K
|
||||
// Claude 8.x
|
||||
{ model: 'claude-sonnet-8', expected: 64000 },
|
||||
{ model: 'claude-haiku-8-2', expected: 64000 },
|
||||
{ model: 'claude-opus-8-latest', expected: 32000 },
|
||||
{ model: 'claude-opus-8-latest', expected: 64000 }, // opus 8+ gets 64K
|
||||
// Claude 9.x
|
||||
{ model: 'claude-sonnet-9', expected: 64000 },
|
||||
{ model: 'claude-haiku-9', expected: 64000 },
|
||||
{ model: 'claude-opus-9', expected: 32000 },
|
||||
{ model: 'claude-opus-9', expected: 64000 }, // opus 9+ gets 64K
|
||||
];
|
||||
|
||||
testCases.forEach(({ model, expected }) => {
|
||||
|
|
|
|||
|
|
@ -595,17 +595,27 @@ export class MCPConnection extends EventEmitter {
|
|||
|
||||
private setupTransportErrorHandlers(transport: Transport): void {
|
||||
transport.onerror = (error) => {
|
||||
logger.error(`${this.getLogPrefix()} Transport error:`, error);
|
||||
|
||||
// Check if it's an OAuth authentication error
|
||||
if (error && typeof error === 'object' && 'code' in error) {
|
||||
const errorCode = (error as unknown as { code?: number }).code;
|
||||
|
||||
// Ignore SSE 404 errors for servers that don't support SSE
|
||||
if (
|
||||
errorCode === 404 &&
|
||||
String(error?.message).toLowerCase().includes('failed to open sse stream')
|
||||
) {
|
||||
logger.warn(`${this.getLogPrefix()} SSE stream not available (404). Ignoring.`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if it's an OAuth authentication error
|
||||
if (errorCode === 401 || errorCode === 403) {
|
||||
logger.warn(`${this.getLogPrefix()} OAuth authentication error detected`);
|
||||
this.emit('oauthError', error);
|
||||
}
|
||||
}
|
||||
|
||||
logger.error(`${this.getLogPrefix()} Transport error:`, error);
|
||||
|
||||
this.emit('connectionChange', 'error');
|
||||
};
|
||||
}
|
||||
|
|
@ -678,7 +688,9 @@ export class MCPConnection extends EventEmitter {
|
|||
const pingUnsupported =
|
||||
error instanceof Error &&
|
||||
((error as Error)?.message.includes('-32601') ||
|
||||
(error as Error)?.message.includes('-32602') ||
|
||||
(error as Error)?.message.includes('invalid method ping') ||
|
||||
(error as Error)?.message.includes('Unsupported method: ping') ||
|
||||
(error as Error)?.message.includes('method not found'));
|
||||
|
||||
if (!pingUnsupported) {
|
||||
|
|
|
|||
|
|
@ -133,8 +133,9 @@ const anthropicModels = {
|
|||
'claude-3.5-sonnet-latest': 200000,
|
||||
'claude-haiku-4-5': 200000,
|
||||
'claude-sonnet-4': 1000000,
|
||||
'claude-opus-4': 200000,
|
||||
'claude-4': 200000,
|
||||
'claude-opus-4': 200000,
|
||||
'claude-opus-4-5': 200000,
|
||||
};
|
||||
|
||||
const deepseekModels = {
|
||||
|
|
@ -334,8 +335,9 @@ const anthropicMaxOutputs = {
|
|||
'claude-3-sonnet': 4096,
|
||||
'claude-3-opus': 4096,
|
||||
'claude-haiku-4-5': 64000,
|
||||
'claude-opus-4': 32000,
|
||||
'claude-sonnet-4': 64000,
|
||||
'claude-opus-4': 32000,
|
||||
'claude-opus-4-5': 64000,
|
||||
'claude-3.5-sonnet': 8192,
|
||||
'claude-3-5-sonnet': 8192,
|
||||
'claude-3.7-sonnet': 128000,
|
||||
|
|
|
|||
|
|
@ -1003,6 +1003,7 @@ const sharedAnthropicModels = [
|
|||
'claude-haiku-4-5-20251001',
|
||||
'claude-opus-4-1',
|
||||
'claude-opus-4-1-20250805',
|
||||
'claude-opus-4-5',
|
||||
'claude-sonnet-4-20250514',
|
||||
'claude-sonnet-4-0',
|
||||
'claude-opus-4-20250514',
|
||||
|
|
|
|||
341
packages/data-provider/src/schemas.spec.ts
Normal file
341
packages/data-provider/src/schemas.spec.ts
Normal file
|
|
@ -0,0 +1,341 @@
|
|||
import { anthropicSettings } from './schemas';
|
||||
|
||||
describe('anthropicSettings', () => {
|
||||
describe('maxOutputTokens.reset()', () => {
|
||||
const { reset } = anthropicSettings.maxOutputTokens;
|
||||
|
||||
describe('Claude Sonnet models', () => {
|
||||
it('should return 64K for claude-sonnet-4', () => {
|
||||
expect(reset('claude-sonnet-4')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-sonnet-4-5', () => {
|
||||
expect(reset('claude-sonnet-4-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-sonnet-5', () => {
|
||||
expect(reset('claude-sonnet-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for future versions like claude-sonnet-9', () => {
|
||||
expect(reset('claude-sonnet-9')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Haiku models', () => {
|
||||
it('should return 64K for claude-haiku-4-5', () => {
|
||||
expect(reset('claude-haiku-4-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-haiku-4', () => {
|
||||
expect(reset('claude-haiku-4')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-haiku-5', () => {
|
||||
expect(reset('claude-haiku-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for future versions like claude-haiku-9', () => {
|
||||
expect(reset('claude-haiku-9')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 4.0-4.4 models (32K limit)', () => {
|
||||
it('should return 32K for claude-opus-4', () => {
|
||||
expect(reset('claude-opus-4')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-0', () => {
|
||||
expect(reset('claude-opus-4-0')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-1', () => {
|
||||
expect(reset('claude-opus-4-1')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-2', () => {
|
||||
expect(reset('claude-opus-4-2')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-3', () => {
|
||||
expect(reset('claude-opus-4-3')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-4', () => {
|
||||
expect(reset('claude-opus-4-4')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4.0', () => {
|
||||
expect(reset('claude-opus-4.0')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4.1', () => {
|
||||
expect(reset('claude-opus-4.1')).toBe(32000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 4.5+ models (64K limit - future-proof)', () => {
|
||||
it('should return 64K for claude-opus-4-5', () => {
|
||||
expect(reset('claude-opus-4-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-6', () => {
|
||||
expect(reset('claude-opus-4-6')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-7', () => {
|
||||
expect(reset('claude-opus-4-7')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-8', () => {
|
||||
expect(reset('claude-opus-4-8')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-9', () => {
|
||||
expect(reset('claude-opus-4-9')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4.5', () => {
|
||||
expect(reset('claude-opus-4.5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4.6', () => {
|
||||
expect(reset('claude-opus-4.6')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 4.10+ models (double-digit minor versions)', () => {
|
||||
it('should return 64K for claude-opus-4-10', () => {
|
||||
expect(reset('claude-opus-4-10')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-11', () => {
|
||||
expect(reset('claude-opus-4-11')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-15', () => {
|
||||
expect(reset('claude-opus-4-15')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-20', () => {
|
||||
expect(reset('claude-opus-4-20')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4.10', () => {
|
||||
expect(reset('claude-opus-4.10')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 5+ models (future major versions)', () => {
|
||||
it('should return 64K for claude-opus-5', () => {
|
||||
expect(reset('claude-opus-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-6', () => {
|
||||
expect(reset('claude-opus-6')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-7', () => {
|
||||
expect(reset('claude-opus-7')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-9', () => {
|
||||
expect(reset('claude-opus-9')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-5-0', () => {
|
||||
expect(reset('claude-opus-5-0')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-5.0', () => {
|
||||
expect(reset('claude-opus-5.0')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Model name variations with dates and suffixes', () => {
|
||||
it('should return 64K for claude-opus-4-5-20250420', () => {
|
||||
expect(reset('claude-opus-4-5-20250420')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-6-20260101', () => {
|
||||
expect(reset('claude-opus-4-6-20260101')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-1-20250805', () => {
|
||||
expect(reset('claude-opus-4-1-20250805')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-0-20240229', () => {
|
||||
expect(reset('claude-opus-4-0-20240229')).toBe(32000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Legacy Claude models', () => {
|
||||
it('should return 8192 for claude-3-opus', () => {
|
||||
expect(reset('claude-3-opus')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for claude-3-5-sonnet', () => {
|
||||
expect(reset('claude-3-5-sonnet')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for claude-3-5-haiku', () => {
|
||||
expect(reset('claude-3-5-haiku')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for claude-3-7-sonnet', () => {
|
||||
expect(reset('claude-3-7-sonnet')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for claude-2', () => {
|
||||
expect(reset('claude-2')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for claude-2.1', () => {
|
||||
expect(reset('claude-2.1')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for claude-instant', () => {
|
||||
expect(reset('claude-instant')).toBe(8192);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Non-Claude models and edge cases', () => {
|
||||
it('should return 8192 for unknown model', () => {
|
||||
expect(reset('unknown-model')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for empty string', () => {
|
||||
expect(reset('')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for gpt-4', () => {
|
||||
expect(reset('gpt-4')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should return 8192 for gemini-pro', () => {
|
||||
expect(reset('gemini-pro')).toBe(8192);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Regex pattern edge cases', () => {
|
||||
it('should not match claude-opus-3', () => {
|
||||
expect(reset('claude-opus-3')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should not match opus-4-5 without claude prefix', () => {
|
||||
expect(reset('opus-4-5')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should NOT match claude.opus.4.5 (incorrect separator pattern)', () => {
|
||||
// Model names use hyphens after "claude", not dots
|
||||
expect(reset('claude.opus.4.5')).toBe(8192);
|
||||
});
|
||||
|
||||
it('should match claude-opus45 (no separator after opus)', () => {
|
||||
// The regex allows optional separators, so "45" can follow directly
|
||||
// In practice, Anthropic uses separators, but regex is permissive
|
||||
expect(reset('claude-opus45')).toBe(64000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('maxOutputTokens.set()', () => {
|
||||
const { set } = anthropicSettings.maxOutputTokens;
|
||||
|
||||
describe('Claude Sonnet and Haiku 4+ models (64K cap)', () => {
|
||||
it('should cap at 64K for claude-sonnet-4 when value exceeds', () => {
|
||||
expect(set(100000, 'claude-sonnet-4')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should allow 50K for claude-sonnet-4', () => {
|
||||
expect(set(50000, 'claude-sonnet-4')).toBe(50000);
|
||||
});
|
||||
|
||||
it('should cap at 64K for claude-haiku-4-5 when value exceeds', () => {
|
||||
expect(set(80000, 'claude-haiku-4-5')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 4.5+ models (64K cap)', () => {
|
||||
it('should cap at 64K for claude-opus-4-5 when value exceeds', () => {
|
||||
expect(set(100000, 'claude-opus-4-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should cap at model-specific 64K limit, not global 128K limit', () => {
|
||||
// Values between 64K and 128K should be capped at 64K (model limit)
|
||||
// This verifies the fix for the unreachable code issue
|
||||
expect(set(70000, 'claude-opus-4-5')).toBe(64000);
|
||||
expect(set(80000, 'claude-opus-4-5')).toBe(64000);
|
||||
expect(set(100000, 'claude-opus-4-5')).toBe(64000);
|
||||
expect(set(128000, 'claude-opus-4-5')).toBe(64000);
|
||||
|
||||
// Values above 128K should also be capped at 64K (not 128K)
|
||||
expect(set(150000, 'claude-opus-4-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should allow 50K for claude-opus-4-5', () => {
|
||||
expect(set(50000, 'claude-opus-4-5')).toBe(50000);
|
||||
});
|
||||
|
||||
it('should cap at 64K for claude-opus-4-6', () => {
|
||||
expect(set(80000, 'claude-opus-4-6')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should cap at 64K for claude-opus-5', () => {
|
||||
expect(set(100000, 'claude-opus-5')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should cap at 64K for claude-opus-4-10', () => {
|
||||
expect(set(100000, 'claude-opus-4-10')).toBe(64000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 4.0-4.4 models (32K cap)', () => {
|
||||
it('should cap at 32K for claude-opus-4', () => {
|
||||
expect(set(50000, 'claude-opus-4')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should allow 20K for claude-opus-4', () => {
|
||||
expect(set(20000, 'claude-opus-4')).toBe(20000);
|
||||
});
|
||||
|
||||
it('should cap at 32K for claude-opus-4-1', () => {
|
||||
expect(set(50000, 'claude-opus-4-1')).toBe(32000);
|
||||
});
|
||||
|
||||
it('should cap at 32K for claude-opus-4-4', () => {
|
||||
expect(set(40000, 'claude-opus-4-4')).toBe(32000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Global 128K cap for all models', () => {
|
||||
it('should cap at model-specific limit first, then global', () => {
|
||||
// claude-sonnet-4 has 64K limit, so caps at 64K not 128K
|
||||
expect(set(150000, 'claude-sonnet-4')).toBe(64000);
|
||||
});
|
||||
|
||||
it('should cap at 128K for claude-3 models', () => {
|
||||
expect(set(150000, 'claude-3-opus')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should cap at 128K for unknown models', () => {
|
||||
expect(set(200000, 'unknown-model')).toBe(128000);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Valid values within limits', () => {
|
||||
it('should allow valid values for legacy models', () => {
|
||||
expect(set(8000, 'claude-3-opus')).toBe(8000);
|
||||
});
|
||||
|
||||
it('should allow 1 token minimum', () => {
|
||||
expect(set(1, 'claude-opus-4-5')).toBe(1);
|
||||
});
|
||||
|
||||
it('should allow 128K exactly', () => {
|
||||
expect(set(128000, 'claude-3-opus')).toBe(128000);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -386,6 +386,10 @@ export const anthropicSettings = {
|
|||
return CLAUDE_4_64K_MAX_OUTPUT;
|
||||
}
|
||||
|
||||
if (/claude-opus[-.]?(?:[5-9]|4[-.]?([5-9]|\d{2,}))/.test(modelName)) {
|
||||
return CLAUDE_4_64K_MAX_OUTPUT;
|
||||
}
|
||||
|
||||
if (/claude-opus[-.]?[4-9]/.test(modelName)) {
|
||||
return CLAUDE_32K_MAX_OUTPUT;
|
||||
}
|
||||
|
|
@ -397,7 +401,14 @@ export const anthropicSettings = {
|
|||
return CLAUDE_4_64K_MAX_OUTPUT;
|
||||
}
|
||||
|
||||
if (/claude-(?:opus|haiku)[-.]?[4-9]/.test(modelName) && value > CLAUDE_32K_MAX_OUTPUT) {
|
||||
if (/claude-opus[-.]?(?:[5-9]|4[-.]?([5-9]|\d{2,}))/.test(modelName)) {
|
||||
if (value > CLAUDE_4_64K_MAX_OUTPUT) {
|
||||
return CLAUDE_4_64K_MAX_OUTPUT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (/claude-opus[-.]?[4-9]/.test(modelName) && value > CLAUDE_32K_MAX_OUTPUT) {
|
||||
return CLAUDE_32K_MAX_OUTPUT;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue