diff --git a/src/shared/providers/definitions/eden-ai-responses.ts b/src/shared/providers/definitions/eden-ai-responses.ts new file mode 100644 index 0000000000..6343c06657 --- /dev/null +++ b/src/shared/providers/definitions/eden-ai-responses.ts @@ -0,0 +1,64 @@ +import { ModelProviderEnum, ModelProviderType } from '../../types' +import { defineProvider } from '../registry' +import EdenAIResponses from './models/eden-ai-responses' + +// Eden AI Responses API: a stateful, OpenAI-compatible alternative to chat +// completions that stores conversation history server-side. +// Docs: https://www.edenai.co/docs/api-reference/responses/create-response +export const edenAIResponsesProvider = defineProvider({ + id: ModelProviderEnum.EdenAIResponses, + name: 'Eden AI (Responses)', + type: ModelProviderType.OpenAIResponses, + description: 'eden-ai-responses', + curatedModelIds: ['@edenai', 'openai/gpt-4o', 'anthropic/claude-sonnet-4-5'], + urls: { + website: 'https://www.edenai.co/', + apiKey: 'https://app.edenai.run/admin/api-settings/features-preferences', + docs: 'https://www.edenai.co/docs/api-reference/responses/create-response', + }, + defaultSettings: { + apiHost: 'https://api.edenai.run/v3', + apiPath: '/responses', + models: [ + { + modelId: '@edenai', + nickname: 'Eden AI (auto-router)', + capabilities: ['tool_use'], + contextWindow: 128_000, + }, + { + modelId: 'openai/gpt-4o', + nickname: 'GPT-4o', + capabilities: ['vision', 'tool_use'], + contextWindow: 128_000, + maxOutput: 16_384, + }, + { + modelId: 'anthropic/claude-sonnet-4-5', + nickname: 'Claude Sonnet 4.5', + capabilities: ['vision', 'tool_use', 'reasoning'], + contextWindow: 200_000, + maxOutput: 64_000, + }, + ], + }, + createModel: (config) => { + return new EdenAIResponses( + { + apiKey: config.providerSetting.apiKey || '', + apiHost: config.formattedApiHost || 'https://api.edenai.run/v3', + apiPath: config.formattedApiPath || '/responses', + model: config.model, + temperature: config.settings.temperature, + topP: config.settings.topP, + maxOutputTokens: config.settings.maxTokens, + stream: config.settings.stream, + useProxy: config.providerSetting.useProxy, + }, + config.dependencies + ) + }, + getDisplayName: (modelId, providerSettings) => { + return `Eden AI Responses (${providerSettings?.models?.find((m) => m.modelId === modelId)?.nickname || modelId})` + }, +}) diff --git a/src/shared/providers/definitions/eden-ai.ts b/src/shared/providers/definitions/eden-ai.ts new file mode 100644 index 0000000000..0a5239870f --- /dev/null +++ b/src/shared/providers/definitions/eden-ai.ts @@ -0,0 +1,89 @@ +import { ModelProviderEnum, ModelProviderType } from '../../types' +import { defineProvider } from '../registry' +import EdenAI from './models/eden-ai' + +// Eden AI exposes an OpenAI-compatible Chat Completions endpoint that routes to +// 100+ models from many providers. Model ids use the `provider/model` format, +// e.g. `openai/gpt-4o`. The special `@edenai` id auto-routes to the best model. +// Docs: https://www.edenai.co/docs/api-reference/chat/chat-completions +export const edenAIProvider = defineProvider({ + id: ModelProviderEnum.EdenAI, + name: 'Eden AI', + type: ModelProviderType.OpenAI, + description: 'eden-ai', + curatedModelIds: [ + '@edenai', + 'openai/gpt-4o', + 'openai/gpt-4o-mini', + 'anthropic/claude-sonnet-4-5', + 'google/gemini-2.5-pro', + 'mistral/mistral-large-latest', + ], + urls: { + website: 'https://www.edenai.co/', + apiKey: 'https://app.edenai.run/admin/api-settings/features-preferences', + docs: 'https://www.edenai.co/docs/api-reference/chat/chat-completions', + }, + defaultSettings: { + apiHost: 'https://api.edenai.run/v3', + models: [ + { + modelId: '@edenai', + nickname: 'Eden AI (auto-router)', + capabilities: ['tool_use'], + contextWindow: 128_000, + }, + { + modelId: 'openai/gpt-4o', + nickname: 'GPT-4o', + capabilities: ['vision', 'tool_use'], + contextWindow: 128_000, + maxOutput: 16_384, + }, + { + modelId: 'openai/gpt-4o-mini', + nickname: 'GPT-4o mini', + capabilities: ['vision', 'tool_use'], + contextWindow: 128_000, + maxOutput: 16_384, + }, + { + modelId: 'anthropic/claude-sonnet-4-5', + nickname: 'Claude Sonnet 4.5', + capabilities: ['vision', 'tool_use', 'reasoning'], + contextWindow: 200_000, + maxOutput: 64_000, + }, + { + modelId: 'google/gemini-2.5-pro', + nickname: 'Gemini 2.5 Pro', + capabilities: ['vision', 'tool_use', 'reasoning'], + contextWindow: 1_048_576, + maxOutput: 65_536, + }, + { + modelId: 'mistral/mistral-large-latest', + nickname: 'Mistral Large', + capabilities: ['tool_use'], + contextWindow: 128_000, + maxOutput: 8_192, + }, + ], + }, + createModel: (config) => { + return new EdenAI( + { + apiKey: config.providerSetting.apiKey || '', + model: config.model, + temperature: config.settings.temperature, + topP: config.settings.topP, + maxOutputTokens: config.settings.maxTokens, + stream: config.settings.stream, + }, + config.dependencies + ) + }, + getDisplayName: (modelId, providerSettings) => { + return `Eden AI (${providerSettings?.models?.find((m) => m.modelId === modelId)?.nickname || modelId})` + }, +}) diff --git a/src/shared/providers/definitions/models/eden-ai-responses.ts b/src/shared/providers/definitions/models/eden-ai-responses.ts new file mode 100644 index 0000000000..d11daa164d --- /dev/null +++ b/src/shared/providers/definitions/models/eden-ai-responses.ts @@ -0,0 +1,86 @@ +import { createOpenAI } from '@ai-sdk/openai' +import { extractReasoningMiddleware, wrapLanguageModel } from 'ai' +import AbstractAISDKModel from '../../../models/abstract-ai-sdk' +import { fetchRemoteModels } from '../../../models/openai-compatible' +import type { CallChatCompletionOptions } from '../../../models/types' +import { createFetchWithProxy } from '../../../models/utils/fetch-proxy' +import type { ProviderModelInfo } from '../../../types' +import type { ModelDependencies } from '../../../types/adapters' + +interface Options { + apiKey: string + apiHost: string + apiPath: string + model: ProviderModelInfo + temperature?: number + topP?: number + maxOutputTokens?: number + stream?: boolean + useProxy?: boolean +} + +type FetchFunction = typeof globalThis.fetch + +// Eden AI's Responses endpoint is OpenAI-compatible (stateful alternative to chat +// completions). Unlike OpenAI's `https://api.openai.com` host, Eden AI keeps the +// version segment in the path (`https://api.edenai.run/v3/responses`), so we route +// requests explicitly to `${apiHost}${apiPath}` instead of using the shared +// `normalizeOpenAIResponsesHostAndPath` helper (which would append `/v1`). +// Docs: https://www.edenai.co/docs/api-reference/responses/create-response +export default class EdenAIResponses extends AbstractAISDKModel { + public name = 'Eden AI Responses' + + constructor( + public options: Options, + dependencies: ModelDependencies + ) { + super(options, dependencies) + } + + protected getCallSettings() { + return { + temperature: this.options.temperature, + topP: this.options.topP, + maxOutputTokens: this.options.maxOutputTokens, + stream: this.options.stream, + } + } + + static isSupportTextEmbedding() { + return true + } + + protected getProvider(_options: CallChatCompletionOptions, fetchFunction?: FetchFunction) { + return createOpenAI({ + apiKey: this.options.apiKey, + baseURL: this.options.apiHost, + fetch: fetchFunction, + }) + } + + protected getChatModel(options: CallChatCompletionOptions) { + const { apiHost, apiPath } = this.options + const provider = this.getProvider(options, (_input, init) => + createFetchWithProxy(this.options.useProxy, this.dependencies)(`${apiHost}${apiPath}`, init) + ) + return wrapLanguageModel({ + model: provider.responses(this.options.model.modelId), + middleware: extractReasoningMiddleware({ tagName: 'think' }), + }) + } + + public listModels() { + return fetchRemoteModels( + { + apiHost: this.options.apiHost, + apiKey: this.options.apiKey, + useProxy: this.options.useProxy, + }, + this.dependencies + ) + } + + protected getImageModel() { + return null + } +} diff --git a/src/shared/providers/definitions/models/eden-ai.ts b/src/shared/providers/definitions/models/eden-ai.ts new file mode 100644 index 0000000000..d3588fc030 --- /dev/null +++ b/src/shared/providers/definitions/models/eden-ai.ts @@ -0,0 +1,28 @@ +import OpenAICompatible, { type OpenAICompatibleSettings } from '../../../models/openai-compatible' +import type { ModelDependencies } from '../../../types/adapters' + +interface Options extends OpenAICompatibleSettings {} + +export default class EdenAI extends OpenAICompatible { + public name = 'Eden AI' + public options: Options + constructor(options: Omit, dependencies: ModelDependencies) { + const apiHost = 'https://api.edenai.run/v3' + super( + { + apiKey: options.apiKey, + apiHost, + model: options.model, + temperature: options.temperature, + topP: options.topP, + maxOutputTokens: options.maxOutputTokens, + stream: options.stream, + }, + dependencies + ) + this.options = { + ...options, + apiHost, + } + } +} diff --git a/src/shared/providers/index.ts b/src/shared/providers/index.ts index ebfa92a3ea..f6ad2de9c6 100644 --- a/src/shared/providers/index.ts +++ b/src/shared/providers/index.ts @@ -17,6 +17,8 @@ import './definitions/minimax' import './definitions/moonshot' import './definitions/siliconflow' import './definitions/openrouter' +import './definitions/eden-ai' +import './definitions/eden-ai-responses' import './definitions/ollama' import './definitions/lmstudio' import './definitions/azure' diff --git a/src/shared/types/provider.ts b/src/shared/types/provider.ts index 85eb01d760..367dc28a34 100644 --- a/src/shared/types/provider.ts +++ b/src/shared/types/provider.ts @@ -25,6 +25,8 @@ export enum ModelProviderEnum { Perplexity = 'perplexity', XAI = 'xAI', OpenRouter = 'openrouter', + EdenAI = 'eden-ai', + EdenAIResponses = 'eden-ai-responses', Bedrock = 'bedrock', Custom = 'custom', }