diff --git a/api/models/tx.js b/api/models/tx.js
index 6ff105a458..97a4f89dbc 100644
--- a/api/models/tx.js
+++ b/api/models/tx.js
@@ -1,10 +1,40 @@
const { matchModelName, findMatchingPattern } = require('@librechat/api');
const defaultRate = 6;
+/**
+ * Token Pricing Configuration
+ *
+ * IMPORTANT: Key Ordering for Pattern Matching
+ * ============================================
+ * The `findMatchingPattern` function iterates through object keys in REVERSE order
+ * (last-defined keys are checked first) and uses `modelName.includes(key)` for matching.
+ *
+ * This means:
+ * 1. BASE PATTERNS must be defined FIRST (e.g., "kimi", "moonshot")
+ * 2. SPECIFIC PATTERNS must be defined AFTER their base patterns (e.g., "kimi-k2", "kimi-k2.5")
+ *
+ * Example ordering for Kimi models:
+ * kimi: { prompt: 0.6, completion: 2.5 }, // Base pattern - checked last
+ * 'kimi-k2': { prompt: 0.6, completion: 2.5 }, // More specific - checked before "kimi"
+ * 'kimi-k2.5': { prompt: 0.6, completion: 3.0 }, // Most specific - checked first
+ *
+ * Why this matters:
+ * - Model name "kimi-k2.5" contains both "kimi" and "kimi-k2" as substrings
+ * - If "kimi" were checked first, it would incorrectly match and return wrong pricing
+ * - By defining specific patterns AFTER base patterns, they're checked first in reverse iteration
+ *
+ * This applies to BOTH `tokenValues` and `cacheTokenValues` objects.
+ *
+ * When adding new model families:
+ * 1. Define the base/generic pattern first
+ * 2. Define increasingly specific patterns after
+ * 3. Ensure no pattern is a substring of another that should match differently
+ */
+
/**
* AWS Bedrock pricing
* source: https://aws.amazon.com/bedrock/pricing/
- * */
+ */
const bedrockValues = {
// Basic llama2 patterns (base defaults to smallest variant)
llama2: { prompt: 0.75, completion: 1.0 },
@@ -80,6 +110,11 @@ const bedrockValues = {
'nova-pro': { prompt: 0.8, completion: 3.2 },
'nova-premier': { prompt: 2.5, completion: 12.5 },
'deepseek.r1': { prompt: 1.35, completion: 5.4 },
+ // Moonshot/Kimi models on Bedrock
+ 'moonshot.kimi': { prompt: 0.6, completion: 2.5 },
+ 'moonshot.kimi-k2': { prompt: 0.6, completion: 2.5 },
+ 'moonshot.kimi-k2.5': { prompt: 0.6, completion: 3.0 },
+ 'moonshot.kimi-k2-thinking': { prompt: 0.6, completion: 2.5 },
};
/**
@@ -189,7 +224,31 @@ const tokenValues = Object.assign(
'pixtral-large': { prompt: 2.0, completion: 6.0 },
'mistral-large': { prompt: 2.0, completion: 6.0 },
'mixtral-8x22b': { prompt: 0.65, completion: 0.65 },
- kimi: { prompt: 0.14, completion: 2.49 }, // Base pattern (using kimi-k2 pricing)
+ // Moonshot/Kimi models (base patterns first, specific patterns last for correct matching)
+ kimi: { prompt: 0.6, completion: 2.5 }, // Base pattern
+ moonshot: { prompt: 2.0, completion: 5.0 }, // Base pattern (using 128k pricing)
+ 'kimi-latest': { prompt: 0.2, completion: 2.0 }, // Uses 8k/32k/128k pricing dynamically
+ 'kimi-k2': { prompt: 0.6, completion: 2.5 },
+ 'kimi-k2.5': { prompt: 0.6, completion: 3.0 },
+ 'kimi-k2-turbo': { prompt: 1.15, completion: 8.0 },
+ 'kimi-k2-turbo-preview': { prompt: 1.15, completion: 8.0 },
+ 'kimi-k2-0905': { prompt: 0.6, completion: 2.5 },
+ 'kimi-k2-0905-preview': { prompt: 0.6, completion: 2.5 },
+ 'kimi-k2-0711': { prompt: 0.6, completion: 2.5 },
+ 'kimi-k2-0711-preview': { prompt: 0.6, completion: 2.5 },
+ 'kimi-k2-thinking': { prompt: 0.6, completion: 2.5 },
+ 'kimi-k2-thinking-turbo': { prompt: 1.15, completion: 8.0 },
+ 'moonshot-v1': { prompt: 2.0, completion: 5.0 },
+ 'moonshot-v1-auto': { prompt: 2.0, completion: 5.0 },
+ 'moonshot-v1-8k': { prompt: 0.2, completion: 2.0 },
+ 'moonshot-v1-8k-vision': { prompt: 0.2, completion: 2.0 },
+ 'moonshot-v1-8k-vision-preview': { prompt: 0.2, completion: 2.0 },
+ 'moonshot-v1-32k': { prompt: 1.0, completion: 3.0 },
+ 'moonshot-v1-32k-vision': { prompt: 1.0, completion: 3.0 },
+ 'moonshot-v1-32k-vision-preview': { prompt: 1.0, completion: 3.0 },
+ 'moonshot-v1-128k': { prompt: 2.0, completion: 5.0 },
+ 'moonshot-v1-128k-vision': { prompt: 2.0, completion: 5.0 },
+ 'moonshot-v1-128k-vision-preview': { prompt: 2.0, completion: 5.0 },
// GPT-OSS models (specific sizes)
'gpt-oss:20b': { prompt: 0.05, completion: 0.2 },
'gpt-oss-20b': { prompt: 0.05, completion: 0.2 },
@@ -255,6 +314,18 @@ const cacheTokenValues = {
deepseek: { write: 0.28, read: 0.028 },
'deepseek-chat': { write: 0.28, read: 0.028 },
'deepseek-reasoner': { write: 0.28, read: 0.028 },
+ // Moonshot/Kimi models - cache hit: $0.15/1M (k2) or $0.10/1M (k2.5), cache miss: $0.60/1M
+ kimi: { write: 0.6, read: 0.15 },
+ 'kimi-k2': { write: 0.6, read: 0.15 },
+ 'kimi-k2.5': { write: 0.6, read: 0.1 },
+ 'kimi-k2-turbo': { write: 1.15, read: 0.15 },
+ 'kimi-k2-turbo-preview': { write: 1.15, read: 0.15 },
+ 'kimi-k2-0905': { write: 0.6, read: 0.15 },
+ 'kimi-k2-0905-preview': { write: 0.6, read: 0.15 },
+ 'kimi-k2-0711': { write: 0.6, read: 0.15 },
+ 'kimi-k2-0711-preview': { write: 0.6, read: 0.15 },
+ 'kimi-k2-thinking': { write: 0.6, read: 0.15 },
+ 'kimi-k2-thinking-turbo': { write: 1.15, read: 0.15 },
};
/**
diff --git a/api/models/tx.spec.js b/api/models/tx.spec.js
index f70a6af47c..5058446104 100644
--- a/api/models/tx.spec.js
+++ b/api/models/tx.spec.js
@@ -881,6 +881,193 @@ describe('Deepseek Model Tests', () => {
});
});
+describe('Moonshot/Kimi Model Tests - Pricing', () => {
+ describe('Kimi Models', () => {
+ it('should return correct pricing for kimi base pattern', () => {
+ expect(getMultiplier({ model: 'kimi', tokenType: 'prompt' })).toBe(
+ tokenValues['kimi'].prompt,
+ );
+ expect(getMultiplier({ model: 'kimi', tokenType: 'completion' })).toBe(
+ tokenValues['kimi'].completion,
+ );
+ });
+
+ it('should return correct pricing for kimi-k2.5', () => {
+ expect(getMultiplier({ model: 'kimi-k2.5', tokenType: 'prompt' })).toBe(
+ tokenValues['kimi-k2.5'].prompt,
+ );
+ expect(getMultiplier({ model: 'kimi-k2.5', tokenType: 'completion' })).toBe(
+ tokenValues['kimi-k2.5'].completion,
+ );
+ });
+
+ it('should return correct pricing for kimi-k2 series', () => {
+ expect(getMultiplier({ model: 'kimi-k2', tokenType: 'prompt' })).toBe(
+ tokenValues['kimi-k2'].prompt,
+ );
+ expect(getMultiplier({ model: 'kimi-k2', tokenType: 'completion' })).toBe(
+ tokenValues['kimi-k2'].completion,
+ );
+ });
+
+ it('should return correct pricing for kimi-k2-turbo (higher pricing)', () => {
+ expect(getMultiplier({ model: 'kimi-k2-turbo', tokenType: 'prompt' })).toBe(
+ tokenValues['kimi-k2-turbo'].prompt,
+ );
+ expect(getMultiplier({ model: 'kimi-k2-turbo', tokenType: 'completion' })).toBe(
+ tokenValues['kimi-k2-turbo'].completion,
+ );
+ });
+
+ it('should return correct pricing for kimi-k2-thinking models', () => {
+ expect(getMultiplier({ model: 'kimi-k2-thinking', tokenType: 'prompt' })).toBe(
+ tokenValues['kimi-k2-thinking'].prompt,
+ );
+ expect(getMultiplier({ model: 'kimi-k2-thinking', tokenType: 'completion' })).toBe(
+ tokenValues['kimi-k2-thinking'].completion,
+ );
+ expect(getMultiplier({ model: 'kimi-k2-thinking-turbo', tokenType: 'prompt' })).toBe(
+ tokenValues['kimi-k2-thinking-turbo'].prompt,
+ );
+ expect(getMultiplier({ model: 'kimi-k2-thinking-turbo', tokenType: 'completion' })).toBe(
+ tokenValues['kimi-k2-thinking-turbo'].completion,
+ );
+ });
+
+ it('should handle Kimi model variations with provider prefixes', () => {
+ const modelVariations = ['openrouter/kimi-k2', 'openrouter/kimi-k2.5', 'openrouter/kimi'];
+
+ modelVariations.forEach((model) => {
+ const promptMultiplier = getMultiplier({ model, tokenType: 'prompt' });
+ const completionMultiplier = getMultiplier({ model, tokenType: 'completion' });
+ expect(promptMultiplier).toBe(tokenValues['kimi'].prompt);
+ expect([tokenValues['kimi'].completion, tokenValues['kimi-k2.5'].completion]).toContain(
+ completionMultiplier,
+ );
+ });
+ });
+ });
+
+ describe('Moonshot Models', () => {
+ it('should return correct pricing for moonshot base pattern (128k pricing)', () => {
+ expect(getMultiplier({ model: 'moonshot', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot'].completion,
+ );
+ });
+
+ it('should return correct pricing for moonshot-v1-8k', () => {
+ expect(getMultiplier({ model: 'moonshot-v1-8k', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot-v1-8k'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-8k', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot-v1-8k'].completion,
+ );
+ });
+
+ it('should return correct pricing for moonshot-v1-32k', () => {
+ expect(getMultiplier({ model: 'moonshot-v1-32k', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot-v1-32k'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-32k', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot-v1-32k'].completion,
+ );
+ });
+
+ it('should return correct pricing for moonshot-v1-128k', () => {
+ expect(getMultiplier({ model: 'moonshot-v1-128k', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot-v1-128k'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-128k', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot-v1-128k'].completion,
+ );
+ });
+
+ it('should return correct pricing for moonshot-v1 vision models', () => {
+ expect(getMultiplier({ model: 'moonshot-v1-8k-vision', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot-v1-8k-vision'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-8k-vision', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot-v1-8k-vision'].completion,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-32k-vision', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot-v1-32k-vision'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-32k-vision', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot-v1-32k-vision'].completion,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-128k-vision', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot-v1-128k-vision'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot-v1-128k-vision', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot-v1-128k-vision'].completion,
+ );
+ });
+ });
+
+ describe('Kimi Cache Multipliers', () => {
+ it('should return correct cache multipliers for kimi-k2 models', () => {
+ expect(getCacheMultiplier({ model: 'kimi', cacheType: 'write' })).toBe(
+ cacheTokenValues['kimi'].write,
+ );
+ expect(getCacheMultiplier({ model: 'kimi', cacheType: 'read' })).toBe(
+ cacheTokenValues['kimi'].read,
+ );
+ });
+
+ it('should return correct cache multipliers for kimi-k2.5 (lower read price)', () => {
+ expect(getCacheMultiplier({ model: 'kimi-k2.5', cacheType: 'write' })).toBe(
+ cacheTokenValues['kimi-k2.5'].write,
+ );
+ expect(getCacheMultiplier({ model: 'kimi-k2.5', cacheType: 'read' })).toBe(
+ cacheTokenValues['kimi-k2.5'].read,
+ );
+ });
+
+ it('should return correct cache multipliers for kimi-k2-turbo', () => {
+ expect(getCacheMultiplier({ model: 'kimi-k2-turbo', cacheType: 'write' })).toBe(
+ cacheTokenValues['kimi-k2-turbo'].write,
+ );
+ expect(getCacheMultiplier({ model: 'kimi-k2-turbo', cacheType: 'read' })).toBe(
+ cacheTokenValues['kimi-k2-turbo'].read,
+ );
+ });
+
+ it('should handle Kimi cache multipliers with model variations', () => {
+ const modelVariations = ['openrouter/kimi-k2', 'openrouter/kimi'];
+
+ modelVariations.forEach((model) => {
+ const writeMultiplier = getCacheMultiplier({ model, cacheType: 'write' });
+ const readMultiplier = getCacheMultiplier({ model, cacheType: 'read' });
+ expect(writeMultiplier).toBe(cacheTokenValues['kimi'].write);
+ expect(readMultiplier).toBe(cacheTokenValues['kimi'].read);
+ });
+ });
+ });
+
+ describe('Bedrock Moonshot Models', () => {
+ it('should return correct pricing for Bedrock moonshot models', () => {
+ expect(getMultiplier({ model: 'moonshot.kimi', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot.kimi'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot.kimi', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot.kimi'].completion,
+ );
+ expect(getMultiplier({ model: 'moonshot.kimi-k2', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot.kimi-k2'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot.kimi-k2.5', tokenType: 'prompt' })).toBe(
+ tokenValues['moonshot.kimi-k2.5'].prompt,
+ );
+ expect(getMultiplier({ model: 'moonshot.kimi-k2.5', tokenType: 'completion' })).toBe(
+ tokenValues['moonshot.kimi-k2.5'].completion,
+ );
+ });
+ });
+});
+
describe('Qwen3 Model Tests', () => {
describe('Qwen3 Base Models', () => {
it('should return correct pricing for qwen3 base pattern', () => {
diff --git a/api/server/services/Endpoints/index.js b/api/server/services/Endpoints/index.js
index 034162702d..3cabfe1c58 100644
--- a/api/server/services/Endpoints/index.js
+++ b/api/server/services/Endpoints/index.js
@@ -12,7 +12,7 @@ const initGoogle = require('~/server/services/Endpoints/google/initialize');
* @returns {boolean} - True if the provider is a known custom provider, false otherwise
*/
function isKnownCustomProvider(provider) {
- return [Providers.XAI, Providers.DEEPSEEK, Providers.OPENROUTER].includes(
+ return [Providers.XAI, Providers.DEEPSEEK, Providers.OPENROUTER, Providers.MOONSHOT].includes(
provider?.toLowerCase() || '',
);
}
@@ -20,6 +20,7 @@ function isKnownCustomProvider(provider) {
const providerConfigMap = {
[Providers.XAI]: initCustom,
[Providers.DEEPSEEK]: initCustom,
+ [Providers.MOONSHOT]: initCustom,
[Providers.OPENROUTER]: initCustom,
[EModelEndpoint.openAI]: initOpenAI,
[EModelEndpoint.google]: initGoogle,
diff --git a/api/utils/tokens.spec.js b/api/utils/tokens.spec.js
index 3336a0f82d..c57ed2b6d4 100644
--- a/api/utils/tokens.spec.js
+++ b/api/utils/tokens.spec.js
@@ -1064,44 +1064,149 @@ describe('Claude Model Tests', () => {
});
});
-describe('Kimi Model Tests', () => {
+describe('Moonshot/Kimi Model Tests', () => {
describe('getModelMaxTokens', () => {
- test('should return correct tokens for Kimi models', () => {
- expect(getModelMaxTokens('kimi')).toBe(131000);
- expect(getModelMaxTokens('kimi-k2')).toBe(131000);
- expect(getModelMaxTokens('kimi-vl')).toBe(131000);
+ test('should return correct tokens for kimi-k2.5 (multi-modal)', () => {
+ expect(getModelMaxTokens('kimi-k2.5')).toBe(maxTokensMap[EModelEndpoint.openAI]['kimi-k2.5']);
+ expect(getModelMaxTokens('kimi-k2.5-latest')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2.5'],
+ );
});
- test('should return correct tokens for Kimi models with provider prefix', () => {
- expect(getModelMaxTokens('moonshotai/kimi-k2')).toBe(131000);
- expect(getModelMaxTokens('moonshotai/kimi')).toBe(131000);
- expect(getModelMaxTokens('moonshotai/kimi-vl')).toBe(131000);
+ test('should return correct tokens for kimi-k2 series models', () => {
+ expect(getModelMaxTokens('kimi')).toBe(maxTokensMap[EModelEndpoint.openAI]['kimi']);
+ expect(getModelMaxTokens('kimi-k2')).toBe(maxTokensMap[EModelEndpoint.openAI]['kimi-k2']);
+ expect(getModelMaxTokens('kimi-k2-turbo')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-turbo'],
+ );
+ expect(getModelMaxTokens('kimi-k2-turbo-preview')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-turbo-preview'],
+ );
+ expect(getModelMaxTokens('kimi-k2-0905')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-0905'],
+ );
+ expect(getModelMaxTokens('kimi-k2-0905-preview')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-0905-preview'],
+ );
+ expect(getModelMaxTokens('kimi-k2-thinking')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-thinking'],
+ );
+ expect(getModelMaxTokens('kimi-k2-thinking-turbo')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-thinking-turbo'],
+ );
});
- test('should handle partial matches for Kimi models', () => {
- expect(getModelMaxTokens('kimi-k2-latest')).toBe(131000);
- expect(getModelMaxTokens('kimi-vl-preview')).toBe(131000);
- expect(getModelMaxTokens('kimi-2024')).toBe(131000);
+ test('should return correct tokens for kimi-k2-0711 (smaller context)', () => {
+ expect(getModelMaxTokens('kimi-k2-0711')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-0711'],
+ );
+ expect(getModelMaxTokens('kimi-k2-0711-preview')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-0711-preview'],
+ );
+ });
+
+ test('should return correct tokens for kimi-latest', () => {
+ expect(getModelMaxTokens('kimi-latest')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-latest'],
+ );
+ });
+
+ test('should return correct tokens for moonshot-v1 series models', () => {
+ expect(getModelMaxTokens('moonshot')).toBe(maxTokensMap[EModelEndpoint.openAI]['moonshot']);
+ expect(getModelMaxTokens('moonshot-v1')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-auto')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-auto'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-8k')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-8k'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-8k-vision')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-8k-vision'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-8k-vision-preview')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-8k-vision-preview'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-32k')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-32k'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-32k-vision')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-32k-vision'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-32k-vision-preview')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-32k-vision-preview'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-128k')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-128k'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-128k-vision')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-128k-vision'],
+ );
+ expect(getModelMaxTokens('moonshot-v1-128k-vision-preview')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-128k-vision-preview'],
+ );
+ });
+
+ test('should return correct tokens for Bedrock moonshot models', () => {
+ expect(getModelMaxTokens('moonshot.kimi', EModelEndpoint.bedrock)).toBe(
+ maxTokensMap[EModelEndpoint.bedrock]['moonshot.kimi'],
+ );
+ expect(getModelMaxTokens('moonshot.kimi-k2', EModelEndpoint.bedrock)).toBe(
+ maxTokensMap[EModelEndpoint.bedrock]['moonshot.kimi-k2'],
+ );
+ expect(getModelMaxTokens('moonshot.kimi-k2.5', EModelEndpoint.bedrock)).toBe(
+ maxTokensMap[EModelEndpoint.bedrock]['moonshot.kimi-k2.5'],
+ );
+ expect(getModelMaxTokens('moonshot.kimi-k2-thinking', EModelEndpoint.bedrock)).toBe(
+ maxTokensMap[EModelEndpoint.bedrock]['moonshot.kimi-k2-thinking'],
+ );
+ expect(getModelMaxTokens('moonshot.kimi-k2-0711', EModelEndpoint.bedrock)).toBe(
+ maxTokensMap[EModelEndpoint.bedrock]['moonshot.kimi-k2-0711'],
+ );
+ });
+
+ test('should handle Moonshot/Kimi models with provider prefixes', () => {
+ expect(getModelMaxTokens('openrouter/kimi-k2')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2'],
+ );
+ expect(getModelMaxTokens('openrouter/kimi-k2.5')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2.5'],
+ );
+ expect(getModelMaxTokens('openrouter/kimi-k2-turbo')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['kimi-k2-turbo'],
+ );
+ expect(getModelMaxTokens('openrouter/moonshot-v1-128k')).toBe(
+ maxTokensMap[EModelEndpoint.openAI]['moonshot-v1-128k'],
+ );
});
});
describe('matchModelName', () => {
test('should match exact Kimi model names', () => {
expect(matchModelName('kimi')).toBe('kimi');
- expect(matchModelName('kimi-k2')).toBe('kimi');
- expect(matchModelName('kimi-vl')).toBe('kimi');
+ expect(matchModelName('kimi-k2')).toBe('kimi-k2');
+ expect(matchModelName('kimi-k2.5')).toBe('kimi-k2.5');
+ expect(matchModelName('kimi-k2-turbo')).toBe('kimi-k2-turbo');
+ expect(matchModelName('kimi-k2-0711')).toBe('kimi-k2-0711');
+ });
+
+ test('should match moonshot model names', () => {
+ expect(matchModelName('moonshot')).toBe('moonshot');
+ expect(matchModelName('moonshot-v1-8k')).toBe('moonshot-v1-8k');
+ expect(matchModelName('moonshot-v1-32k')).toBe('moonshot-v1-32k');
+ expect(matchModelName('moonshot-v1-128k')).toBe('moonshot-v1-128k');
});
test('should match Kimi model variations with provider prefix', () => {
- expect(matchModelName('moonshotai/kimi')).toBe('kimi');
- expect(matchModelName('moonshotai/kimi-k2')).toBe('kimi');
- expect(matchModelName('moonshotai/kimi-vl')).toBe('kimi');
+ expect(matchModelName('openrouter/kimi')).toBe('kimi');
+ expect(matchModelName('openrouter/kimi-k2')).toBe('kimi-k2');
+ expect(matchModelName('openrouter/kimi-k2.5')).toBe('kimi-k2.5');
});
test('should match Kimi model variations with suffixes', () => {
- expect(matchModelName('kimi-k2-latest')).toBe('kimi');
- expect(matchModelName('kimi-vl-preview')).toBe('kimi');
- expect(matchModelName('kimi-2024')).toBe('kimi');
+ expect(matchModelName('kimi-k2-latest')).toBe('kimi-k2');
+ expect(matchModelName('kimi-k2.5-preview')).toBe('kimi-k2.5');
});
});
});
diff --git a/client/src/hooks/Endpoint/UnknownIcon.tsx b/client/src/hooks/Endpoint/UnknownIcon.tsx
index 20619e0b7d..be7531a34a 100644
--- a/client/src/hooks/Endpoint/UnknownIcon.tsx
+++ b/client/src/hooks/Endpoint/UnknownIcon.tsx
@@ -1,6 +1,6 @@
import { memo } from 'react';
-import { CustomMinimalIcon, XAIcon } from '@librechat/client';
import { EModelEndpoint, KnownEndpoints } from 'librechat-data-provider';
+import { CustomMinimalIcon, XAIcon, MoonshotIcon } from '@librechat/client';
import { IconContext } from '~/common';
import { cn } from '~/utils';
@@ -30,9 +30,6 @@ const knownEndpointClasses = {
[KnownEndpoints.cohere]: {
[IconContext.landing]: 'p-2',
},
- [KnownEndpoints.xai]: {
- [IconContext.landing]: 'p-2',
- },
};
const getKnownClass = ({
@@ -73,15 +70,11 @@ function UnknownIcon({
const currentEndpoint = endpoint.toLowerCase();
if (currentEndpoint === KnownEndpoints.xai) {
- return (
-
- );
+ return ;
+ }
+
+ if (currentEndpoint === KnownEndpoints.moonshot) {
+ return ;
}
if (iconURL) {
diff --git a/packages/api/src/agents/run.ts b/packages/api/src/agents/run.ts
index 8544d01300..189ef59469 100644
--- a/packages/api/src/agents/run.ts
+++ b/packages/api/src/agents/run.ts
@@ -132,6 +132,7 @@ export function overrideDeferLoadingForDiscoveredTools(
const customProviders = new Set([
Providers.XAI,
Providers.DEEPSEEK,
+ Providers.MOONSHOT,
Providers.OPENROUTER,
KnownEndpoints.ollama,
]);
diff --git a/packages/api/src/endpoints/config.ts b/packages/api/src/endpoints/config.ts
index 041f8ca73d..97246fa336 100644
--- a/packages/api/src/endpoints/config.ts
+++ b/packages/api/src/endpoints/config.ts
@@ -21,7 +21,7 @@ export type InitializeFn = (params: BaseInitializeParams) => Promise = {
[Providers.XAI]: initializeCustom,
[Providers.DEEPSEEK]: initializeCustom,
+ [Providers.MOONSHOT]: initializeCustom,
[Providers.OPENROUTER]: initializeCustom,
[EModelEndpoint.openAI]: initializeOpenAI,
[EModelEndpoint.google]: initializeGoogle,
diff --git a/packages/api/src/utils/tokens.ts b/packages/api/src/utils/tokens.ts
index 750a2c9244..37dbb7c8da 100644
--- a/packages/api/src/utils/tokens.ts
+++ b/packages/api/src/utils/tokens.ts
@@ -2,6 +2,34 @@ import z from 'zod';
import { EModelEndpoint } from 'librechat-data-provider';
import type { EndpointTokenConfig, TokenConfig } from '~/types';
+/**
+ * Model Token Configuration Maps
+ *
+ * IMPORTANT: Key Ordering for Pattern Matching
+ * ============================================
+ * The `findMatchingPattern` function iterates through object keys in REVERSE order
+ * (last-defined keys are checked first) and uses `modelName.includes(key)` for matching.
+ *
+ * This means:
+ * 1. BASE PATTERNS must be defined FIRST (e.g., "kimi", "moonshot")
+ * 2. SPECIFIC PATTERNS must be defined AFTER their base patterns (e.g., "kimi-k2", "kimi-k2.5")
+ *
+ * Example ordering for Kimi models:
+ * kimi: 262144, // Base pattern - checked last
+ * 'kimi-k2': 262144, // More specific - checked before "kimi"
+ * 'kimi-k2.5': 262144, // Most specific - checked first
+ *
+ * Why this matters:
+ * - Model name "kimi-k2.5" contains both "kimi" and "kimi-k2" as substrings
+ * - If "kimi" were checked first, it would incorrectly match "kimi-k2.5"
+ * - By defining specific patterns AFTER base patterns, they're checked first in reverse iteration
+ *
+ * When adding new model families:
+ * 1. Define the base/generic pattern first
+ * 2. Define increasingly specific patterns after
+ * 3. Ensure no pattern is a substring of another that should match differently
+ */
+
const openAIModels = {
'o4-mini': 200000,
'o3-mini': 195000, // -5000 from max
@@ -134,6 +162,42 @@ const deepseekModels = {
'deepseek.r1': 128000,
};
+const moonshotModels = {
+ // Base patterns (check last due to reverse iteration)
+ kimi: 262144,
+ moonshot: 131072,
+ // kimi-k2 series (specific patterns)
+ 'kimi-latest': 128000,
+ 'kimi-k2': 262144,
+ 'kimi-k2.5': 262144,
+ 'kimi-k2-turbo': 262144,
+ 'kimi-k2-turbo-preview': 262144,
+ 'kimi-k2-0905': 262144,
+ 'kimi-k2-0905-preview': 262144,
+ 'kimi-k2-0711': 131072,
+ 'kimi-k2-0711-preview': 131072,
+ 'kimi-k2-thinking': 262144,
+ 'kimi-k2-thinking-turbo': 262144,
+ // moonshot-v1 series (specific patterns)
+ 'moonshot-v1': 131072,
+ 'moonshot-v1-auto': 131072,
+ 'moonshot-v1-8k': 8192,
+ 'moonshot-v1-8k-vision': 8192,
+ 'moonshot-v1-8k-vision-preview': 8192,
+ 'moonshot-v1-32k': 32768,
+ 'moonshot-v1-32k-vision': 32768,
+ 'moonshot-v1-32k-vision-preview': 32768,
+ 'moonshot-v1-128k': 131072,
+ 'moonshot-v1-128k-vision': 131072,
+ 'moonshot-v1-128k-vision-preview': 131072,
+ // Bedrock moonshot models
+ 'moonshot.kimi': 262144,
+ 'moonshot.kimi-k2': 262144,
+ 'moonshot.kimi-k2.5': 262144,
+ 'moonshot.kimi-k2-thinking': 262144,
+ 'moonshot.kimi-k2-0711': 131072,
+};
+
const metaModels = {
// Basic patterns
llama3: 8000,
@@ -248,6 +312,7 @@ const bedrockModels = {
...mistralModels,
...cohereModels,
...deepseekModels,
+ ...moonshotModels,
...metaModels,
...ai21Models,
...amazonModels,
@@ -279,8 +344,6 @@ const aggregateModels = {
...bedrockModels,
...xAIModels,
...qwenModels,
- // misc.
- kimi: 131000,
// GPT-OSS
'gpt-oss': 131000,
'gpt-oss:20b': 131000,
diff --git a/packages/client/src/svgs/MoonshotIcon.tsx b/packages/client/src/svgs/MoonshotIcon.tsx
new file mode 100644
index 0000000000..599de9f058
--- /dev/null
+++ b/packages/client/src/svgs/MoonshotIcon.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+export default function MoonshotIcon({ className = '' }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/packages/client/src/svgs/index.ts b/packages/client/src/svgs/index.ts
index d3f8c6e45b..1d67d44552 100644
--- a/packages/client/src/svgs/index.ts
+++ b/packages/client/src/svgs/index.ts
@@ -73,3 +73,4 @@ export { default as SheetPaths } from './SheetPaths';
export { default as TextPaths } from './TextPaths';
export { default as VideoPaths } from './VideoPaths';
export { default as SharePointIcon } from './SharePointIcon';
+export { default as MoonshotIcon } from './MoonshotIcon';
diff --git a/packages/data-provider/src/config.ts b/packages/data-provider/src/config.ts
index 53a518b06d..03073e32f8 100644
--- a/packages/data-provider/src/config.ts
+++ b/packages/data-provider/src/config.ts
@@ -1046,6 +1046,7 @@ export enum KnownEndpoints {
cohere = 'cohere',
fireworks = 'fireworks',
deepseek = 'deepseek',
+ moonshot = 'moonshot',
groq = 'groq',
helicone = 'helicone',
huggingface = 'huggingface',
@@ -1090,6 +1091,7 @@ export const alternateName = {
[EModelEndpoint.bedrock]: 'AWS Bedrock',
[KnownEndpoints.ollama]: 'Ollama',
[KnownEndpoints.deepseek]: 'DeepSeek',
+ [KnownEndpoints.moonshot]: 'Moonshot',
[KnownEndpoints.xai]: 'xAI',
[KnownEndpoints.vercel]: 'Vercel',
[KnownEndpoints.helicone]: 'Helicone',
diff --git a/packages/data-provider/src/parsers.ts b/packages/data-provider/src/parsers.ts
index 85b22e7068..f7add8bc1d 100644
--- a/packages/data-provider/src/parsers.ts
+++ b/packages/data-provider/src/parsers.ts
@@ -227,6 +227,10 @@ export const getResponseSender = (endpointOption: Partial): s
return 'Mistral';
} else if (model && model.includes('deepseek')) {
return 'Deepseek';
+ } else if (model && model.includes('kimi')) {
+ return 'Kimi';
+ } else if (model && model.includes('moonshot')) {
+ return 'Moonshot';
} else if (model && model.includes('gpt-')) {
const gptVersion = extractGPTVersion(model);
return gptVersion || 'GPT';
@@ -264,6 +268,10 @@ export const getResponseSender = (endpointOption: Partial): s
return 'Mistral';
} else if (model && model.includes('deepseek')) {
return 'Deepseek';
+ } else if (model && model.includes('kimi')) {
+ return 'Kimi';
+ } else if (model && model.includes('moonshot')) {
+ return 'Moonshot';
} else if (model && model.includes('gpt-')) {
const gptVersion = extractGPTVersion(model);
return gptVersion || 'GPT';
diff --git a/packages/data-provider/src/schemas.ts b/packages/data-provider/src/schemas.ts
index d4c4740267..25a3b59481 100644
--- a/packages/data-provider/src/schemas.ts
+++ b/packages/data-provider/src/schemas.ts
@@ -38,6 +38,7 @@ export enum Providers {
MISTRALAI = 'mistralai',
MISTRAL = 'mistral',
DEEPSEEK = 'deepseek',
+ MOONSHOT = 'moonshot',
OPENROUTER = 'openrouter',
XAI = 'xai',
}
@@ -56,6 +57,7 @@ export const documentSupportedProviders = new Set([
Providers.MISTRALAI,
Providers.MISTRAL,
Providers.DEEPSEEK,
+ Providers.MOONSHOT,
Providers.OPENROUTER,
Providers.XAI,
]);
@@ -67,6 +69,7 @@ const openAILikeProviders = new Set([
Providers.MISTRALAI,
Providers.MISTRAL,
Providers.DEEPSEEK,
+ Providers.MOONSHOT,
Providers.OPENROUTER,
Providers.XAI,
]);