diff --git a/CHANGELOG.md b/CHANGELOG.md index 3715134422..c70a55ffec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 🔧 Bug Fixes +- **providers (friendliai, novita):** fix two provider registry endpoints that rejected valid keys (verified live with real keys). **FriendliAI** pointed at `…/dedicated/v1/chat/completions`, which `403 Forbidden`s a serverless `flp_*` token — switched to `…/serverless/v1/chat/completions` (+ a serverless `modelsUrl`). **Novita** pointed at the legacy `…/v3/…` base with a typo’d model id `ai-ai/llama-3.1-8b-instruct` (both `404`) — switched to the OpenAI-compatible `…/openai/v1/…` base + the valid `meta-llama/llama-3.1-8b-instruct` id. Regression guard: `tests/unit/provider-endpoints-friendliai-novita.test.ts`. ([#5430](https://github.com/diegosouzapw/OmniRoute/issues/5430), [#5455](https://github.com/diegosouzapw/OmniRoute/issues/5455)) - **providers (muse-spark):** align the Muse Spark Web (Meta AI) cookie copy with the live cookie name. The default session cookie migrated from the retired `abra_sess` to `ecto_1_sess` (`META_AI_DEFAULT_COOKIE`), but the provider form hint and one 401 auth-failure message still told users to paste `abra_sess` — a cookie that no longer exists. Both strings now name `ecto_1_sess`. Regression guard: `tests/unit/muse-spark-cookie-copy-5449.test.ts`. ([#5449](https://github.com/diegosouzapw/OmniRoute/issues/5449)) - **dashboard (provider add):** fix three rough edges in the Add-API-Key / model-import flow reported across the provider-catalog audit. (1) The **Validation Model** and **Account ID** form fields shipped untranslated i18n stub copy (`"Validation Model Id Label"`, `"Account Id Placeholder"`, …) that surfaced verbatim in the modal — replaced with real labels/placeholders/hints in `en.json`. (2) Model import **silently fell back to the cached/local catalog**: the route already returned a `warning` ("API unavailable — using local catalog"), but `useModelImportHandlers` only read `models`/`error` and dropped it, so the user got local models with no indication — the warning is now surfaced as an import log line (new pure helper `extractImportWarning`). (3) The required connection-**name** field defaulted to `""`, which let browser autofill inject garbage (e.g. `wiw`) — it now defaults to `"main"`. Regression guard: `tests/unit/provider-add-ux-i18n-import-warning.test.ts`. ([#5421](https://github.com/diegosouzapw/OmniRoute/issues/5421), [#5428](https://github.com/diegosouzapw/OmniRoute/issues/5428), [#5429](https://github.com/diegosouzapw/OmniRoute/issues/5429), [#5431](https://github.com/diegosouzapw/OmniRoute/issues/5431), [#5435](https://github.com/diegosouzapw/OmniRoute/issues/5435)) - **services (installer):** fix `spawn EINVAL` when installing an embedded service (9Router / CLIProxy) on **Windows + Node.js 24+**. Node 24 stopped letting `child_process.execFile()` run `.cmd` batch files without a shell (nodejs/node#52554), and npm on Windows is `npm.cmd`, so `runNpm()` threw `EINVAL` the moment a user clicked **Install**. `runNpm` now enables `shell` on win32 only. To keep Hard Rule #13 intact under a shell — where the shell, not `execFile`, parses argv — the install `--prefix` (a `DATA_DIR` path that can legitimately contain spaces, e.g. `C:\Users\John Doe\.omniroute\…`) is now passed via the `npm_config_prefix` **environment variable** instead of an argv path, and the user-supplied install `version` is constrained to a dist-tag/semver shape (`SERVICE_VERSION_PATTERN`) at the route boundary so it can never carry shell metacharacters. With the prefix in the environment and the version validated, every remaining argv entry is a static flag. Regression guards: `tests/unit/services/installers/runNpm-shell-5379.test.ts` (+ existing `ninerouter.test.ts` aligned to npm's `npm_config_prefix` env). ([#5379](https://github.com/diegosouzapw/OmniRoute/issues/5379)) diff --git a/open-sse/config/providers/registry/friendliai/index.ts b/open-sse/config/providers/registry/friendliai/index.ts index 95e0608bdb..d4dd98179f 100644 --- a/open-sse/config/providers/registry/friendliai/index.ts +++ b/open-sse/config/providers/registry/friendliai/index.ts @@ -6,7 +6,10 @@ export const friendliaiProvider: RegistryEntry = { alias: "friendli", format: "openai", executor: "default", - baseUrl: "https://api.friendli.ai/dedicated/v1/chat/completions", + // #5430: serverless endpoint — a `flp_*` serverless token gets 403 Forbidden on the + // /dedicated path (verified live). Serverless accepts it and serves the public models. + baseUrl: "https://api.friendli.ai/serverless/v1/chat/completions", + modelsUrl: "https://api.friendli.ai/serverless/v1/models", authType: "apikey", authHeader: "bearer", models: CHAT_OPENAI_COMPAT_MODELS.friendliai, diff --git a/open-sse/config/providers/registry/novita/index.ts b/open-sse/config/providers/registry/novita/index.ts index 683ffd600a..cd57d24523 100644 --- a/open-sse/config/providers/registry/novita/index.ts +++ b/open-sse/config/providers/registry/novita/index.ts @@ -5,9 +5,11 @@ export const novitaProvider: RegistryEntry = { alias: "novita", format: "openai", executor: "default", - baseUrl: "https://api.novita.ai/v3/chat/completions", - modelsUrl: "https://api.novita.ai/v3/models", + // #5455: OpenAI-compatible endpoint. The legacy /v3 base + the `ai-ai/…` model id both + // 404 (verified live); /openai/v1 + the `meta-llama/…` id return a clean completion. + baseUrl: "https://api.novita.ai/openai/v1/chat/completions", + modelsUrl: "https://api.novita.ai/openai/v1/models", authType: "apikey", authHeader: "bearer", - models: [{ id: "ai-ai/llama-3.1-8b-instruct", name: "Llama 3.1 8B" }], + models: [{ id: "meta-llama/llama-3.1-8b-instruct", name: "Llama 3.1 8B Instruct" }], }; diff --git a/tests/unit/provider-endpoints-friendliai-novita.test.ts b/tests/unit/provider-endpoints-friendliai-novita.test.ts new file mode 100644 index 0000000000..4c1eca97a8 --- /dev/null +++ b/tests/unit/provider-endpoints-friendliai-novita.test.ts @@ -0,0 +1,38 @@ +import test from "node:test"; +import assert from "node:assert/strict"; + +import { friendliaiProvider } from "../../open-sse/config/providers/registry/friendliai/index.ts"; +import { novitaProvider } from "../../open-sse/config/providers/registry/novita/index.ts"; + +// These guards lock in two registry endpoint fixes validated live with real provider keys +// (Hard Rule #18 — real-environment test recorded in the PR): +// - FriendliAI: a serverless `flp_*` token gets 403 Forbidden on the /dedicated path; the +// /serverless path serves it. (#5430) +// - Novita: the legacy /v3 base + the typo'd `ai-ai/…` model id both 404; /openai/v1 + the +// `meta-llama/…` id return a clean OpenAI completion. (#5455) + +test("#5430 FriendliAI targets the serverless OpenAI-compatible endpoint, not dedicated", () => { + assert.ok( + friendliaiProvider.baseUrl.includes("/serverless/v1/"), + `baseUrl must use the serverless path, got: ${friendliaiProvider.baseUrl}` + ); + assert.ok( + !friendliaiProvider.baseUrl.includes("/dedicated/"), + "baseUrl must not use the dedicated path (403s serverless keys)" + ); + assert.equal(friendliaiProvider.modelsUrl, "https://api.friendli.ai/serverless/v1/models"); +}); + +test("#5455 Novita targets the /openai/v1 endpoint with a valid model id", () => { + assert.equal(novitaProvider.baseUrl, "https://api.novita.ai/openai/v1/chat/completions"); + assert.equal(novitaProvider.modelsUrl, "https://api.novita.ai/openai/v1/models"); + // The `ai-ai/` org does not exist — Novita uses `meta-llama/…`. + assert.ok( + novitaProvider.models.every((m) => !m.id.startsWith("ai-ai/")), + "Novita model ids must not use the non-existent ai-ai/ org" + ); + assert.ok( + novitaProvider.models.some((m) => m.id === "meta-llama/llama-3.1-8b-instruct"), + "Novita must list the valid meta-llama/llama-3.1-8b-instruct id" + ); +});