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:
@@ -4,6 +4,9 @@ import type { IProviderSetting } from '~/types/model';
|
||||
import { createOpenAI } from '@ai-sdk/openai';
|
||||
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 {
|
||||
abstract name: string;
|
||||
abstract staticModels: ModelInfo[];
|
||||
@@ -17,6 +20,48 @@ export abstract class BaseProvider implements ProviderInfo {
|
||||
labelForGetApiKey?: 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: {
|
||||
apiKeys?: Record<string, string>;
|
||||
providerSettings?: IProviderSetting;
|
||||
@@ -59,7 +104,6 @@ export abstract class BaseProvider implements ProviderInfo {
|
||||
serverEnv?: Record<string, string>;
|
||||
}): ModelInfo[] | null {
|
||||
if (!this.cachedDynamicModels) {
|
||||
// console.log('no dynamic models',this.name);
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -67,8 +111,8 @@ export abstract class BaseProvider implements ProviderInfo {
|
||||
const generatedCacheKey = this.getDynamicModelsCacheKey(options);
|
||||
|
||||
if (cacheKey !== generatedCacheKey) {
|
||||
// console.log('cache key mismatch',this.name,cacheKey,generatedCacheKey);
|
||||
this.cachedDynamicModels = undefined;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -79,10 +123,20 @@ export abstract class BaseProvider implements ProviderInfo {
|
||||
providerSettings?: Record<string, IProviderSetting>;
|
||||
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({
|
||||
apiKeys: options.apiKeys?.[this.name],
|
||||
providerSettings: options.providerSettings?.[this.name],
|
||||
serverEnv: options.serverEnv,
|
||||
serverEnv: relevantEnv,
|
||||
});
|
||||
}
|
||||
storeDynamicModels(
|
||||
@@ -95,7 +149,6 @@ export abstract class BaseProvider implements ProviderInfo {
|
||||
) {
|
||||
const cacheId = this.getDynamicModelsCacheKey(options);
|
||||
|
||||
// console.log('caching dynamic models',this.name,cacheId);
|
||||
this.cachedDynamicModels = {
|
||||
cacheId,
|
||||
models,
|
||||
|
||||
Reference in New Issue
Block a user