feat: add Cerebras and Fireworks AI LLM providers (#2113)
* fix: improve local model provider robustness and UX - Extract shared Docker URL rewriting and env conversion into BaseProvider to eliminate 4x duplicated code across Ollama and LMStudio - Add error handling and 5s timeouts to all model-listing fetches so one unreachable provider doesn't block the entire model list - Fix Ollama using createOllama() instead of mutating provider internals - Fix LLMManager singleton ignoring env updates on subsequent requests - Narrow cache key to only include provider-relevant env vars instead of the entire server environment - Fix 'as any' casts in LMStudio and OpenAILike by using shared convertEnvToRecord helper - Replace console.log/error with structured logger in OpenAILike - Fix typo: filteredStaticModesl -> filteredStaticModels in manager - Add connection status indicator (green/red dot) for local providers in the ModelSelector dropdown - Show helpful "is X running?" message when local provider has no models Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * feat: add Cerebras LLM provider - Add Cerebras provider with 8 models (Llama, GPT OSS, Qwen, ZAI GLM) - Integrate @ai-sdk/cerebras@0.2.16 for compatibility - Add CEREBRAS_API_KEY to environment configuration - Register provider in LLMManager registry Models included: - llama3.1-8b, llama-3.3-70b - gpt-oss-120b (reasoning) - qwen-3-32b, qwen-3-235b variants - zai-glm-4.6, zai-glm-4.7 (reasoning) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: add Fireworks AI LLM provider - Add Fireworks provider with 6 popular models - Integrate @ai-sdk/fireworks@0.2.16 for compatibility - Add FIREWORKS_API_KEY to environment configuration - Register provider in LLMManager registry Models included: - Llama 3.1 variants (405B, 70B, 8B Instruct) - DeepSeek R1 (reasoning model) - Qwen 2.5 72B Instruct - FireFunction V2 Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: add coding-specific models to existing providers Enhanced providers with state-of-the-art coding models: **DeepSeek Provider:** + DeepSeek V3.2 (integrates thinking + tool-use) + DeepSeek V3.2-Speciale (high-compute variant, beats GPT-5) **Fireworks Provider:** + Qwen3-Coder 480B (262K context, best for coding) + Qwen3-Coder 30B (fast coding specialist) **Cerebras Provider:** + Qwen3-Coder 480B (2000 tokens/sec!) - Removed deprecated models (qwen-3-32b, llama-3.3-70b) Total new models: 4 Total coding models across all providers: 12+ Performance highlights: - Qwen3-Coder: State-of-the-art coding performance - DeepSeek V3.2: Integrates thinking directly into tool-use - ZAI GLM 4.6: 73.8% SWE-bench score - Ultra-fast inference: 2000 tok/s on Cerebras Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: add dynamic model discovery to providers Implemented getDynamicModels() for automatic model discovery: **DeepSeek Provider:** - Fetches models from https://api.deepseek.com/models - Automatically discovers new models as DeepSeek adds them - Filters out static models to avoid duplicates **Cerebras Provider:** - Fetches models from https://api.cerebras.ai/v1/models - Auto-discovers new Cerebras models - Keeps UI up-to-date with latest offerings **Fireworks Provider:** - Fetches from https://api.fireworks.ai/v1/accounts/fireworks/models - Includes context_length from API response - Discovers new Qwen-Coder and other models automatically **Moonshot Provider:** - Fetches from https://api.moonshot.ai/v1/models - OpenAI-compatible endpoint - Auto-discovers new Kimi models Benefits: - ✅ No manual updates needed when providers add new models - ✅ Users always have access to latest models - ✅ Graceful fallback to static models if API fails - ✅ 5-second timeout prevents hanging - ✅ Caching system built into BaseProvider Technical details: - Uses BaseProvider's built-in caching system - Cache invalidates when API keys change - Failed API calls fallback to static models - All endpoints have 5-second timeout protection Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * feat: add Z.AI provider with GLM models and JWT authentication Merged changes from PR #2069 to add Z.AI provider: - Added GLM-4.6 (200K), GLM-4.5 (128K), and GLM-4.5 Flash models - Implemented secure JWT token generation with HMAC-SHA256 signing - Added dynamic model discovery from Z.AI API - Included proper error handling and token validation - GLM-4.6 achieves 73.8% on SWE-bench coding benchmarks Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
12
.env.example
12
.env.example
@@ -12,6 +12,14 @@
|
|||||||
# Get your API key from: https://console.anthropic.com/
|
# Get your API key from: https://console.anthropic.com/
|
||||||
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||||
|
|
||||||
|
# Cerebras (High-performance inference)
|
||||||
|
# Get your API key from: https://cloud.cerebras.ai/settings
|
||||||
|
CEREBRAS_API_KEY=your_cerebras_api_key_here
|
||||||
|
|
||||||
|
# Fireworks AI (Fast inference with FireAttention engine)
|
||||||
|
# Get your API key from: https://fireworks.ai/api-keys
|
||||||
|
FIREWORKS_API_KEY=your_fireworks_api_key_here
|
||||||
|
|
||||||
# OpenAI GPT models
|
# OpenAI GPT models
|
||||||
# Get your API key from: https://platform.openai.com/api-keys
|
# Get your API key from: https://platform.openai.com/api-keys
|
||||||
OPENAI_API_KEY=your_openai_api_key_here
|
OPENAI_API_KEY=your_openai_api_key_here
|
||||||
@@ -59,6 +67,10 @@ XAI_API_KEY=your_xai_api_key_here
|
|||||||
# Get your API key from: https://platform.moonshot.ai/console/api-keys
|
# Get your API key from: https://platform.moonshot.ai/console/api-keys
|
||||||
MOONSHOT_API_KEY=your_moonshot_api_key_here
|
MOONSHOT_API_KEY=your_moonshot_api_key_here
|
||||||
|
|
||||||
|
# Z.AI (GLM models with JWT authentication)
|
||||||
|
# Get your API key from: https://open.bigmodel.cn/usercenter/apikeys
|
||||||
|
ZAI_API_KEY=your_zai_api_key_here
|
||||||
|
|
||||||
# Hugging Face
|
# Hugging Face
|
||||||
# Get your API key from: https://huggingface.co/settings/tokens
|
# Get your API key from: https://huggingface.co/settings/tokens
|
||||||
HuggingFace_API_KEY=your_huggingface_api_key_here
|
HuggingFace_API_KEY=your_huggingface_api_key_here
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
|
|||||||
import type { KeyboardEvent } from 'react';
|
import type { KeyboardEvent } from 'react';
|
||||||
import type { ModelInfo } from '~/lib/modules/llm/types';
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
import { classNames } from '~/utils/classNames';
|
import { classNames } from '~/utils/classNames';
|
||||||
|
import { LOCAL_PROVIDERS } from '~/lib/stores/settings';
|
||||||
|
|
||||||
// Fuzzy search utilities
|
// Fuzzy search utilities
|
||||||
const levenshteinDistance = (str1: string, str2: string): number => {
|
const levenshteinDistance = (str1: string, str2: string): number => {
|
||||||
@@ -130,6 +131,32 @@ export const ModelSelector = ({
|
|||||||
const providerDropdownRef = useRef<HTMLDivElement>(null);
|
const providerDropdownRef = useRef<HTMLDivElement>(null);
|
||||||
const [showFreeModelsOnly, setShowFreeModelsOnly] = useState(false);
|
const [showFreeModelsOnly, setShowFreeModelsOnly] = useState(false);
|
||||||
|
|
||||||
|
type ConnectionStatus = 'unknown' | 'connected' | 'disconnected';
|
||||||
|
|
||||||
|
const [localProviderStatus, setLocalProviderStatus] = useState<Record<string, ConnectionStatus>>({});
|
||||||
|
|
||||||
|
// Check connectivity of local providers when provider list changes
|
||||||
|
useEffect(() => {
|
||||||
|
const checkLocalProviders = async () => {
|
||||||
|
const statuses: Record<string, 'connected' | 'disconnected'> = {};
|
||||||
|
|
||||||
|
for (const p of providerList) {
|
||||||
|
if (!LOCAL_PROVIDERS.includes(p.name)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the provider has models loaded, it's connected
|
||||||
|
const hasModels = modelList.some((m) => m.provider === p.name);
|
||||||
|
|
||||||
|
statuses[p.name] = hasModels ? 'connected' : 'disconnected';
|
||||||
|
}
|
||||||
|
|
||||||
|
setLocalProviderStatus(statuses);
|
||||||
|
};
|
||||||
|
|
||||||
|
checkLocalProviders();
|
||||||
|
}, [providerList, modelList]);
|
||||||
|
|
||||||
// Debounce search queries
|
// Debounce search queries
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const timer = setTimeout(() => {
|
const timer = setTimeout(() => {
|
||||||
@@ -440,7 +467,28 @@ export const ModelSelector = ({
|
|||||||
tabIndex={0}
|
tabIndex={0}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div className="truncate">{provider?.name || 'Select provider'}</div>
|
<div className="flex items-center gap-2 truncate">
|
||||||
|
{provider?.name && LOCAL_PROVIDERS.includes(provider.name) && (
|
||||||
|
<span
|
||||||
|
className={classNames(
|
||||||
|
'inline-block w-2 h-2 rounded-full flex-shrink-0',
|
||||||
|
localProviderStatus[provider.name] === 'connected'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: localProviderStatus[provider.name] === 'disconnected'
|
||||||
|
? 'bg-red-400'
|
||||||
|
: 'bg-bolt-elements-textTertiary',
|
||||||
|
)}
|
||||||
|
title={
|
||||||
|
localProviderStatus[provider.name] === 'connected'
|
||||||
|
? `${provider.name} is running`
|
||||||
|
: localProviderStatus[provider.name] === 'disconnected'
|
||||||
|
? `${provider.name} is not reachable`
|
||||||
|
: 'Checking...'
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{provider?.name || 'Select provider'}
|
||||||
|
</div>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'i-ph:caret-down w-4 h-4 text-bolt-elements-textSecondary opacity-75',
|
'i-ph:caret-down w-4 h-4 text-bolt-elements-textSecondary opacity-75',
|
||||||
@@ -559,11 +607,25 @@ export const ModelSelector = ({
|
|||||||
}}
|
}}
|
||||||
tabIndex={focusedProviderIndex === index ? 0 : -1}
|
tabIndex={focusedProviderIndex === index ? 0 : -1}
|
||||||
>
|
>
|
||||||
<div
|
<div className="flex items-center gap-2">
|
||||||
dangerouslySetInnerHTML={{
|
{LOCAL_PROVIDERS.includes(providerOption.name) && (
|
||||||
__html: (providerOption as any).highlightedName || providerOption.name,
|
<span
|
||||||
}}
|
className={classNames(
|
||||||
/>
|
'inline-block w-2 h-2 rounded-full flex-shrink-0',
|
||||||
|
localProviderStatus[providerOption.name] === 'connected'
|
||||||
|
? 'bg-green-500'
|
||||||
|
: localProviderStatus[providerOption.name] === 'disconnected'
|
||||||
|
? 'bg-red-400'
|
||||||
|
: 'bg-bolt-elements-textTertiary',
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
<span
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: (providerOption as any).highlightedName || providerOption.name,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
)}
|
)}
|
||||||
@@ -717,8 +779,17 @@ export const ModelSelector = ({
|
|||||||
? `No models match "${debouncedModelSearchQuery}"${showFreeModelsOnly ? ' (free only)' : ''}`
|
? `No models match "${debouncedModelSearchQuery}"${showFreeModelsOnly ? ' (free only)' : ''}`
|
||||||
: showFreeModelsOnly
|
: showFreeModelsOnly
|
||||||
? 'No free models available'
|
? 'No free models available'
|
||||||
: 'No models available'}
|
: provider?.name && LOCAL_PROVIDERS.includes(provider.name)
|
||||||
|
? `No models found — is ${provider.name} running?`
|
||||||
|
: 'No models available'}
|
||||||
</div>
|
</div>
|
||||||
|
{!debouncedModelSearchQuery && provider?.name && LOCAL_PROVIDERS.includes(provider.name) && (
|
||||||
|
<div className="text-xs text-bolt-elements-textTertiary mt-1">
|
||||||
|
Make sure {provider.name} is running and has at least one model loaded.
|
||||||
|
{provider.name === 'Ollama' && ' Try: ollama pull llama3.2'}
|
||||||
|
{provider.name === 'LMStudio' && ' Load a model in LM Studio first.'}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
{debouncedModelSearchQuery && (
|
{debouncedModelSearchQuery && (
|
||||||
<div className="text-xs text-bolt-elements-textTertiary">
|
<div className="text-xs text-bolt-elements-textTertiary">
|
||||||
Try searching for model names, context sizes (e.g., "128k", "1M"), or capabilities
|
Try searching for model names, context sizes (e.g., "128k", "1M"), or capabilities
|
||||||
|
|||||||
@@ -4,6 +4,9 @@ import type { IProviderSetting } from '~/types/model';
|
|||||||
import { createOpenAI } from '@ai-sdk/openai';
|
import { createOpenAI } from '@ai-sdk/openai';
|
||||||
import { LLMManager } from './manager';
|
import { LLMManager } from './manager';
|
||||||
|
|
||||||
|
/** Default timeout for model listing API calls (5 seconds) */
|
||||||
|
const MODEL_FETCH_TIMEOUT = 5_000;
|
||||||
|
|
||||||
export abstract class BaseProvider implements ProviderInfo {
|
export abstract class BaseProvider implements ProviderInfo {
|
||||||
abstract name: string;
|
abstract name: string;
|
||||||
abstract staticModels: ModelInfo[];
|
abstract staticModels: ModelInfo[];
|
||||||
@@ -17,6 +20,48 @@ export abstract class BaseProvider implements ProviderInfo {
|
|||||||
labelForGetApiKey?: string;
|
labelForGetApiKey?: string;
|
||||||
icon?: string;
|
icon?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert Cloudflare Env bindings to a plain Record<string, string>.
|
||||||
|
* Useful because provider methods expect Record<string, string> but
|
||||||
|
* Cloudflare Workers pass an Env interface.
|
||||||
|
*/
|
||||||
|
protected convertEnvToRecord(env?: Env): Record<string, string> {
|
||||||
|
if (!env) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.entries(env).reduce(
|
||||||
|
(acc, [key, value]) => {
|
||||||
|
acc[key] = String(value);
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<string, string>,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rewrite localhost / 127.0.0.1 URLs to host.docker.internal when
|
||||||
|
* running inside Docker. Only applies on the server side.
|
||||||
|
*/
|
||||||
|
protected resolveDockerUrl(baseUrl: string, serverEnv?: Record<string, string>): string {
|
||||||
|
const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true';
|
||||||
|
|
||||||
|
if (!isDocker) {
|
||||||
|
return baseUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseUrl.replace('localhost', 'host.docker.internal').replace('127.0.0.1', 'host.docker.internal');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AbortSignal that times out after the given milliseconds.
|
||||||
|
* Used to prevent model-listing fetches from hanging indefinitely.
|
||||||
|
*/
|
||||||
|
protected createTimeoutSignal(ms: number = MODEL_FETCH_TIMEOUT): AbortSignal {
|
||||||
|
return AbortSignal.timeout(ms);
|
||||||
|
}
|
||||||
|
|
||||||
getProviderBaseUrlAndKey(options: {
|
getProviderBaseUrlAndKey(options: {
|
||||||
apiKeys?: Record<string, string>;
|
apiKeys?: Record<string, string>;
|
||||||
providerSettings?: IProviderSetting;
|
providerSettings?: IProviderSetting;
|
||||||
@@ -59,7 +104,6 @@ export abstract class BaseProvider implements ProviderInfo {
|
|||||||
serverEnv?: Record<string, string>;
|
serverEnv?: Record<string, string>;
|
||||||
}): ModelInfo[] | null {
|
}): ModelInfo[] | null {
|
||||||
if (!this.cachedDynamicModels) {
|
if (!this.cachedDynamicModels) {
|
||||||
// console.log('no dynamic models',this.name);
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -67,8 +111,8 @@ export abstract class BaseProvider implements ProviderInfo {
|
|||||||
const generatedCacheKey = this.getDynamicModelsCacheKey(options);
|
const generatedCacheKey = this.getDynamicModelsCacheKey(options);
|
||||||
|
|
||||||
if (cacheKey !== generatedCacheKey) {
|
if (cacheKey !== generatedCacheKey) {
|
||||||
// console.log('cache key mismatch',this.name,cacheKey,generatedCacheKey);
|
|
||||||
this.cachedDynamicModels = undefined;
|
this.cachedDynamicModels = undefined;
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -79,10 +123,20 @@ export abstract class BaseProvider implements ProviderInfo {
|
|||||||
providerSettings?: Record<string, IProviderSetting>;
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
serverEnv?: Record<string, string>;
|
serverEnv?: Record<string, string>;
|
||||||
}) {
|
}) {
|
||||||
|
// Only include provider-relevant env keys, not the entire server environment
|
||||||
|
const relevantEnvKeys = [this.config.baseUrlKey, this.config.apiTokenKey].filter(Boolean) as string[];
|
||||||
|
const relevantEnv: Record<string, string> = {};
|
||||||
|
|
||||||
|
for (const key of relevantEnvKeys) {
|
||||||
|
if (options.serverEnv?.[key]) {
|
||||||
|
relevantEnv[key] = options.serverEnv[key];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
apiKeys: options.apiKeys?.[this.name],
|
apiKeys: options.apiKeys?.[this.name],
|
||||||
providerSettings: options.providerSettings?.[this.name],
|
providerSettings: options.providerSettings?.[this.name],
|
||||||
serverEnv: options.serverEnv,
|
serverEnv: relevantEnv,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
storeDynamicModels(
|
storeDynamicModels(
|
||||||
@@ -95,7 +149,6 @@ export abstract class BaseProvider implements ProviderInfo {
|
|||||||
) {
|
) {
|
||||||
const cacheId = this.getDynamicModelsCacheKey(options);
|
const cacheId = this.getDynamicModelsCacheKey(options);
|
||||||
|
|
||||||
// console.log('caching dynamic models',this.name,cacheId);
|
|
||||||
this.cachedDynamicModels = {
|
this.cachedDynamicModels = {
|
||||||
cacheId,
|
cacheId,
|
||||||
models,
|
models,
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export class LLMManager {
|
|||||||
private static _instance: LLMManager;
|
private static _instance: LLMManager;
|
||||||
private _providers: Map<string, BaseProvider> = new Map();
|
private _providers: Map<string, BaseProvider> = new Map();
|
||||||
private _modelList: ModelInfo[] = [];
|
private _modelList: ModelInfo[] = [];
|
||||||
private readonly _env: any = {};
|
private _env: Record<string, string> = {};
|
||||||
|
|
||||||
private constructor(_env: Record<string, string>) {
|
private constructor(_env: Record<string, string>) {
|
||||||
this._registerProvidersFromDirectory();
|
this._registerProvidersFromDirectory();
|
||||||
@@ -19,6 +19,9 @@ export class LLMManager {
|
|||||||
static getInstance(env: Record<string, string> = {}): LLMManager {
|
static getInstance(env: Record<string, string> = {}): LLMManager {
|
||||||
if (!LLMManager._instance) {
|
if (!LLMManager._instance) {
|
||||||
LLMManager._instance = new LLMManager(env);
|
LLMManager._instance = new LLMManager(env);
|
||||||
|
} else if (Object.keys(env).length > 0) {
|
||||||
|
// Update env on subsequent calls so Cloudflare Workers get fresh bindings
|
||||||
|
LLMManager._instance._env = env;
|
||||||
}
|
}
|
||||||
|
|
||||||
return LLMManager._instance;
|
return LLMManager._instance;
|
||||||
@@ -121,10 +124,10 @@ export class LLMManager {
|
|||||||
const staticModels = Array.from(this._providers.values()).flatMap((p) => p.staticModels || []);
|
const staticModels = Array.from(this._providers.values()).flatMap((p) => p.staticModels || []);
|
||||||
const dynamicModelsFlat = dynamicModels.flat();
|
const dynamicModelsFlat = dynamicModels.flat();
|
||||||
const dynamicModelKeys = dynamicModelsFlat.map((d) => `${d.name}-${d.provider}`);
|
const dynamicModelKeys = dynamicModelsFlat.map((d) => `${d.name}-${d.provider}`);
|
||||||
const filteredStaticModesl = staticModels.filter((m) => !dynamicModelKeys.includes(`${m.name}-${m.provider}`));
|
const filteredStaticModels = staticModels.filter((m) => !dynamicModelKeys.includes(`${m.name}-${m.provider}`));
|
||||||
|
|
||||||
// Combine static and dynamic models
|
// Combine static and dynamic models
|
||||||
const modelList = [...dynamicModelsFlat, ...filteredStaticModesl];
|
const modelList = [...dynamicModelsFlat, ...filteredStaticModels];
|
||||||
modelList.sort((a, b) => a.name.localeCompare(b.name));
|
modelList.sort((a, b) => a.name.localeCompare(b.name));
|
||||||
this._modelList = modelList;
|
this._modelList = modelList;
|
||||||
|
|
||||||
|
|||||||
137
app/lib/modules/llm/providers/cerebras.ts
Normal file
137
app/lib/modules/llm/providers/cerebras.ts
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
import { BaseProvider } from '~/lib/modules/llm/base-provider';
|
||||||
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
|
import type { IProviderSetting } from '~/types/model';
|
||||||
|
import type { LanguageModelV1 } from 'ai';
|
||||||
|
import { createCerebras } from '@ai-sdk/cerebras';
|
||||||
|
|
||||||
|
export default class CerebrasProvider extends BaseProvider {
|
||||||
|
name = 'Cerebras';
|
||||||
|
getApiKeyLink = 'https://cloud.cerebras.ai/settings';
|
||||||
|
|
||||||
|
config = {
|
||||||
|
apiTokenKey: 'CEREBRAS_API_KEY',
|
||||||
|
};
|
||||||
|
|
||||||
|
staticModels: ModelInfo[] = [
|
||||||
|
{
|
||||||
|
name: 'qwen3-coder-480b',
|
||||||
|
label: 'Qwen3-Coder 480B (2000 tok/s, Best for Coding)',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 262000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'llama3.1-8b',
|
||||||
|
label: 'Llama 3.1 8B',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'gpt-oss-120b',
|
||||||
|
label: 'GPT OSS 120B (Reasoning)',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'qwen-3-235b-a22b-instruct-2507',
|
||||||
|
label: 'Qwen 3 235B A22B Instruct',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'qwen-3-235b-a22b-thinking-2507',
|
||||||
|
label: 'Qwen 3 235B A22B Thinking',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zai-glm-4.6',
|
||||||
|
label: 'ZAI GLM 4.6 (Coding: 73.8% SWE-bench)',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'zai-glm-4.7',
|
||||||
|
label: 'ZAI GLM 4.7 (Reasoning)',
|
||||||
|
provider: 'Cerebras',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv?: Record<string, string>,
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const { apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: settings,
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: '',
|
||||||
|
defaultApiTokenKey: 'CEREBRAS_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api.cerebras.ai/v1/models', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
signal: this.createTimeoutSignal(5000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Cerebras API error: ${response.statusText}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as any;
|
||||||
|
const staticModelIds = this.staticModels.map((m) => m.name);
|
||||||
|
|
||||||
|
// Filter out models we already have in staticModels
|
||||||
|
const dynamicModels =
|
||||||
|
data.data
|
||||||
|
?.filter((model: any) => !staticModelIds.includes(model.id))
|
||||||
|
.map((m: any) => ({
|
||||||
|
name: m.id,
|
||||||
|
label: `${m.id} (Dynamic)`,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: 32000, // Default, Cerebras typically has good context
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return dynamicModels;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch Cerebras models:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getModelInstance(options: {
|
||||||
|
model: string;
|
||||||
|
serverEnv: Env;
|
||||||
|
apiKeys?: Record<string, string>;
|
||||||
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
|
}): LanguageModelV1 {
|
||||||
|
const { model, serverEnv, apiKeys, providerSettings } = options;
|
||||||
|
|
||||||
|
const { apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: providerSettings?.[this.name],
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: '',
|
||||||
|
defaultApiTokenKey: 'CEREBRAS_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(`Missing API key for ${this.name} provider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const cerebras = createCerebras({
|
||||||
|
apiKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
return cerebras(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,8 +34,74 @@ export default class DeepseekProvider extends BaseProvider {
|
|||||||
maxTokenAllowed: 8000,
|
maxTokenAllowed: 8000,
|
||||||
maxCompletionTokens: 8192,
|
maxCompletionTokens: 8192,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: 'deepseek-v3.2',
|
||||||
|
label: 'DeepSeek V3.2 (Coding + Tool Use)',
|
||||||
|
provider: 'Deepseek',
|
||||||
|
maxTokenAllowed: 64000,
|
||||||
|
maxCompletionTokens: 8192,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'deepseek-v3.2-speciale',
|
||||||
|
label: 'DeepSeek V3.2 Speciale (High-Compute)',
|
||||||
|
provider: 'Deepseek',
|
||||||
|
maxTokenAllowed: 64000,
|
||||||
|
maxCompletionTokens: 8192,
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv?: Record<string, string>,
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const { apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: settings,
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: '',
|
||||||
|
defaultApiTokenKey: 'DEEPSEEK_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api.deepseek.com/models', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
signal: this.createTimeoutSignal(5000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`DeepSeek API error: ${response.statusText}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as any;
|
||||||
|
const staticModelIds = this.staticModels.map((m) => m.name);
|
||||||
|
|
||||||
|
// Filter out models we already have in staticModels
|
||||||
|
const dynamicModels =
|
||||||
|
data.data
|
||||||
|
?.filter((model: any) => !staticModelIds.includes(model.id))
|
||||||
|
.map((m: any) => ({
|
||||||
|
name: m.id,
|
||||||
|
label: `${m.id} (Dynamic)`,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: 64000, // Default, adjust per model if available
|
||||||
|
maxCompletionTokens: 8192,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return dynamicModels;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch DeepSeek models:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getModelInstance(options: {
|
getModelInstance(options: {
|
||||||
model: string;
|
model: string;
|
||||||
serverEnv: Env;
|
serverEnv: Env;
|
||||||
|
|||||||
147
app/lib/modules/llm/providers/fireworks.ts
Normal file
147
app/lib/modules/llm/providers/fireworks.ts
Normal file
@@ -0,0 +1,147 @@
|
|||||||
|
import { BaseProvider } from '~/lib/modules/llm/base-provider';
|
||||||
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
|
import type { IProviderSetting } from '~/types/model';
|
||||||
|
import type { LanguageModelV1 } from 'ai';
|
||||||
|
import { createFireworks } from '@ai-sdk/fireworks';
|
||||||
|
|
||||||
|
export default class FireworksProvider extends BaseProvider {
|
||||||
|
name = 'Fireworks';
|
||||||
|
getApiKeyLink = 'https://fireworks.ai/api-keys';
|
||||||
|
|
||||||
|
config = {
|
||||||
|
apiTokenKey: 'FIREWORKS_API_KEY',
|
||||||
|
};
|
||||||
|
|
||||||
|
staticModels: ModelInfo[] = [
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/qwen3-coder-480b-a35b-instruct',
|
||||||
|
label: 'Qwen3-Coder 480B (Best for Coding)',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 262000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/qwen3-coder-30b-a3b-instruct',
|
||||||
|
label: 'Qwen3-Coder 30B (Fast Coding)',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 262000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/llama-v3p1-405b-instruct',
|
||||||
|
label: 'Llama 3.1 405B Instruct',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 128000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/llama-v3p1-70b-instruct',
|
||||||
|
label: 'Llama 3.1 70B Instruct',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 128000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/llama-v3p1-8b-instruct',
|
||||||
|
label: 'Llama 3.1 8B Instruct',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 128000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/deepseek-r1',
|
||||||
|
label: 'DeepSeek R1 (Reasoning)',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 64000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/qwen2p5-72b-instruct',
|
||||||
|
label: 'Qwen 2.5 72B Instruct',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 128000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'accounts/fireworks/models/firefunction-v2',
|
||||||
|
label: 'FireFunction V2',
|
||||||
|
provider: 'Fireworks',
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv?: Record<string, string>,
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const { apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: settings,
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: '',
|
||||||
|
defaultApiTokenKey: 'FIREWORKS_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Try the accounts/fireworks/models endpoint which lists public models
|
||||||
|
const response = await fetch('https://api.fireworks.ai/v1/accounts/fireworks/models?page_size=100', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
signal: this.createTimeoutSignal(5000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Fireworks API error: ${response.statusText}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as any;
|
||||||
|
const staticModelIds = this.staticModels.map((m) => m.name);
|
||||||
|
|
||||||
|
// Filter out models we already have in staticModels
|
||||||
|
const dynamicModels =
|
||||||
|
data.data
|
||||||
|
?.filter((model: any) => {
|
||||||
|
const modelPath = `accounts/fireworks/models/${model.id}`;
|
||||||
|
return !staticModelIds.includes(modelPath) && !staticModelIds.includes(model.id);
|
||||||
|
})
|
||||||
|
.map((m: any) => ({
|
||||||
|
name: `accounts/fireworks/models/${m.id}`,
|
||||||
|
label: `${m.id} (Dynamic)`,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: m.context_length || 128000,
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return dynamicModels;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch Fireworks models:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getModelInstance(options: {
|
||||||
|
model: string;
|
||||||
|
serverEnv: Env;
|
||||||
|
apiKeys?: Record<string, string>;
|
||||||
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
|
}): LanguageModelV1 {
|
||||||
|
const { model, serverEnv, apiKeys, providerSettings } = options;
|
||||||
|
|
||||||
|
const { apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: providerSettings?.[this.name],
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: '',
|
||||||
|
defaultApiTokenKey: 'FIREWORKS_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(`Missing API key for ${this.name} provider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const fireworks = createFireworks({
|
||||||
|
apiKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
return fireworks(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -18,11 +18,11 @@ export default class LMStudioProvider extends BaseProvider {
|
|||||||
|
|
||||||
staticModels: ModelInfo[] = [];
|
staticModels: ModelInfo[] = [];
|
||||||
|
|
||||||
async getDynamicModels(
|
private _resolveBaseUrl(
|
||||||
apiKeys?: Record<string, string>,
|
apiKeys?: Record<string, string>,
|
||||||
settings?: IProviderSetting,
|
settings?: IProviderSetting,
|
||||||
serverEnv: Record<string, string> = {},
|
serverEnv?: Record<string, string>,
|
||||||
): Promise<ModelInfo[]> {
|
): string {
|
||||||
let { baseUrl } = this.getProviderBaseUrlAndKey({
|
let { baseUrl } = this.getProviderBaseUrlAndKey({
|
||||||
apiKeys,
|
apiKeys,
|
||||||
providerSettings: settings,
|
providerSettings: settings,
|
||||||
@@ -35,27 +35,54 @@ export default class LMStudioProvider extends BaseProvider {
|
|||||||
throw new Error('No baseUrl found for LMStudio provider');
|
throw new Error('No baseUrl found for LMStudio provider');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
baseUrl = this.resolveDockerUrl(baseUrl, serverEnv);
|
||||||
/*
|
|
||||||
* Running in Server
|
|
||||||
* Backend: Check if we're running in Docker
|
|
||||||
*/
|
|
||||||
const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true';
|
|
||||||
|
|
||||||
baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl;
|
return baseUrl;
|
||||||
baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/v1/models`);
|
|
||||||
const data = (await response.json()) as { data: Array<{ id: string }> };
|
|
||||||
|
|
||||||
return data.data.map((model) => ({
|
|
||||||
name: model.id,
|
|
||||||
label: model.id,
|
|
||||||
provider: this.name,
|
|
||||||
maxTokenAllowed: 8000,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv: Record<string, string> = {},
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const baseUrl = this._resolveBaseUrl(apiKeys, settings, serverEnv);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/v1/models`, {
|
||||||
|
signal: this.createTimeoutSignal(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as { data: Array<{ id: string }> };
|
||||||
|
|
||||||
|
return data.data.map((model) => ({
|
||||||
|
name: model.id,
|
||||||
|
label: model.id,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DOMException && error.name === 'TimeoutError') {
|
||||||
|
logger.warn('LMStudio model fetch timed out — is LM Studio running?');
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||||
|
logger.warn(`LMStudio not reachable at ${baseUrl} — is LM Studio running?`);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Error fetching LMStudio models:', error);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getModelInstance: (options: {
|
getModelInstance: (options: {
|
||||||
model: string;
|
model: string;
|
||||||
serverEnv?: Env;
|
serverEnv?: Env;
|
||||||
@@ -63,24 +90,9 @@ export default class LMStudioProvider extends BaseProvider {
|
|||||||
providerSettings?: Record<string, IProviderSetting>;
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
}) => LanguageModelV1 = (options) => {
|
}) => LanguageModelV1 = (options) => {
|
||||||
const { apiKeys, providerSettings, serverEnv, model } = options;
|
const { apiKeys, providerSettings, serverEnv, model } = options;
|
||||||
let { baseUrl } = this.getProviderBaseUrlAndKey({
|
const envRecord = this.convertEnvToRecord(serverEnv);
|
||||||
apiKeys,
|
|
||||||
providerSettings: providerSettings?.[this.name],
|
|
||||||
serverEnv: serverEnv as any,
|
|
||||||
defaultBaseUrlKey: 'LMSTUDIO_API_BASE_URL',
|
|
||||||
defaultApiTokenKey: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!baseUrl) {
|
const baseUrl = this._resolveBaseUrl(apiKeys, providerSettings?.[this.name], envRecord);
|
||||||
throw new Error('No baseUrl found for LMStudio provider');
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true';
|
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
|
||||||
baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl;
|
|
||||||
baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl;
|
|
||||||
}
|
|
||||||
|
|
||||||
logger.debug('LMStudio Base Url used: ', baseUrl);
|
logger.debug('LMStudio Base Url used: ', baseUrl);
|
||||||
|
|
||||||
|
|||||||
@@ -41,6 +41,57 @@ export default class MoonshotProvider extends BaseProvider {
|
|||||||
{ name: 'kimi-thinking-preview', label: 'Kimi Thinking', provider: 'Moonshot', maxTokenAllowed: 128000 },
|
{ name: 'kimi-thinking-preview', label: 'Kimi Thinking', provider: 'Moonshot', maxTokenAllowed: 128000 },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv?: Record<string, string>,
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const { apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: settings,
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: '',
|
||||||
|
defaultApiTokenKey: 'MOONSHOT_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://api.moonshot.ai/v1/models', {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${apiKey}`,
|
||||||
|
},
|
||||||
|
signal: this.createTimeoutSignal(5000),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
console.error(`Moonshot API error: ${response.statusText}`);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as any;
|
||||||
|
const staticModelIds = this.staticModels.map((m) => m.name);
|
||||||
|
|
||||||
|
// Filter out models we already have in staticModels
|
||||||
|
const dynamicModels =
|
||||||
|
data.data
|
||||||
|
?.filter((model: any) => !staticModelIds.includes(model.id))
|
||||||
|
.map((m: any) => ({
|
||||||
|
name: m.id,
|
||||||
|
label: `${m.id} (Dynamic)`,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: 128000, // Kimi models typically have large context
|
||||||
|
})) || [];
|
||||||
|
|
||||||
|
return dynamicModels;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch Moonshot models:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
getModelInstance(options: {
|
getModelInstance(options: {
|
||||||
model: string;
|
model: string;
|
||||||
serverEnv: Env;
|
serverEnv: Env;
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { BaseProvider } from '~/lib/modules/llm/base-provider';
|
|||||||
import type { ModelInfo } from '~/lib/modules/llm/types';
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
import type { IProviderSetting } from '~/types/model';
|
import type { IProviderSetting } from '~/types/model';
|
||||||
import type { LanguageModelV1 } from 'ai';
|
import type { LanguageModelV1 } from 'ai';
|
||||||
import { ollama } from 'ollama-ai-provider';
|
import { createOllama } from 'ollama-ai-provider';
|
||||||
import { logger } from '~/utils/logger';
|
import { logger } from '~/utils/logger';
|
||||||
|
|
||||||
interface OllamaModelDetails {
|
interface OllamaModelDetails {
|
||||||
@@ -39,31 +39,17 @@ export default class OllamaProvider extends BaseProvider {
|
|||||||
|
|
||||||
staticModels: ModelInfo[] = [];
|
staticModels: ModelInfo[] = [];
|
||||||
|
|
||||||
private _convertEnvToRecord(env?: Env): Record<string, string> {
|
|
||||||
if (!env) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Convert Env to a plain object with string values
|
|
||||||
return Object.entries(env).reduce(
|
|
||||||
(acc, [key, value]) => {
|
|
||||||
acc[key] = String(value);
|
|
||||||
return acc;
|
|
||||||
},
|
|
||||||
{} as Record<string, string>,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
getDefaultNumCtx(serverEnv?: Env): number {
|
getDefaultNumCtx(serverEnv?: Env): number {
|
||||||
const envRecord = this._convertEnvToRecord(serverEnv);
|
const envRecord = this.convertEnvToRecord(serverEnv);
|
||||||
|
|
||||||
return envRecord.DEFAULT_NUM_CTX ? parseInt(envRecord.DEFAULT_NUM_CTX, 10) : 32768;
|
return envRecord.DEFAULT_NUM_CTX ? parseInt(envRecord.DEFAULT_NUM_CTX, 10) : 32768;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getDynamicModels(
|
private _resolveBaseUrl(
|
||||||
apiKeys?: Record<string, string>,
|
apiKeys?: Record<string, string>,
|
||||||
settings?: IProviderSetting,
|
settings?: IProviderSetting,
|
||||||
serverEnv: Record<string, string> = {},
|
serverEnv?: Record<string, string>,
|
||||||
): Promise<ModelInfo[]> {
|
): string {
|
||||||
let { baseUrl } = this.getProviderBaseUrlAndKey({
|
let { baseUrl } = this.getProviderBaseUrlAndKey({
|
||||||
apiKeys,
|
apiKeys,
|
||||||
providerSettings: settings,
|
providerSettings: settings,
|
||||||
@@ -73,31 +59,55 @@ export default class OllamaProvider extends BaseProvider {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (!baseUrl) {
|
if (!baseUrl) {
|
||||||
throw new Error('No baseUrl found for OLLAMA provider');
|
throw new Error('No baseUrl found for Ollama provider');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof window === 'undefined') {
|
baseUrl = this.resolveDockerUrl(baseUrl, serverEnv);
|
||||||
/*
|
|
||||||
* Running in Server
|
|
||||||
* Backend: Check if we're running in Docker
|
|
||||||
*/
|
|
||||||
const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || serverEnv?.RUNNING_IN_DOCKER === 'true';
|
|
||||||
|
|
||||||
baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl;
|
return baseUrl;
|
||||||
baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl;
|
}
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv: Record<string, string> = {},
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const baseUrl = this._resolveBaseUrl(apiKeys, settings, serverEnv);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/api/tags`, {
|
||||||
|
signal: this.createTimeoutSignal(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = (await response.json()) as OllamaApiResponse;
|
||||||
|
|
||||||
|
return data.models.map((model: OllamaModel) => ({
|
||||||
|
name: model.name,
|
||||||
|
label: `${model.name} (${model.details.parameter_size})`,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: 8000,
|
||||||
|
}));
|
||||||
|
} catch (error) {
|
||||||
|
if (error instanceof DOMException && error.name === 'TimeoutError') {
|
||||||
|
logger.warn('Ollama model fetch timed out — is Ollama running?');
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof TypeError && error.message.includes('fetch')) {
|
||||||
|
logger.warn(`Ollama not reachable at ${baseUrl} — is Ollama running?`);
|
||||||
|
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.error('Error fetching Ollama models:', error);
|
||||||
|
|
||||||
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`${baseUrl}/api/tags`);
|
|
||||||
const data = (await response.json()) as OllamaApiResponse;
|
|
||||||
|
|
||||||
// console.log({ ollamamodels: data.models });
|
|
||||||
|
|
||||||
return data.models.map((model: OllamaModel) => ({
|
|
||||||
name: model.name,
|
|
||||||
label: `${model.name} (${model.details.parameter_size})`,
|
|
||||||
provider: this.name,
|
|
||||||
maxTokenAllowed: 8000,
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
getModelInstance: (options: {
|
getModelInstance: (options: {
|
||||||
@@ -107,33 +117,18 @@ export default class OllamaProvider extends BaseProvider {
|
|||||||
providerSettings?: Record<string, IProviderSetting>;
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
}) => LanguageModelV1 = (options) => {
|
}) => LanguageModelV1 = (options) => {
|
||||||
const { apiKeys, providerSettings, serverEnv, model } = options;
|
const { apiKeys, providerSettings, serverEnv, model } = options;
|
||||||
const envRecord = this._convertEnvToRecord(serverEnv);
|
const envRecord = this.convertEnvToRecord(serverEnv);
|
||||||
|
|
||||||
let { baseUrl } = this.getProviderBaseUrlAndKey({
|
const baseUrl = this._resolveBaseUrl(apiKeys, providerSettings?.[this.name], envRecord);
|
||||||
apiKeys,
|
|
||||||
providerSettings: providerSettings?.[this.name],
|
|
||||||
serverEnv: envRecord,
|
|
||||||
defaultBaseUrlKey: 'OLLAMA_API_BASE_URL',
|
|
||||||
defaultApiTokenKey: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
// Backend: Check if we're running in Docker
|
|
||||||
if (!baseUrl) {
|
|
||||||
throw new Error('No baseUrl found for OLLAMA provider');
|
|
||||||
}
|
|
||||||
|
|
||||||
const isDocker = process?.env?.RUNNING_IN_DOCKER === 'true' || envRecord.RUNNING_IN_DOCKER === 'true';
|
|
||||||
baseUrl = isDocker ? baseUrl.replace('localhost', 'host.docker.internal') : baseUrl;
|
|
||||||
baseUrl = isDocker ? baseUrl.replace('127.0.0.1', 'host.docker.internal') : baseUrl;
|
|
||||||
|
|
||||||
logger.debug('Ollama Base Url used: ', baseUrl);
|
logger.debug('Ollama Base Url used: ', baseUrl);
|
||||||
|
|
||||||
const ollamaInstance = ollama(model, {
|
const ollamaProvider = createOllama({
|
||||||
|
baseURL: `${baseUrl}/api`,
|
||||||
|
});
|
||||||
|
|
||||||
|
return ollamaProvider(model, {
|
||||||
numCtx: this.getDefaultNumCtx(serverEnv),
|
numCtx: this.getDefaultNumCtx(serverEnv),
|
||||||
}) as LanguageModelV1 & { config: any };
|
});
|
||||||
|
|
||||||
ollamaInstance.config.baseURL = `${baseUrl}/api`;
|
|
||||||
|
|
||||||
return ollamaInstance;
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,11 @@ import { BaseProvider, getOpenAILikeModel } from '~/lib/modules/llm/base-provide
|
|||||||
import type { ModelInfo } from '~/lib/modules/llm/types';
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
import type { IProviderSetting } from '~/types/model';
|
import type { IProviderSetting } from '~/types/model';
|
||||||
import type { LanguageModelV1 } from 'ai';
|
import type { LanguageModelV1 } from 'ai';
|
||||||
|
import { logger } from '~/utils/logger';
|
||||||
|
|
||||||
|
interface OpenAIModelsResponse {
|
||||||
|
data: Array<{ id: string }>;
|
||||||
|
}
|
||||||
|
|
||||||
export default class OpenAILikeProvider extends BaseProvider {
|
export default class OpenAILikeProvider extends BaseProvider {
|
||||||
name = 'OpenAILike';
|
name = 'OpenAILike';
|
||||||
@@ -37,29 +42,31 @@ export default class OpenAILikeProvider extends BaseProvider {
|
|||||||
headers: {
|
headers: {
|
||||||
Authorization: `Bearer ${apiKey}`,
|
Authorization: `Bearer ${apiKey}`,
|
||||||
},
|
},
|
||||||
|
signal: this.createTimeoutSignal(),
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = (await response.json()) as any;
|
const res = (await response.json()) as OpenAIModelsResponse;
|
||||||
|
|
||||||
return res.data.map((model: any) => ({
|
return res.data.map((model) => ({
|
||||||
name: model.id,
|
name: model.id,
|
||||||
label: model.id,
|
label: model.id,
|
||||||
provider: this.name,
|
provider: this.name,
|
||||||
maxTokenAllowed: 8000,
|
maxTokenAllowed: 8000,
|
||||||
}));
|
}));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`${this.name}: Not allowed to GET /models endpoint for provider`, error);
|
logger.info(`${this.name}: Could not fetch /models endpoint, checking fallback env`, error);
|
||||||
|
|
||||||
// Fallback to OPENAI_LIKE_API_MODELS if available
|
// Fallback to OPENAI_LIKE_API_MODELS if available
|
||||||
// eslint-disable-next-line dot-notation
|
// eslint-disable-next-line dot-notation
|
||||||
const modelsEnv = serverEnv['OPENAI_LIKE_API_MODELS'] || settings?.OPENAI_LIKE_API_MODELS;
|
const modelsEnv = serverEnv['OPENAI_LIKE_API_MODELS'] || settings?.OPENAI_LIKE_API_MODELS;
|
||||||
|
|
||||||
if (modelsEnv) {
|
if (modelsEnv) {
|
||||||
console.log(`${this.name}: OPENAI_LIKE_API_MODELS=${modelsEnv}`);
|
logger.info(`${this.name}: Using OPENAI_LIKE_API_MODELS fallback`);
|
||||||
|
|
||||||
return this._parseModelsFromEnv(modelsEnv);
|
return this._parseModelsFromEnv(modelsEnv);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,11 +114,11 @@ export default class OpenAILikeProvider extends BaseProvider {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`${this.name}: Parsed Models:`, models);
|
logger.info(`${this.name}: Parsed ${models.length} models from env`);
|
||||||
|
|
||||||
return models;
|
return models;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`${this.name}: Error parsing OPENAI_LIKE_API_MODELS:`, error);
|
logger.error(`${this.name}: Error parsing OPENAI_LIKE_API_MODELS:`, error);
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,11 +156,12 @@ export default class OpenAILikeProvider extends BaseProvider {
|
|||||||
providerSettings?: Record<string, IProviderSetting>;
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
}): LanguageModelV1 {
|
}): LanguageModelV1 {
|
||||||
const { model, serverEnv, apiKeys, providerSettings } = options;
|
const { model, serverEnv, apiKeys, providerSettings } = options;
|
||||||
|
const envRecord = this.convertEnvToRecord(serverEnv);
|
||||||
|
|
||||||
const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
apiKeys,
|
apiKeys,
|
||||||
providerSettings: providerSettings?.[this.name],
|
providerSettings: providerSettings?.[this.name],
|
||||||
serverEnv: serverEnv as any,
|
serverEnv: envRecord,
|
||||||
defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
|
defaultBaseUrlKey: 'OPENAI_LIKE_API_BASE_URL',
|
||||||
defaultApiTokenKey: 'OPENAI_LIKE_API_KEY',
|
defaultApiTokenKey: 'OPENAI_LIKE_API_KEY',
|
||||||
});
|
});
|
||||||
|
|||||||
193
app/lib/modules/llm/providers/z-ai.ts
Normal file
193
app/lib/modules/llm/providers/z-ai.ts
Normal file
@@ -0,0 +1,193 @@
|
|||||||
|
import { BaseProvider } from '~/lib/modules/llm/base-provider';
|
||||||
|
import type { IProviderSetting } from '~/types/model';
|
||||||
|
import type { LanguageModelV1 } from 'ai';
|
||||||
|
import type { ModelInfo } from '~/lib/modules/llm/types';
|
||||||
|
import { createOpenAI } from '@ai-sdk/openai';
|
||||||
|
import crypto from 'node:crypto';
|
||||||
|
|
||||||
|
export default class ZaiProvider extends BaseProvider {
|
||||||
|
name = 'Z.ai';
|
||||||
|
getApiKeyLink = 'https://open.bigmodel.cn/usercenter/apikeys';
|
||||||
|
|
||||||
|
config = {
|
||||||
|
baseUrlKey: 'ZAI_BASE_URL',
|
||||||
|
apiTokenKey: 'ZAI_API_KEY',
|
||||||
|
baseUrl: 'https://api.z.ai/api/coding/paas/v4', //Dedicated endpoint for Coding Plan
|
||||||
|
};
|
||||||
|
|
||||||
|
staticModels: ModelInfo[] = [
|
||||||
|
{
|
||||||
|
name: 'glm-4.6',
|
||||||
|
label: 'GLM-4.6 (200K)',
|
||||||
|
provider: 'Z.ai',
|
||||||
|
maxTokenAllowed: 200000,
|
||||||
|
maxCompletionTokens: 65536,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glm-4.5',
|
||||||
|
label: 'GLM-4.5 (128K)',
|
||||||
|
provider: 'Z.ai',
|
||||||
|
maxTokenAllowed: 128000,
|
||||||
|
maxCompletionTokens: 65536,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'glm-4.5-flash',
|
||||||
|
label: 'GLM-4.5 Flash (128K)',
|
||||||
|
provider: 'Z.ai',
|
||||||
|
maxTokenAllowed: 128000,
|
||||||
|
maxCompletionTokens: 65536,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
async getDynamicModels(
|
||||||
|
apiKeys?: Record<string, string>,
|
||||||
|
settings?: IProviderSetting,
|
||||||
|
serverEnv?: Record<string, string>,
|
||||||
|
): Promise<ModelInfo[]> {
|
||||||
|
const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: settings,
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: 'ZAI_BASE_URL',
|
||||||
|
defaultApiTokenKey: 'ZAI_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(`Missing Api Key configuration for ${this.name} provider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this._generateToken(apiKey);
|
||||||
|
|
||||||
|
if (!this._isValidToken(token)) {
|
||||||
|
throw new Error(`Invalid API key format for ${this.name} provider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${baseUrl}/models`, {
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Failed to fetch models: ${response.status} ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = (await response.json()) as any;
|
||||||
|
const staticModelIds = this.staticModels.map((m) => m.name);
|
||||||
|
|
||||||
|
// Filter out static models and only include GLM models
|
||||||
|
const data =
|
||||||
|
res.data?.filter(
|
||||||
|
(model: any) =>
|
||||||
|
model.object === 'model' && model.id?.startsWith('glm-') && !staticModelIds.includes(model.id),
|
||||||
|
) || [];
|
||||||
|
|
||||||
|
return data.map((m: any) => {
|
||||||
|
let contextWindow = 128000;
|
||||||
|
let maxCompletionTokens = 65536;
|
||||||
|
|
||||||
|
if (m.id?.includes('glm-4.6')) {
|
||||||
|
contextWindow = 200000;
|
||||||
|
maxCompletionTokens = 65536;
|
||||||
|
} else if (m.id?.includes('glm-4.5')) {
|
||||||
|
contextWindow = 128000;
|
||||||
|
maxCompletionTokens = 65536;
|
||||||
|
} else if (m.id?.includes('glm-4')) {
|
||||||
|
contextWindow = 128000;
|
||||||
|
maxCompletionTokens = 8192;
|
||||||
|
} else if (m.id?.includes('glm-3')) {
|
||||||
|
contextWindow = 32000;
|
||||||
|
maxCompletionTokens = 4096;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: m.id,
|
||||||
|
label: `${m.id} (${Math.floor(contextWindow / 1000)}k context)`,
|
||||||
|
provider: this.name,
|
||||||
|
maxTokenAllowed: contextWindow,
|
||||||
|
maxCompletionTokens,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to fetch dynamic models for ${this.name}:`, error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private _generateToken(apiKey: string): string {
|
||||||
|
try {
|
||||||
|
const [id, secret] = apiKey.split('.');
|
||||||
|
|
||||||
|
if (!id || !secret) {
|
||||||
|
throw new Error(`Invalid API key format for ${this.name}. Expected: id.secret`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const now = Date.now();
|
||||||
|
const payload = {
|
||||||
|
apiKey: id,
|
||||||
|
exp: now + 3600 * 1000,
|
||||||
|
timestamp: now,
|
||||||
|
};
|
||||||
|
|
||||||
|
const header = { alg: 'HS256', sign_type: 'SIGN' };
|
||||||
|
|
||||||
|
const base64Url = (obj: any) =>
|
||||||
|
Buffer.from(JSON.stringify(obj)).toString('base64').replace(/=/g, '').replace(/\+/g, '-').replace(/\//g, '_');
|
||||||
|
const signature = crypto
|
||||||
|
.createHmac('sha256', secret)
|
||||||
|
.update(base64Url(header) + '.' + base64Url(payload))
|
||||||
|
.digest('base64')
|
||||||
|
.replace(/=/g, '')
|
||||||
|
.replace(/\+/g, '-')
|
||||||
|
.replace(/\//g, '_');
|
||||||
|
|
||||||
|
return `${base64Url(header)}.${base64Url(payload)}.${signature}`;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to generate JWT token for ${this.name}:`, error);
|
||||||
|
throw new Error(`Failed to generate JWT token: ${error instanceof Error ? error.message : 'Unknown error'}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates JWT token format
|
||||||
|
*/
|
||||||
|
private _isValidToken(token: string): boolean {
|
||||||
|
try {
|
||||||
|
const parts = token.split('.');
|
||||||
|
return parts.length === 3 && parts.every((part) => part.length > 0);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getModelInstance(options: {
|
||||||
|
model: string;
|
||||||
|
serverEnv: Env;
|
||||||
|
apiKeys?: Record<string, string>;
|
||||||
|
providerSettings?: Record<string, IProviderSetting>;
|
||||||
|
}): LanguageModelV1 {
|
||||||
|
const { model, serverEnv, apiKeys, providerSettings } = options;
|
||||||
|
|
||||||
|
const { baseUrl, apiKey } = this.getProviderBaseUrlAndKey({
|
||||||
|
apiKeys,
|
||||||
|
providerSettings: providerSettings?.[this.name],
|
||||||
|
serverEnv: serverEnv as any,
|
||||||
|
defaultBaseUrlKey: 'ZAI_BASE_URL',
|
||||||
|
defaultApiTokenKey: 'ZAI_API_KEY',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
throw new Error(`Missing API key for ${this.name} provider`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const token = this._generateToken(apiKey);
|
||||||
|
const zaiClient = createOpenAI({
|
||||||
|
baseURL: baseUrl,
|
||||||
|
apiKey: token,
|
||||||
|
});
|
||||||
|
|
||||||
|
return zaiClient(model);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import AnthropicProvider from './providers/anthropic';
|
import AnthropicProvider from './providers/anthropic';
|
||||||
|
import CerebrasProvider from './providers/cerebras';
|
||||||
import CohereProvider from './providers/cohere';
|
import CohereProvider from './providers/cohere';
|
||||||
import DeepseekProvider from './providers/deepseek';
|
import DeepseekProvider from './providers/deepseek';
|
||||||
|
import FireworksProvider from './providers/fireworks';
|
||||||
import GoogleProvider from './providers/google';
|
import GoogleProvider from './providers/google';
|
||||||
import GroqProvider from './providers/groq';
|
import GroqProvider from './providers/groq';
|
||||||
import HuggingFaceProvider from './providers/huggingface';
|
import HuggingFaceProvider from './providers/huggingface';
|
||||||
@@ -17,11 +19,14 @@ import HyperbolicProvider from './providers/hyperbolic';
|
|||||||
import AmazonBedrockProvider from './providers/amazon-bedrock';
|
import AmazonBedrockProvider from './providers/amazon-bedrock';
|
||||||
import GithubProvider from './providers/github';
|
import GithubProvider from './providers/github';
|
||||||
import MoonshotProvider from './providers/moonshot';
|
import MoonshotProvider from './providers/moonshot';
|
||||||
|
import ZaiProvider from './providers/z-ai';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
AnthropicProvider,
|
AnthropicProvider,
|
||||||
|
CerebrasProvider,
|
||||||
CohereProvider,
|
CohereProvider,
|
||||||
DeepseekProvider,
|
DeepseekProvider,
|
||||||
|
FireworksProvider,
|
||||||
GoogleProvider,
|
GoogleProvider,
|
||||||
GroqProvider,
|
GroqProvider,
|
||||||
HuggingFaceProvider,
|
HuggingFaceProvider,
|
||||||
@@ -38,4 +43,5 @@ export {
|
|||||||
LMStudioProvider,
|
LMStudioProvider,
|
||||||
AmazonBedrockProvider,
|
AmazonBedrockProvider,
|
||||||
GithubProvider,
|
GithubProvider,
|
||||||
|
ZaiProvider,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,8 +48,10 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/amazon-bedrock": "1.0.6",
|
"@ai-sdk/amazon-bedrock": "1.0.6",
|
||||||
"@ai-sdk/anthropic": "0.0.39",
|
"@ai-sdk/anthropic": "0.0.39",
|
||||||
|
"@ai-sdk/cerebras": "^0.2.16",
|
||||||
"@ai-sdk/cohere": "1.0.3",
|
"@ai-sdk/cohere": "1.0.3",
|
||||||
"@ai-sdk/deepseek": "0.1.3",
|
"@ai-sdk/deepseek": "0.1.3",
|
||||||
|
"@ai-sdk/fireworks": "^0.2.16",
|
||||||
"@ai-sdk/google": "0.0.52",
|
"@ai-sdk/google": "0.0.52",
|
||||||
"@ai-sdk/mistral": "0.0.43",
|
"@ai-sdk/mistral": "0.0.43",
|
||||||
"@ai-sdk/openai": "1.1.2",
|
"@ai-sdk/openai": "1.1.2",
|
||||||
|
|||||||
546
pnpm-lock.yaml
generated
546
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
110
vite.config.ts.timestamp-1770328346417-a90f095482a09.mjs
Normal file
110
vite.config.ts.timestamp-1770328346417-a90f095482a09.mjs
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user