Add Self-hosted STT/TTS/Embedding providers that read baseUrl per connection instead of a fixed registry endpoint, so 9Router can point at whisper.cpp, faster-whisper, Kokoro-FastAPI, llama-server, vLLM, Infinity, and similar OpenAI-compatible local servers. Self-hosted Embedding refuses to run without a baseUrl rather than falling back to api.openai.com like openaiCompatNode does, since that fallback would silently send input text and the API key to OpenAI under a provider named "Self-hosted". Also fixes embeddingsCore to catch adapter build errors as a 400 instead of letting them escape uncaught, and bounds the upstream fetch with FETCH_CONNECT_TIMEOUT_MS to avoid hanging forever on a dead endpoint. Self-hosted TTS treats a bare model value as the model rather than the voice, since the generic OpenAI TTS convention (bare = voice) is backwards for a provider where the model is the variable part.
47 lines
2.1 KiB
JavaScript
47 lines
2.1 KiB
JavaScript
// Self-hosted embeddings — like openaiCompatNode, but the baseUrl is REQUIRED.
|
|
//
|
|
// openaiCompatNode falls back to https://api.openai.com/v1 when a connection
|
|
// carries no providerSpecificData.baseUrl. For a custom NODE that default is
|
|
// defensible: the node was created by pointing at some OpenAI-compatible URL, and
|
|
// OpenAI is the archetype. For a provider whose entire purpose is "my own
|
|
// server", it is actively harmful — a connection saved without a baseUrl sends
|
|
// the INPUT TEXT and the API KEY to OpenAI, silently, under a provider named
|
|
// "Self-hosted Embedding".
|
|
//
|
|
// Observed exactly that with a placeholder connection (2026-08-04):
|
|
//
|
|
// [selfhosted-embedding/embedding] [401]: Incorrect API key provided: abc.
|
|
// You can find your API key at https://platform.openai.com/account/api-keys.
|
|
//
|
|
// The key "abc" was typed as a throwaway for a LOCAL server and left the network.
|
|
// A self-hosted provider must never have a cloud fallback, so this one refuses
|
|
// instead: no baseUrl means a configuration error, reported as such.
|
|
import createOpenAIEmbeddingAdapter from "./openai.js";
|
|
|
|
const baseAdapter = createOpenAIEmbeddingAdapter("openai");
|
|
|
|
export class MissingBaseUrlError extends Error {
|
|
constructor() {
|
|
super(
|
|
"Self-hosted Embedding needs an endpoint: set this connection's baseUrl to " +
|
|
"the OpenAI base URL of your server, e.g. http://host:8080/v1 (note the /v1 — " +
|
|
"\"/embeddings\" is appended to it). Refusing to fall back to api.openai.com, " +
|
|
"which would send your input and API key to OpenAI."
|
|
);
|
|
this.name = "MissingBaseUrlError";
|
|
this.isConfigError = true;
|
|
}
|
|
}
|
|
|
|
export default {
|
|
...baseAdapter,
|
|
buildUrl: (_model, creds) => {
|
|
const rawBaseUrl = creds?.providerSpecificData?.baseUrl;
|
|
if (!rawBaseUrl || !String(rawBaseUrl).trim()) throw new MissingBaseUrlError();
|
|
// Accept either the OpenAI base or a full embeddings URL, so a value pasted
|
|
// from a curl example works as well as one typed from the help text.
|
|
const baseUrl = String(rawBaseUrl).trim().replace(/\/$/, "").replace(/\/embeddings$/, "");
|
|
return `${baseUrl}/embeddings`;
|
|
},
|
|
};
|