mirror of
https://github.com/danny-avila/LibreChat.git
synced 2026-02-09 02:54:23 +01:00
🤖 feat: Claude Opus 4.6 - 1M Context, Premium Pricing, Adaptive Thinking (#11670)
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
Some checks are pending
Docker Dev Branch Images Build / build (Dockerfile, lc-dev, node) (push) Waiting to run
Docker Dev Branch Images Build / build (Dockerfile.multi, lc-dev-api, api-build) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile, librechat-dev, node) (push) Waiting to run
Docker Dev Images Build / build (Dockerfile.multi, librechat-dev-api, api-build) (push) Waiting to run
Sync Locize Translations & Create Translation PR / Sync Translation Keys with Locize (push) Waiting to run
Sync Locize Translations & Create Translation PR / Create Translation PR on Version Published (push) Blocked by required conditions
* feat: Implement new features for Claude Opus 4.6 model - Added support for tiered pricing based on input token count for the Claude Opus 4.6 model. - Updated token value calculations to include inputTokenCount for accurate pricing. - Enhanced transaction handling to apply premium rates when input tokens exceed defined thresholds. - Introduced comprehensive tests to validate pricing logic for both standard and premium rates across various scenarios. - Updated related utility functions and models to accommodate new pricing structure. This change improves the flexibility and accuracy of token pricing for the Claude Opus 4.6 model, ensuring users are charged appropriately based on their usage. * feat: Add effort field to conversation and preset schemas - Introduced a new optional `effort` field of type `String` in both the `IPreset` and `IConversation` interfaces. - Updated the `conversationPreset` schema to include the `effort` field, enhancing the data structure for better context management. * chore: Clean up unused variable and comments in initialize function * chore: update dependencies and SDK versions - Updated @anthropic-ai/sdk to version 0.73.0 in package.json and overrides. - Updated @anthropic-ai/vertex-sdk to version 0.14.3 in packages/api/package.json. - Updated @librechat/agents to version 3.1.34 in packages/api/package.json. - Refactored imports in packages/api/src/endpoints/anthropic/vertex.ts for consistency. * chore: remove postcss-loader from dependencies * feat: Bedrock model support for adaptive thinking configuration - Updated .env.example to include new Bedrock model IDs for Claude Opus 4.6. - Refactored bedrockInputParser to support adaptive thinking for Opus models, allowing for dynamic thinking configurations. - Introduced a new function to check model compatibility with adaptive thinking. - Added an optional `effort` field to the input schemas and updated related configurations. - Enhanced tests to validate the new adaptive thinking logic and model configurations. * feat: Add tests for Opus 4.6 adaptive thinking configuration * feat: Update model references for Opus 4.6 by removing version suffix * feat: Update @librechat/agents to version 3.1.35 in package.json and package-lock.json * chore: @librechat/agents to version 3.1.36 in package.json and package-lock.json * feat: Normalize inputTokenCount for spendTokens and enhance transaction handling - Introduced normalization for promptTokens to ensure inputTokenCount does not go negative. - Updated transaction logic to reflect normalized inputTokenCount in pricing calculations. - Added comprehensive tests to validate the new normalization logic and its impact on transaction rates for both standard and premium models. - Refactored related functions to improve clarity and maintainability of token value calculations. * chore: Simplify adaptive thinking configuration in helpers.ts - Removed unnecessary type casting for the thinking property in updatedOptions. - Ensured that adaptive thinking is directly assigned when conditions are met, improving code clarity. * refactor: Replace hard-coded token values with dynamic retrieval from maxTokensMap in model tests * fix: Ensure non-negative token values in spendTokens calculations - Updated token value retrieval to use Math.max for prompt and completion tokens, preventing negative values. - Enhanced clarity in token calculations for both prompt and completion transactions. * test: Add test for normalization of negative structured token values in spendStructuredTokens - Implemented a test to ensure that negative structured token values are normalized to zero during token spending. - Verified that the transaction rates remain consistent with the expected standard values after normalization. * refactor: Bedrock model support for adaptive thinking and context handling - Added tests for various alternate naming conventions of Claude models to validate adaptive thinking and context support. - Refactored `supportsAdaptiveThinking` and `supportsContext1m` functions to utilize new parsing methods for model version extraction. - Updated `bedrockInputParser` to handle effort configurations more effectively and strip unnecessary fields for non-adaptive models. - Improved handling of anthropic model configurations in the input parser. * fix: Improve token value retrieval in getMultiplier function - Updated the token value retrieval logic to use optional chaining for better safety against undefined values. - Added a test case to ensure that the function returns the default rate when the provided valueKey does not exist in tokenValues.
This commit is contained in:
parent
1d5f2eb04b
commit
41e2348d47
32 changed files with 2902 additions and 1087 deletions
|
|
@ -1,10 +1,12 @@
|
|||
import { z } from 'zod';
|
||||
import * as s from './schemas';
|
||||
|
||||
type ThinkingConfig = {
|
||||
type: 'enabled';
|
||||
budget_tokens: number;
|
||||
};
|
||||
const DEFAULT_ENABLED_MAX_TOKENS = 8192;
|
||||
const DEFAULT_ADAPTIVE_MAX_TOKENS = 16000;
|
||||
const DEFAULT_THINKING_BUDGET = 2000;
|
||||
|
||||
type ThinkingConfig = { type: 'enabled'; budget_tokens: number } | { type: 'adaptive' };
|
||||
|
||||
type AnthropicReasoning = {
|
||||
thinking?: ThinkingConfig | boolean;
|
||||
thinkingBudget?: number;
|
||||
|
|
@ -15,6 +17,64 @@ type AnthropicInput = BedrockConverseInput & {
|
|||
AnthropicReasoning;
|
||||
};
|
||||
|
||||
/** Extracts opus major/minor version from both naming formats */
|
||||
function parseOpusVersion(model: string): { major: number; minor: number } | null {
|
||||
const nameFirst = model.match(/claude-opus[-.]?(\d+)(?:[-.](\d+))?/);
|
||||
if (nameFirst) {
|
||||
return {
|
||||
major: parseInt(nameFirst[1], 10),
|
||||
minor: nameFirst[2] != null ? parseInt(nameFirst[2], 10) : 0,
|
||||
};
|
||||
}
|
||||
const numFirst = model.match(/claude-(\d+)(?:[-.](\d+))?-opus/);
|
||||
if (numFirst) {
|
||||
return {
|
||||
major: parseInt(numFirst[1], 10),
|
||||
minor: numFirst[2] != null ? parseInt(numFirst[2], 10) : 0,
|
||||
};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Extracts sonnet major version from both naming formats */
|
||||
function parseSonnetVersion(model: string): number | null {
|
||||
const nameFirst = model.match(/claude-sonnet[-.]?(\d+)/);
|
||||
if (nameFirst) {
|
||||
return parseInt(nameFirst[1], 10);
|
||||
}
|
||||
const numFirst = model.match(/claude-(\d+)(?:[-.]?\d+)?-sonnet/);
|
||||
if (numFirst) {
|
||||
return parseInt(numFirst[1], 10);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Checks if a model supports adaptive thinking (Opus 4.6+, Sonnet 5+) */
|
||||
export function supportsAdaptiveThinking(model: string): boolean {
|
||||
const opus = parseOpusVersion(model);
|
||||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 6))) {
|
||||
return true;
|
||||
}
|
||||
const sonnet = parseSonnetVersion(model);
|
||||
if (sonnet != null && sonnet >= 5) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Checks if a model qualifies for the context-1m beta header (Sonnet 4+, Opus 4.6+, Opus 5+) */
|
||||
export function supportsContext1m(model: string): boolean {
|
||||
const sonnet = parseSonnetVersion(model);
|
||||
if (sonnet != null && sonnet >= 4) {
|
||||
return true;
|
||||
}
|
||||
const opus = parseOpusVersion(model);
|
||||
if (opus && (opus.major > 4 || (opus.major === 4 && opus.minor >= 6))) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the appropriate anthropic_beta headers for Bedrock Anthropic models.
|
||||
* Bedrock uses `anthropic_beta` (with underscore) in additionalModelRequestFields.
|
||||
|
|
@ -38,7 +98,7 @@ function getBedrockAnthropicBetaHeaders(model: string): string[] {
|
|||
betaHeaders.push('output-128k-2025-02-19');
|
||||
}
|
||||
|
||||
if (isSonnet4PlusModel) {
|
||||
if (isSonnet4PlusModel || supportsAdaptiveThinking(model)) {
|
||||
betaHeaders.push('context-1m-2025-08-07');
|
||||
}
|
||||
|
||||
|
|
@ -67,6 +127,7 @@ export const bedrockInputSchema = s.tConversationSchema
|
|||
stop: true,
|
||||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
promptCache: true,
|
||||
/* Catch-all fields */
|
||||
topK: true,
|
||||
|
|
@ -75,10 +136,11 @@ export const bedrockInputSchema = s.tConversationSchema
|
|||
.transform((obj) => {
|
||||
if ((obj as AnthropicInput).additionalModelRequestFields?.thinking != null) {
|
||||
const _obj = obj as AnthropicInput;
|
||||
obj.thinking = !!_obj.additionalModelRequestFields.thinking;
|
||||
const thinking = _obj.additionalModelRequestFields.thinking;
|
||||
obj.thinking = !!thinking;
|
||||
obj.thinkingBudget =
|
||||
typeof _obj.additionalModelRequestFields.thinking === 'object'
|
||||
? (_obj.additionalModelRequestFields.thinking as ThinkingConfig)?.budget_tokens
|
||||
typeof thinking === 'object' && 'budget_tokens' in thinking
|
||||
? thinking.budget_tokens
|
||||
: undefined;
|
||||
delete obj.additionalModelRequestFields;
|
||||
}
|
||||
|
|
@ -109,6 +171,7 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
stop: true,
|
||||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
promptCache: true,
|
||||
/* Catch-all fields */
|
||||
topK: true,
|
||||
|
|
@ -157,34 +220,77 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
typedData.model,
|
||||
))
|
||||
) {
|
||||
if (additionalFields.thinking === undefined) {
|
||||
additionalFields.thinking = true;
|
||||
} else if (additionalFields.thinking === false) {
|
||||
delete additionalFields.thinking;
|
||||
delete additionalFields.thinkingBudget;
|
||||
const isAdaptive = supportsAdaptiveThinking(typedData.model as string);
|
||||
|
||||
if (isAdaptive) {
|
||||
const effort = additionalFields.effort;
|
||||
if (effort && typeof effort === 'string' && effort !== '') {
|
||||
additionalFields.output_config = { effort };
|
||||
}
|
||||
delete additionalFields.effort;
|
||||
|
||||
if (additionalFields.thinking === false) {
|
||||
delete additionalFields.thinking;
|
||||
delete additionalFields.thinkingBudget;
|
||||
} else {
|
||||
additionalFields.thinking = { type: 'adaptive' };
|
||||
delete additionalFields.thinkingBudget;
|
||||
}
|
||||
} else {
|
||||
if (additionalFields.thinking === undefined) {
|
||||
additionalFields.thinking = true;
|
||||
} else if (additionalFields.thinking === false) {
|
||||
delete additionalFields.thinking;
|
||||
delete additionalFields.thinkingBudget;
|
||||
}
|
||||
|
||||
if (additionalFields.thinking === true && additionalFields.thinkingBudget === undefined) {
|
||||
additionalFields.thinkingBudget = DEFAULT_THINKING_BUDGET;
|
||||
}
|
||||
delete additionalFields.effort;
|
||||
}
|
||||
|
||||
if (additionalFields.thinking === true && additionalFields.thinkingBudget === undefined) {
|
||||
additionalFields.thinkingBudget = 2000;
|
||||
}
|
||||
if (typedData.model.includes('anthropic.')) {
|
||||
const betaHeaders = getBedrockAnthropicBetaHeaders(typedData.model);
|
||||
if ((typedData.model as string).includes('anthropic.')) {
|
||||
const betaHeaders = getBedrockAnthropicBetaHeaders(typedData.model as string);
|
||||
if (betaHeaders.length > 0) {
|
||||
additionalFields.anthropic_beta = betaHeaders;
|
||||
}
|
||||
}
|
||||
} else if (additionalFields.thinking != null || additionalFields.thinkingBudget != null) {
|
||||
} else {
|
||||
delete additionalFields.thinking;
|
||||
delete additionalFields.thinkingBudget;
|
||||
delete additionalFields.effort;
|
||||
delete additionalFields.output_config;
|
||||
delete additionalFields.anthropic_beta;
|
||||
}
|
||||
|
||||
const isAnthropicModel =
|
||||
typeof typedData.model === 'string' && typedData.model.includes('anthropic.');
|
||||
|
||||
/** Strip stale anthropic_beta from previously-persisted additionalModelRequestFields */
|
||||
if (
|
||||
!isAnthropicModel &&
|
||||
typeof typedData.additionalModelRequestFields === 'object' &&
|
||||
typedData.additionalModelRequestFields != null
|
||||
) {
|
||||
const amrf = typedData.additionalModelRequestFields as Record<string, unknown>;
|
||||
delete amrf.anthropic_beta;
|
||||
delete amrf.thinking;
|
||||
delete amrf.thinkingBudget;
|
||||
delete amrf.effort;
|
||||
delete amrf.output_config;
|
||||
}
|
||||
|
||||
/** Default promptCache for claude and nova models, if not defined */
|
||||
if (
|
||||
typeof typedData.model === 'string' &&
|
||||
(typedData.model.includes('claude') || typedData.model.includes('nova')) &&
|
||||
typedData.promptCache === undefined
|
||||
(typedData.model.includes('claude') || typedData.model.includes('nova'))
|
||||
) {
|
||||
typedData.promptCache = true;
|
||||
if (typedData.promptCache === undefined) {
|
||||
typedData.promptCache = true;
|
||||
}
|
||||
} else if (typedData.promptCache === true) {
|
||||
typedData.promptCache = undefined;
|
||||
}
|
||||
|
||||
if (Object.keys(additionalFields).length > 0) {
|
||||
|
|
@ -212,20 +318,34 @@ export const bedrockInputParser = s.tConversationSchema
|
|||
*/
|
||||
function configureThinking(data: AnthropicInput): AnthropicInput {
|
||||
const updatedData = { ...data };
|
||||
if (updatedData.additionalModelRequestFields?.thinking === true) {
|
||||
updatedData.maxTokens = updatedData.maxTokens ?? updatedData.maxOutputTokens ?? 8192;
|
||||
const thinking = updatedData.additionalModelRequestFields?.thinking;
|
||||
|
||||
if (thinking === true) {
|
||||
updatedData.maxTokens =
|
||||
updatedData.maxTokens ?? updatedData.maxOutputTokens ?? DEFAULT_ENABLED_MAX_TOKENS;
|
||||
delete updatedData.maxOutputTokens;
|
||||
const thinkingConfig: AnthropicReasoning['thinking'] = {
|
||||
const thinkingConfig: ThinkingConfig = {
|
||||
type: 'enabled',
|
||||
budget_tokens: updatedData.additionalModelRequestFields.thinkingBudget ?? 2000,
|
||||
budget_tokens:
|
||||
updatedData.additionalModelRequestFields?.thinkingBudget ?? DEFAULT_THINKING_BUDGET,
|
||||
};
|
||||
|
||||
if (thinkingConfig.budget_tokens > updatedData.maxTokens) {
|
||||
thinkingConfig.budget_tokens = Math.floor(updatedData.maxTokens * 0.9);
|
||||
}
|
||||
updatedData.additionalModelRequestFields.thinking = thinkingConfig;
|
||||
delete updatedData.additionalModelRequestFields.thinkingBudget;
|
||||
updatedData.additionalModelRequestFields!.thinking = thinkingConfig;
|
||||
delete updatedData.additionalModelRequestFields!.thinkingBudget;
|
||||
} else if (
|
||||
typeof thinking === 'object' &&
|
||||
thinking != null &&
|
||||
(thinking as { type: string }).type === 'adaptive'
|
||||
) {
|
||||
updatedData.maxTokens =
|
||||
updatedData.maxTokens ?? updatedData.maxOutputTokens ?? DEFAULT_ADAPTIVE_MAX_TOKENS;
|
||||
delete updatedData.maxOutputTokens;
|
||||
delete updatedData.additionalModelRequestFields!.thinkingBudget;
|
||||
}
|
||||
|
||||
return updatedData;
|
||||
}
|
||||
|
||||
|
|
@ -268,8 +388,8 @@ export const bedrockOutputParser = (data: Record<string, unknown>) => {
|
|||
}
|
||||
|
||||
result = configureThinking(result as AnthropicInput);
|
||||
// Remove additionalModelRequestFields from the result if it doesn't thinking config
|
||||
if ((result as AnthropicInput).additionalModelRequestFields?.thinking == null) {
|
||||
const amrf = result.additionalModelRequestFields as Record<string, unknown> | undefined;
|
||||
if (!amrf || Object.keys(amrf).length === 0) {
|
||||
delete result.additionalModelRequestFields;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1133,6 +1133,7 @@ const sharedOpenAIModels = [
|
|||
];
|
||||
|
||||
const sharedAnthropicModels = [
|
||||
'claude-opus-4-6',
|
||||
'claude-sonnet-4-5',
|
||||
'claude-sonnet-4-5-20250929',
|
||||
'claude-haiku-4-5',
|
||||
|
|
@ -1153,6 +1154,7 @@ const sharedAnthropicModels = [
|
|||
];
|
||||
|
||||
export const bedrockModels = [
|
||||
'anthropic.claude-opus-4-6-v1',
|
||||
'anthropic.claude-sonnet-4-5-20250929-v1:0',
|
||||
'anthropic.claude-haiku-4-5-20251001-v1:0',
|
||||
'anthropic.claude-opus-4-1-20250805-v1:0',
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
openAISettings,
|
||||
googleSettings,
|
||||
ReasoningEffort,
|
||||
AnthropicEffort,
|
||||
ReasoningSummary,
|
||||
BedrockProviders,
|
||||
anthropicSettings,
|
||||
|
|
@ -445,6 +446,26 @@ const anthropic: Record<string, SettingDefinition> = {
|
|||
showDefault: false,
|
||||
columnSpan: 2,
|
||||
},
|
||||
effort: {
|
||||
key: 'effort',
|
||||
label: 'com_endpoint_effort',
|
||||
labelCode: true,
|
||||
description: 'com_endpoint_anthropic_effort',
|
||||
descriptionCode: true,
|
||||
type: 'enum',
|
||||
default: anthropicSettings.effort.default,
|
||||
component: 'slider',
|
||||
options: anthropicSettings.effort.options,
|
||||
enumMappings: {
|
||||
[AnthropicEffort.unset]: 'com_ui_auto',
|
||||
[AnthropicEffort.low]: 'com_ui_low',
|
||||
[AnthropicEffort.medium]: 'com_ui_medium',
|
||||
[AnthropicEffort.high]: 'com_ui_high',
|
||||
[AnthropicEffort.max]: 'com_ui_max',
|
||||
},
|
||||
optionType: 'model',
|
||||
columnSpan: 4,
|
||||
},
|
||||
};
|
||||
|
||||
const bedrock: Record<string, SettingDefinition> = {
|
||||
|
|
@ -734,6 +755,7 @@ const anthropicConfig: SettingsConfiguration = [
|
|||
anthropic.promptCache,
|
||||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
anthropic.web_search,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
|
@ -754,6 +776,7 @@ const anthropicCol2: SettingsConfiguration = [
|
|||
anthropic.promptCache,
|
||||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
anthropic.web_search,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
|
@ -772,6 +795,7 @@ const bedrockAnthropic: SettingsConfiguration = [
|
|||
bedrock.promptCache,
|
||||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
||||
|
|
@ -829,6 +853,7 @@ const bedrockAnthropicCol2: SettingsConfiguration = [
|
|||
bedrock.promptCache,
|
||||
anthropic.thinking,
|
||||
anthropic.thinkingBudget,
|
||||
anthropic.effort,
|
||||
librechat.fileTokenLimit,
|
||||
];
|
||||
|
||||
|
|
|
|||
|
|
@ -74,81 +74,83 @@ describe('anthropicSettings', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Claude Opus 4.5+ models (64K limit - future-proof)', () => {
|
||||
describe('Claude Opus 4.5 models (64K limit)', () => {
|
||||
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.6+ models (128K limit - future-proof)', () => {
|
||||
it('should return 128K for claude-opus-4-6', () => {
|
||||
expect(reset('claude-opus-4-6')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 128K for claude-opus-4.6', () => {
|
||||
expect(reset('claude-opus-4.6')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 128K for claude-opus-4-7', () => {
|
||||
expect(reset('claude-opus-4-7')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 128K for claude-opus-4-8', () => {
|
||||
expect(reset('claude-opus-4-8')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 128K for claude-opus-4-9', () => {
|
||||
expect(reset('claude-opus-4-9')).toBe(128000);
|
||||
});
|
||||
});
|
||||
|
||||
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 128K for claude-opus-4-10', () => {
|
||||
expect(reset('claude-opus-4-10')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-11', () => {
|
||||
expect(reset('claude-opus-4-11')).toBe(64000);
|
||||
it('should return 128K for claude-opus-4-11', () => {
|
||||
expect(reset('claude-opus-4-11')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-15', () => {
|
||||
expect(reset('claude-opus-4-15')).toBe(64000);
|
||||
it('should return 128K for claude-opus-4-15', () => {
|
||||
expect(reset('claude-opus-4-15')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4-20', () => {
|
||||
expect(reset('claude-opus-4-20')).toBe(64000);
|
||||
it('should return 128K for claude-opus-4-20', () => {
|
||||
expect(reset('claude-opus-4-20')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-4.10', () => {
|
||||
expect(reset('claude-opus-4.10')).toBe(64000);
|
||||
it('should return 128K for claude-opus-4.10', () => {
|
||||
expect(reset('claude-opus-4.10')).toBe(128000);
|
||||
});
|
||||
});
|
||||
|
||||
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 128K for claude-opus-5', () => {
|
||||
expect(reset('claude-opus-5')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-6', () => {
|
||||
expect(reset('claude-opus-6')).toBe(64000);
|
||||
it('should return 128K for claude-opus-6', () => {
|
||||
expect(reset('claude-opus-6')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-7', () => {
|
||||
expect(reset('claude-opus-7')).toBe(64000);
|
||||
it('should return 128K for claude-opus-7', () => {
|
||||
expect(reset('claude-opus-7')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-9', () => {
|
||||
expect(reset('claude-opus-9')).toBe(64000);
|
||||
it('should return 128K for claude-opus-9', () => {
|
||||
expect(reset('claude-opus-9')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-5-0', () => {
|
||||
expect(reset('claude-opus-5-0')).toBe(64000);
|
||||
it('should return 128K for claude-opus-5-0', () => {
|
||||
expect(reset('claude-opus-5-0')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 64K for claude-opus-5.0', () => {
|
||||
expect(reset('claude-opus-5.0')).toBe(64000);
|
||||
it('should return 128K for claude-opus-5.0', () => {
|
||||
expect(reset('claude-opus-5.0')).toBe(128000);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -157,8 +159,8 @@ describe('anthropicSettings', () => {
|
|||
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 128K for claude-opus-4-6-20260101', () => {
|
||||
expect(reset('claude-opus-4-6-20260101')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should return 32K for claude-opus-4-1-20250805', () => {
|
||||
|
|
@ -234,8 +236,8 @@ describe('anthropicSettings', () => {
|
|||
|
||||
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);
|
||||
// "45" is treated as major version 45 (>= 5), so it gets 128K
|
||||
expect(reset('claude-opus45')).toBe(128000);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -278,16 +280,28 @@ describe('anthropicSettings', () => {
|
|||
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 allow 80K for claude-opus-4-6 (128K cap)', () => {
|
||||
expect(set(80000, 'claude-opus-4-6')).toBe(80000);
|
||||
});
|
||||
|
||||
it('should cap at 64K for claude-opus-5', () => {
|
||||
expect(set(100000, 'claude-opus-5')).toBe(64000);
|
||||
it('should cap at 128K for claude-opus-4-6', () => {
|
||||
expect(set(150000, 'claude-opus-4-6')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should cap at 64K for claude-opus-4-10', () => {
|
||||
expect(set(100000, 'claude-opus-4-10')).toBe(64000);
|
||||
it('should cap at 128K for claude-opus-5', () => {
|
||||
expect(set(150000, 'claude-opus-5')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should allow 100K for claude-opus-5', () => {
|
||||
expect(set(100000, 'claude-opus-5')).toBe(100000);
|
||||
});
|
||||
|
||||
it('should cap at 128K for claude-opus-4-10', () => {
|
||||
expect(set(150000, 'claude-opus-4-10')).toBe(128000);
|
||||
});
|
||||
|
||||
it('should allow 100K for claude-opus-4-10', () => {
|
||||
expect(set(100000, 'claude-opus-4-10')).toBe(100000);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -173,6 +173,14 @@ export enum ReasoningEffort {
|
|||
xhigh = 'xhigh',
|
||||
}
|
||||
|
||||
export enum AnthropicEffort {
|
||||
unset = '',
|
||||
low = 'low',
|
||||
medium = 'medium',
|
||||
high = 'high',
|
||||
max = 'max',
|
||||
}
|
||||
|
||||
export enum ReasoningSummary {
|
||||
none = '',
|
||||
auto = 'auto',
|
||||
|
|
@ -201,6 +209,7 @@ export const imageDetailValue = {
|
|||
|
||||
export const eImageDetailSchema = z.nativeEnum(ImageDetail);
|
||||
export const eReasoningEffortSchema = z.nativeEnum(ReasoningEffort);
|
||||
export const eAnthropicEffortSchema = z.nativeEnum(AnthropicEffort);
|
||||
export const eReasoningSummarySchema = z.nativeEnum(ReasoningSummary);
|
||||
export const eVerbositySchema = z.nativeEnum(Verbosity);
|
||||
|
||||
|
|
@ -382,6 +391,10 @@ export const anthropicSettings = {
|
|||
step: 1 as const,
|
||||
default: DEFAULT_MAX_OUTPUT,
|
||||
reset: (modelName: string) => {
|
||||
if (/claude-opus[-.]?(?:4[-.]?(?:[6-9]|\d{2,})|[5-9]|\d{2,})/.test(modelName)) {
|
||||
return ANTHROPIC_MAX_OUTPUT;
|
||||
}
|
||||
|
||||
if (/claude-(?:sonnet|haiku)[-.]?[4-9]/.test(modelName)) {
|
||||
return CLAUDE_4_64K_MAX_OUTPUT;
|
||||
}
|
||||
|
|
@ -397,6 +410,13 @@ export const anthropicSettings = {
|
|||
return DEFAULT_MAX_OUTPUT;
|
||||
},
|
||||
set: (value: number, modelName: string) => {
|
||||
if (/claude-opus[-.]?(?:4[-.]?(?:[6-9]|\d{2,})|[5-9]|\d{2,})/.test(modelName)) {
|
||||
if (value > ANTHROPIC_MAX_OUTPUT) {
|
||||
return ANTHROPIC_MAX_OUTPUT;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
if (/claude-(?:sonnet|haiku)[-.]?[4-9]/.test(modelName) && value > CLAUDE_4_64K_MAX_OUTPUT) {
|
||||
return CLAUDE_4_64K_MAX_OUTPUT;
|
||||
}
|
||||
|
|
@ -445,6 +465,16 @@ export const anthropicSettings = {
|
|||
default: LEGACY_ANTHROPIC_MAX_OUTPUT,
|
||||
},
|
||||
},
|
||||
effort: {
|
||||
default: AnthropicEffort.unset,
|
||||
options: [
|
||||
AnthropicEffort.unset,
|
||||
AnthropicEffort.low,
|
||||
AnthropicEffort.medium,
|
||||
AnthropicEffort.high,
|
||||
AnthropicEffort.max,
|
||||
],
|
||||
},
|
||||
web_search: {
|
||||
default: false as const,
|
||||
},
|
||||
|
|
@ -703,6 +733,8 @@ export const tConversationSchema = z.object({
|
|||
verbosity: eVerbositySchema.optional().nullable(),
|
||||
/* OpenAI: use Responses API */
|
||||
useResponsesApi: z.boolean().optional(),
|
||||
/* Anthropic: Effort control */
|
||||
effort: eAnthropicEffortSchema.optional().nullable(),
|
||||
/* OpenAI Responses API / Anthropic API / Google API */
|
||||
web_search: z.boolean().optional(),
|
||||
/* disable streaming */
|
||||
|
|
@ -825,6 +857,7 @@ export const tQueryParamsSchema = tConversationSchema
|
|||
promptCache: true,
|
||||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
/** @endpoints bedrock */
|
||||
region: true,
|
||||
/** @endpoints bedrock */
|
||||
|
|
@ -1122,6 +1155,7 @@ export const anthropicBaseSchema = tConversationSchema.pick({
|
|||
promptCache: true,
|
||||
thinking: true,
|
||||
thinkingBudget: true,
|
||||
effort: true,
|
||||
artifacts: true,
|
||||
iconURL: true,
|
||||
greeting: true,
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ export type TEndpointOption = Pick<
|
|||
| 'promptCache'
|
||||
| 'thinking'
|
||||
| 'thinkingBudget'
|
||||
| 'effort'
|
||||
// Assistant/Agent fields
|
||||
| 'assistant_id'
|
||||
| 'agent_id'
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue