mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-17 12:42:21 +03:00
* feat(providers): add Segmind image+video provider (#6656) Segmind exposes 200+ hosted image/video models under a single `POST https://api.segmind.com/v1/{model}` REST shape: x-api-key auth, JSON request body, raw media bytes response (no JSON envelope). - New IMAGE_PROVIDERS + VIDEO_PROVIDERS registry entries (format: "segmind") with a curated starter model list (Flux, SDXL, SD3.5, Kandinsky for image; Wan, Hunyuan, LTX, Kling for video). - New connection-metadata entry in specialty-media.ts; segmind added to IMAGE_ONLY_PROVIDER_IDS and VIDEO_PROVIDER_IDS. - Dedicated handlers (imageGeneration/providers/segmind.ts, videoGeneration/providers/segmind.ts) built on a shared REST client (utils/segmindClient.ts) that centralizes the fetch/error/log path so both stay under the complexity/max-lines ratchets. - Extracted the pre-existing Alibaba DashScope video handler out of the frozen videoGeneration.ts into videoGeneration/providers/ dashscope.ts (no behavior change) to make room for the new Segmind dispatch branch under the frozen file-size baseline. - Error responses route through sanitizeErrorMessage() (Hard Rule #12) — verified by dedicated no-leak tests. - Regenerated docs/reference/PROVIDER_REFERENCE.md (250 -> 251 providers) and synced the plain-text provider counts in README.md/ AGENTS.md/CLAUDE.md (anchors/badges left untouched). Tests: tests/unit/segmind-image-video-provider-6656.test.ts (11 cases — registry shape, connection metadata, IMAGE_ONLY/VIDEO_ PROVIDER_IDS membership, mocked-fetch request mapping for both image and video, and sanitized-error-path assertions for both upstream error bodies and network exceptions). No live Segmind key required; response shape (raw media bytes, x-api-key auth) is sourced from https://docs.segmind.com/ and corroborated against https://www.segmind.com/models/flux-schnell/api, https://www.segmind.com/models/sdxl1.0-txt2img/api, and https://www.segmind.com/models/wan2.1-t2v/api. Gates run clean: check-file-size, check:complexity-ratchets (2055/889, both under baseline), typecheck:core, typecheck:noimplicit:core (no new errors), lint (targeted files), check:cycles, check:docs-counts (STRICT provider-count drift resolved), check:docs-sync, check:any-budget:t11, check:tracked-artifacts, check:provider-consistency, check:known-symbols. * test(providers): align APIKEY_PROVIDERS count 167→168 for the new segmind provider (#6656) Adding segmind to specialty-media.ts grows APIKEY_PROVIDERS by one; providers-constants-split.test.ts hardcodes the family-partition total. Legitimate count alignment, not a weakened assertion — all 4 partition/ dedup checks still enforced.
94 lines
4.1 KiB
TypeScript
94 lines
4.1 KiB
TypeScript
// Characterization of the providers.ts catalog split (god-file decomposition): the host became a
|
|
// barrel that re-exports 10 data catalogs now living under constants/providers/*, and APIKEY is
|
|
// merged from 6 semantic family files (apikey/<family>.ts). Locks: the public surface (every catalog
|
|
// + helpers still exported), the spread-merge integrity (168 APIKEY entries, no loss/dup), and that
|
|
// load-time Zod validation still runs. Pure-data move → behavior must be identical.
|
|
// Count was 171 before obsolete provider removals (PR #6675: glhf/kluster/cablyai/inclusionai etc.,
|
|
// 171->167) plus #6126 (ClinePass dual-auth): the API-key-only APIKEY_PROVIDERS_GATEWAYS entry was
|
|
// removed as a duplicate now that clinepass is OAuth-primary (OAUTH_PROVIDERS.clinepass) with its
|
|
// BYOK path admitted through the DUAL_AUTH_APIKEY_PROVIDER_IDS gate instead (167->166), then the
|
|
// OpenVecta inference-gateway addition brought it back to 167, then #7246 (Chenzk API gateway)
|
|
// brought it to 168.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
|
|
const P = await import("../../src/shared/constants/providers.ts");
|
|
|
|
test("barrel still exports every catalog + key helpers", () => {
|
|
for (const name of [
|
|
"NOAUTH_PROVIDERS",
|
|
"OAUTH_PROVIDERS",
|
|
"WEB_COOKIE_PROVIDERS",
|
|
"APIKEY_PROVIDERS",
|
|
"LOCAL_PROVIDERS",
|
|
"SEARCH_PROVIDERS",
|
|
"AUDIO_ONLY_PROVIDERS",
|
|
"UPSTREAM_PROXY_PROVIDERS",
|
|
"CLOUD_AGENT_PROVIDERS",
|
|
"SYSTEM_PROVIDERS",
|
|
"AI_PROVIDERS",
|
|
"ALIAS_TO_ID",
|
|
"ID_TO_ALIAS",
|
|
"getProviderById",
|
|
"getProviderByAlias",
|
|
"resolveProviderId",
|
|
]) {
|
|
assert.ok(name in P, `missing export: ${name}`);
|
|
}
|
|
});
|
|
|
|
test("APIKEY_PROVIDERS merges the 6 family files into 172 entries (no loss / no dup)", async () => {
|
|
const keys = Object.keys((P as Record<string, object>).APIKEY_PROVIDERS);
|
|
assert.equal(keys.length, 172);
|
|
assert.equal(new Set(keys).size, 172, "duplicate keys after spread-merge");
|
|
// the merged object's entry-count equals the sum of the 6 semantic family files; families are a
|
|
// strict partition (every provider in exactly one), so the sum must be exactly 172.
|
|
const families: [string, string][] = [
|
|
["gateways", "APIKEY_PROVIDERS_GATEWAYS"],
|
|
["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"],
|
|
["inference-hosts", "APIKEY_PROVIDERS_INFERENCE"],
|
|
["enterprise-cloud", "APIKEY_PROVIDERS_ENTERPRISE"],
|
|
["regional", "APIKEY_PROVIDERS_REGIONAL"],
|
|
["specialty-media", "APIKEY_PROVIDERS_SPECIALTY"],
|
|
];
|
|
let famTotal = 0;
|
|
const seen = new Set<string>();
|
|
for (const [file, exportName] of families) {
|
|
const mod = await import(`../../src/shared/constants/providers/apikey/${file}.ts`);
|
|
const famKeys = Object.keys(mod[exportName]);
|
|
famTotal += famKeys.length;
|
|
for (const k of famKeys) {
|
|
assert.ok(!seen.has(k), `provider ${k} appears in more than one family`);
|
|
seen.add(k);
|
|
}
|
|
}
|
|
assert.equal(famTotal, 172, "families must partition all 172 providers");
|
|
});
|
|
|
|
test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {
|
|
const ai = (P as Record<string, Record<string, unknown>>).AI_PROVIDERS;
|
|
assert.ok(Object.keys(ai).length > 200);
|
|
assert.ok((P as Record<string, (id: string) => unknown>).getProviderById("openai"));
|
|
assert.ok((P as Record<string, (id: string) => unknown>).getProviderById("claude"));
|
|
// a moved catalog is reachable through the barrel re-export
|
|
assert.ok((P as Record<string, Record<string, unknown>>).APIKEY_PROVIDERS["openai"]);
|
|
});
|
|
|
|
test("each extracted data module is importable on its own", async () => {
|
|
const mods = [
|
|
["noauth", "NOAUTH_PROVIDERS"],
|
|
["oauth", "OAUTH_PROVIDERS"],
|
|
["web-cookie", "WEB_COOKIE_PROVIDERS"],
|
|
["local", "LOCAL_PROVIDERS"],
|
|
["search", "SEARCH_PROVIDERS"],
|
|
["audio", "AUDIO_ONLY_PROVIDERS"],
|
|
["upstream-proxy", "UPSTREAM_PROXY_PROVIDERS"],
|
|
["cloud-agent", "CLOUD_AGENT_PROVIDERS"],
|
|
["system", "SYSTEM_PROVIDERS"],
|
|
];
|
|
for (const [file, name] of mods) {
|
|
const m = await import(`../../src/shared/constants/providers/${file}.ts`);
|
|
assert.ok(m[name] && typeof m[name] === "object", `${file}.ts must export ${name}`);
|
|
}
|
|
});
|