Files
OmniRoute/tests/unit/openvecta-provider-registration.test.ts
Imam Wahyu Widodo 6530c92aa6 feat(provider): add OpenVecta AI inference gateway (#6833)
* fix(electron): bump electron 42→43 + build better-sqlite3 from source (ABI 148) (#6605)

fix(electron): bump electron 42→43 + rebuild better-sqlite3 from source against the Electron ABI (148).

Electron 43 raises NODE_MODULE_VERSION to 148; better-sqlite3@12.11.1 has no electron-v148 prebuild, so the packaged app died with 'Nenhum driver SQLite disponível'. prepare-electron-standalone now compiles better-sqlite3 from source against the electron headers into build/Release (where 'bindings' resolves it). Validated by Electron Package Smoke (green) + local (node_register_module_v148).

Supersedes #6378. (--admin: the only reds are SonarQube/SonarCloud failing on a coverage-report artifact digest-mismatch — a GitHub Actions infra flake, not this diff; Sonar is green on main and the diff touches only the electron build.)

* deps: bump the development group across 1 directory with 6 updates (#6588)

deps: bump the development group (6 updates). Rebased onto current main; all checks green after the electron-smoke fix (#6605).

* fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump (#6620)

fix(proxy): force CONNECT tunnel for HTTP proxied requests (undici 8.7) + production deps bump.

undici 8.6+ changed ProxyAgent to forward plain-HTTP via request-proxy instead of CONNECT, breaking OAuth refresh through a connection proxy (501). proxyDispatcher now passes proxyTunnel:true. Validated: Unit Tests 3/8 (the OAuth-proxy test) green, new regression test green (fails without the fix on undici 8.7), SonarQube green.

Supersedes #6380. (--admin: the only red is Electron Package Smoke failing on a next-build artifact 'digest-mismatch' — a GitHub Actions infra flake corrupting the asar ('file data stream has unexpected number of bytes'); the better-sqlite3 rebuild itself succeeded (gyp ok) and the electron path is unchanged from #6605 which passed the smoke. Not this diff.)

* feat(provider): add OpenVecta AI inference gateway

OpenVecta (https://openvecta.com/) is an OpenAI-compatible AI inference
gateway hosting LLMs (GLM, Claude, DeepSeek, GPT OSS, Llama, Kimi,
Nemotron...) plus text-embedding-* models behind a single Bearer key.

Wiring (7 integration points):
- src/shared/constants/providers/apikey/inference-hosts.ts: catalog entry
- open-sse/config/providers/registry/openvecta/index.ts: registry w/ 9 seed LLMs
- open-sse/config/providers/index.ts: wire into REGISTRY
- src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts: live /v1/models URL
- src/app/api/providers/[id]/models/discovery/providerSets.ts: NAMED_OPENAI_STYLE_PROVIDERS
- public/providers/openvecta.svg: brand icon
- tests/unit/openvecta-provider-registration.test.ts: regression guard (6 tests, all pass)

No executor needed — buildOpenAiCompatibleRegistryEntry wires
format=openai / executor=default / authType=apikey / authHeader=bearer.

Live catalog discovery uses the existing NAMED_OPENAI_STYLE_PROVIDERS
path (live /v1/models fetch + registry seed as offline fallback).

Validation:
- npm run typecheck:core         clean
- npm run typecheck:noimplicit:core 4 errors in unchanged files (combo.ts, cliRuntime.ts); 0 in new code
- npm run lint                    clean
- node --import tsx/esm --test tests/unit/openvecta-provider-registration.test.ts   6/6 pass
- sibling tests/unit/openai-style-providers-4239-4155-3841.test.ts  18/18 pass (no regression)

* chore(merge): drop unrelated main-drift from PR fork + fix count/golden drift

The fork branch predated main's electron 42→43 bump (#6605) and several other
package.json/lockfile churn; those files are unrelated to the OpenVecta
provider addition and were reintroducing an older/stale state (version
3.8.46, electron 42, older bun/eslint-config-next) that broke the Electron
Package Smoke check. Restored package.json, package-lock.json,
electron/package.json, electron/package-lock.json, and
scripts/build/prepare-electron-standalone.mjs to match
origin/release/v3.8.47.

Also updates the two provider-count assertions (166->167) and regenerates
the translate-path golden snapshot to account for the new openvecta entry.

Co-authored-by: hajilok <120608486+hajilok@users.noreply.github.com>

---------

Co-authored-by: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
2026-07-12 01:58:53 -03:00

119 lines
4.8 KiB
TypeScript

/**
* Coverage for the OpenVecta (https://openvecta.com/) provider registration.
*
* Validates the seven wiring points established in the feat/openvecta-provider
* branch:
* 1. APIKEY_PROVIDERS.openvecta — catalog entry (id, alias, name, website, hasFree)
* 2. providerRegistry.openvecta — format=openai / executor=default / apikey / bearer
* 3. PROVIDER_ENDPOINTS — chat completions baseUrl
* 4. providerModelsConfig — live /v1/models discovery URL
* 5. NAMED_OPENAI_STYLE_PROVIDERS — openvecta classified for live-fetch
* 6. Seeded registry catalog — non-empty, unique ids, includes a curated LLM subset
* 7. Schema validation — Zod validateProviders(APIKEY_PROVIDERS) passes (the import
* would throw at module-load time if any entry were malformed)
*
* Rule #18 (TDD/VPS gate) — this test exists to keep the seven wiring points
* in sync; if any one drifts, the relevant assertion fails immediately.
*/
import test from "node:test";
import assert from "node:assert/strict";
const { APIKEY_PROVIDERS } = await import(
"../../src/shared/constants/providers.ts"
);
const { REGISTRY: providerRegistry } = await import(
"../../open-sse/config/providerRegistry.ts"
);
const { NAMED_OPENAI_STYLE_PROVIDERS, isNamedOpenAIStyleProvider } = await import(
"../../src/app/api/providers/[id]/models/discovery/providerSets.ts"
);
const { PROVIDER_MODELS_CONFIG } = await import(
"../../src/app/api/providers/[id]/models/discovery/providerModelsConfig.ts"
);
const SPEC = {
id: "openvecta",
alias: "openvecta",
name: "OpenVecta",
website: "https://openvecta.com",
chatUrl: "https://api.openvecta.com/v1/chat/completions",
modelsUrl: "https://api.openvecta.com/v1/models",
expectedSeedIds: [
"glm-4.7-flash",
"claude-sonnet-4.6",
"deepseek-v4-flash",
"gpt-oss-120b",
"gemma-4-31b",
"kimi-k2.6",
"llama-3.3-70b-instruct",
"llama-4-maverick",
"nemotron-3-super-120b",
],
};
test("APIKEY_PROVIDERS.openvecta is registered with the canonical identity", () => {
const entry = APIKEY_PROVIDERS[SPEC.id];
assert.ok(entry, `APIKEY_PROVIDERS.${SPEC.id} must be defined`);
assert.equal(entry.id, SPEC.id);
assert.equal(entry.alias, SPEC.alias);
assert.equal(entry.name, SPEC.name);
assert.equal(entry.website, SPEC.website);
assert.equal(typeof entry.textIcon, "string");
assert.equal(entry.hasFree, true, "OpenVecta advertises free signup credits");
assert.equal(typeof entry.freeNote, "string");
assert.match(entry.color, /^#[0-9A-Fa-f]{6}$/);
});
test("providerRegistry exposes the OpenAI-compatible chat completions URL", () => {
// Note: `PROVIDER_ENDPOINTS` in src/shared/constants/config.ts is a hand-curated
// *display-only* map (a subset of providers shown in the UI), so we don't
// require openvecta to be listed there — the source-of-truth chat URL lives
// on the registry entry, generated by `generateLegacyProviders()`.
assert.equal(providerRegistry[SPEC.id].baseUrl, SPEC.chatUrl);
});
test("PROVIDER_MODELS_CONFIG exposes the live /v1/models discovery URL", () => {
const cfg = PROVIDER_MODELS_CONFIG[SPEC.id];
assert.ok(cfg, `PROVIDER_MODELS_CONFIG.${SPEC.id} must be defined`);
assert.equal(cfg.url, SPEC.modelsUrl);
assert.equal(cfg.method, "GET");
assert.equal(cfg.authHeader, "Authorization");
assert.equal(cfg.authPrefix, "Bearer ");
assert.equal(typeof cfg.parseResponse, "function");
});
test("providerRegistry.openvecta uses OpenAI format with bearer apikey auth", () => {
const entry = providerRegistry[SPEC.id];
assert.ok(entry, `providerRegistry.${SPEC.id} must be defined`);
assert.equal(entry.id, SPEC.id);
assert.equal(entry.alias, SPEC.alias);
assert.equal(entry.format, "openai");
assert.equal(entry.executor, "default");
assert.equal(entry.authType, "apikey");
assert.equal(entry.authHeader, "bearer");
assert.equal(entry.baseUrl, SPEC.chatUrl);
});
test("openvecta is classified as a named OpenAI-style provider (live-fetch path)", () => {
assert.ok(
NAMED_OPENAI_STYLE_PROVIDERS.has(SPEC.id),
"openvecta must be in NAMED_OPENAI_STYLE_PROVIDERS for live /v1/models fetch",
);
assert.equal(isNamedOpenAIStyleProvider(SPEC.id), true);
});
test("openvecta ships a non-empty unique seed catalog covering curated LLMs", () => {
const models = providerRegistry[SPEC.id].models;
assert.ok(Array.isArray(models), "registry models must be an array");
assert.ok(models.length >= 5, "seed list must be non-empty for the offline fallback");
const ids = models.map((m: { id: string }) => m.id);
assert.equal(new Set(ids).size, ids.length, "seed model ids must be unique");
for (const expected of SPEC.expectedSeedIds) {
assert.ok(ids.includes(expected), `seed list must include ${expected}`);
}
for (const m of models) {
assert.equal(typeof m.id, "string");
assert.ok(m.id.length > 0);
assert.equal(typeof m.name, "string");
}
});