Files
OmniRoute/open-sse/config/embeddingRegistry.ts
Diego Rodrigues de Sa e Souza 1442c47bbb chore(release): v3.5.6 — email masking, model toggle, OpenRouter registries & bug fixes (#1080)
* fix(minimax): switch auth from x-api-key to Authorization Bearer (#1076)

Integrated into release/v3.5.6 — MiniMax auth fix with authHeader consistency normalization

* feat(CI,i18n): autogenerate language files + Add missing strings (#1071)

Integrated into release/v3.5.6 — i18n translations for memory, skills, and missing keys across 31 languages

* fix(ci): restore i18n continue-on-error, remove auto-commit race condition

* fix(husky): load nvm in hooks for VS Code compatibility

* fix(husky): gracefully skip hooks when npm is not in PATH

* fix: convert OpenAI function tool_choice to Claude tool format (#1072)

* fix: prevent EPIPE feedback loop filling logs at GB/s (#1006)

* fix: fallback to native fetch when undici dispatcher fails (#1054)

* fix: improve Qoder PAT validation with actionable error messages (#966)

- Add QODER_PERSONAL_ACCESS_TOKEN env var fallback for both validation and execution
- Pre-flight ping check to diagnose connectivity issues (Docker/proxy)
- Detect encrypted auth blobs from ~/.qoder/.auth/user and guide to website PAT
- Clear error messages for auth failures with link to integrations page
- Treat non-auth 4xx as auth-pass (request format issue, not token issue)
- Update tests to cover new validation paths (23 tests, all passing)

* feat: Improve the Chinese translation (#1079)

Integrated into release/v3.5.6

* chore(release): v3.5.6 — i18n updates and credential security fixes

* fix(ci): resolve e2e and docs-sync pipeline failures

* fix(security): bump next to 16.2.3 to resolve SNYK-JS-NEXT-15954202

* fix: guard Memory/Cache UI against null toLocaleString crash (#1083)

* fix: translate OpenAI tool_choice type 'function' to Claude 'tool' format (#1072)

* fix: pass custom baseUrl in provider API key validation (#1078)

* docs: update CHANGELOG with v3.5.6 bug fixes and security patches

* docs: rewrite implement-features workflow with 5-phase harvest-research-report-plan-execute pipeline

* docs: organize _ideia/ into viable/defer/notfit + add Phase 2.5 auto-response workflow

* docs: implementation plans for #1025, #750, #960, #1046 + close already-implemented #833, #973, #982

* feat: mask email addresses in dashboard for privacy (#1025)

* feat: add OpenRouter and GitHub to embedding/image provider registries (#960)

* feat: add model visibility toggle and search filter to provider page (#750)

* docs: move implemented features to notfit, update task plans status

* chore: untrack _ideia/ and _tasks/ from git — private/internal only

* chore(release): bump to v3.5.6 — changelog, docs, version sync & any-budget fix

* fix: remove explicit .ts extension in qoderCli import that caused 500 error in production build

---------

Co-authored-by: Jean Brito <jeanfbrito@gmail.com>
Co-authored-by: zenobit <zenobit@disroot.org>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Ethan Hunt <136065060+only4copilot@users.noreply.github.com>
2026-04-09 15:55:59 -03:00

197 lines
6.0 KiB
TypeScript

/**
* Embedding Provider Registry
*
* Defines providers that support the /v1/embeddings endpoint.
* All providers use the OpenAI-compatible format.
*
* API keys are stored in the same provider credentials system,
* keyed by provider ID (e.g. "nebius", "openai").
*/
export interface EmbeddingProvider {
id: string;
baseUrl: string;
authType: string;
authHeader: string;
models: { id: string; name: string; dimensions?: number }[];
}
export interface EmbeddingProviderNodeRow {
id?: string;
prefix: string;
name: string;
baseUrl: string;
apiType?: string;
}
/**
* Build a dynamic EmbeddingProvider from a local provider_node.
* Only used for local providers (localhost) — caller must filter by hostname.
*/
export function buildDynamicEmbeddingProvider(node: EmbeddingProviderNodeRow): EmbeddingProvider {
if (!node.prefix || !node.baseUrl) {
throw new Error(`Invalid provider_node: missing prefix or baseUrl`);
}
if (node.prefix.includes("/") || node.prefix.includes(" ")) {
throw new Error(`Invalid provider_node prefix "${node.prefix}": must not contain / or spaces`);
}
const baseUrl = node.baseUrl.replace(/\/+$/, "");
return {
id: node.prefix,
baseUrl: `${baseUrl}/embeddings`,
authType: "none",
authHeader: "none",
models: [],
};
}
export const EMBEDDING_PROVIDERS: Record<string, EmbeddingProvider> = {
nebius: {
id: "nebius",
baseUrl: "https://api.tokenfactory.nebius.com/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [{ id: "Qwen/Qwen3-Embedding-8B", name: "Qwen3 Embedding 8B", dimensions: 4096 }],
},
openai: {
id: "openai",
baseUrl: "https://api.openai.com/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "text-embedding-3-small", name: "Text Embedding 3 Small", dimensions: 1536 },
{ id: "text-embedding-3-large", name: "Text Embedding 3 Large", dimensions: 3072 },
{ id: "text-embedding-ada-002", name: "Text Embedding Ada 002", dimensions: 1536 },
],
},
mistral: {
id: "mistral",
baseUrl: "https://api.mistral.ai/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [{ id: "mistral-embed", name: "Mistral Embed", dimensions: 1024 }],
},
together: {
id: "together",
baseUrl: "https://api.together.xyz/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "BAAI/bge-large-en-v1.5", name: "BGE Large EN v1.5", dimensions: 1024 },
{ id: "togethercomputer/m2-bert-80M-8k-retrieval", name: "M2 BERT 80M 8K", dimensions: 768 },
],
},
fireworks: {
id: "fireworks",
baseUrl: "https://api.fireworks.ai/inference/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "nomic-ai/nomic-embed-text-v1.5", name: "Nomic Embed Text v1.5", dimensions: 768 },
],
},
nvidia: {
id: "nvidia",
baseUrl: "https://integrate.api.nvidia.com/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [{ id: "nvidia/nv-embedqa-e5-v5", name: "NV EmbedQA E5 v5", dimensions: 1024 }],
},
openrouter: {
id: "openrouter",
baseUrl: "https://openrouter.ai/api/v1/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "openai/text-embedding-3-small", name: "Text Embedding 3 Small (OpenRouter)", dimensions: 1536 },
{ id: "openai/text-embedding-3-large", name: "Text Embedding 3 Large (OpenRouter)", dimensions: 3072 },
{ id: "openai/text-embedding-ada-002", name: "Text Embedding Ada 002 (OpenRouter)", dimensions: 1536 },
],
},
github: {
id: "github",
baseUrl: "https://models.inference.ai.azure.com/embeddings",
authType: "apikey",
authHeader: "bearer",
models: [
{ id: "text-embedding-3-small", name: "Text Embedding 3 Small (GitHub)", dimensions: 1536 },
{ id: "text-embedding-3-large", name: "Text Embedding 3 Large (GitHub)", dimensions: 3072 },
],
},
};
/**
* Get embedding provider config by ID
*/
export function getEmbeddingProvider(providerId: string): EmbeddingProvider | null {
return EMBEDDING_PROVIDERS[providerId] || null;
}
/**
* Parse embedding model string (format: "provider/model" or just "model")
* Returns { provider, model }
*/
export function parseEmbeddingModel(
modelStr: string | null,
dynamicProviders?: EmbeddingProvider[]
): { provider: string | null; model: string | null } {
if (!modelStr) return { provider: null, model: null };
// Check for "provider/model" format
const slashIdx = modelStr.indexOf("/");
if (slashIdx > 0) {
// Phase 1: Try each hardcoded provider prefix
for (const [providerId] of Object.entries(EMBEDDING_PROVIDERS)) {
if (modelStr.startsWith(providerId + "/")) {
return { provider: providerId, model: modelStr.slice(providerId.length + 1) };
}
}
// Phase 2: Try dynamic provider_nodes prefix
if (dynamicProviders) {
for (const dp of dynamicProviders) {
if (modelStr.startsWith(dp.id + "/")) {
return { provider: dp.id, model: modelStr.slice(dp.id.length + 1) };
}
}
}
// Phase 3: Fallback — first segment is provider
const provider = modelStr.slice(0, slashIdx);
const model = modelStr.slice(slashIdx + 1);
return { provider, model };
}
// No provider prefix — search hardcoded providers for the model
for (const [providerId, config] of Object.entries(EMBEDDING_PROVIDERS)) {
if (config.models.some((m) => m.id === modelStr)) {
return { provider: providerId, model: modelStr };
}
}
return { provider: null, model: modelStr };
}
/**
* Get all embedding models as a flat list
*/
export function getAllEmbeddingModels() {
const models = [];
for (const [providerId, config] of Object.entries(EMBEDDING_PROVIDERS)) {
for (const model of config.models) {
models.push({
id: `${providerId}/${model.id}`,
name: model.name,
provider: providerId,
dimensions: model.dimensions,
});
}
}
return models;
}