mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-03 05:45:04 +03:00
* feat(media): Adobe Firefly image + video generation provider
Add unofficial adobe-firefly media provider with full OpenAI-compatible
image and video generation: Nano Banana / GPT Image families, Sora 2,
Veo 3.1 (standard/fast/reference), and Kling 3.0.
Supports browser session cookies (auto IMS token exchange) or direct
IMS access tokens, async submit-and-poll against Firefly 3P endpoints,
aspect-ratio and resolution controls, and multi-account web-session UX.
Chat completions are intentionally rejected (media-only surface).
Includes unit coverage for registry wiring, payload builders, auth
resolution, and mocked generate happy-paths.
* fix(adobe-firefly): clio auth, discovery fallback, credits balance
Root-cause 401 invalid token against live firefly.adobe.com captures:
generate/discovery use x-api-key + IMS client_id clio-playground-web
(not projectx_webapp). Align headers/origin, dual cookie to IMS exchange
(clio first, Express fallback), BKS poll rewrite for /jobs/result.
Models: parse POST /v2/models/discovery + static fallback catalog from
adobe/get_models.txt; expand image/video registries.
Limits: GET firefly.adobe.io/v1/credits/balance (SunbreakWebUI1) with
total/remaining + free/plan detail quotas. Clarify cookie vs JWT UX.
Unit tests: 27/27 pass.
* fix(adobe-firefly): reject guest tokens from page-only cookies
Live repro with firefly.adobe.com Cookie export: IMS check with
guest_allowed=true returns account_type=guest (no AdobeID). That token
fails generate (401 invalid token) and credits/balance (403
ErrMismatchOauthToken). guest_allowed=false needs adobelogin.com IMS
session cookies which are not present in a page-only Cookie paste.
- Detect/reject guest JWTs; clear error tells user to paste Bearer JWT
- Prefer user JWT from HAR/mixed paste; improve credential extraction
- Update web-cookie + credential UX to recommend Authorization Bearer
Unit tests 29/29.
* fix(adobe-firefly): production auth, Limits, and 408 load handling
Live validation against firefly.adobe.com + packaged VibeProxy:
Auth / credentials
- Prefer IMS user JWT (Bearer from firefly-3p); reject guest tokens from
page-only cookies with an actionable error
- Extract JWT from Bearer, access_token=, IMS sessionStorage tokenValue,
and mixed HAR pastes; prefer non-guest tokens
- Strip JWT from Cookie header (undici Headers.append crash on mixed paste)
- Keep sherlockToken → x-arp-session-id + sanitized Cookie for generate
Limits
- credits/balance → Record quotas (firefly_total / free / plan) so
providerLimits caches them (arrays were ignored)
- Allowlist adobe-firefly + firefly in USAGE_SUPPORTED + APIKEY limits
- Live: 10000 plan credits parsed end-to-end after refresh
Generate
- Browser-shaped gpt-image body (size auto, no extra top-level size)
- Exponential 408 "system under load" retries (8 attempts) with clear
client message that 408 is Adobe capacity, not invalid token
- Live: generate returns proper 408 under load; balance/models stay 200
Tests: adobe-firefly unit suite 33/33 pass.
* fix(adobe-firefly): match live capture headers; add gpt-image-2
- Do not send firefly.adobe.com Cookie to firefly-3p (wrong-origin; soft 408)
- Lift sherlockToken only into x-arp-session-id
- Poll headers match status_check.txt (Bearer + accept, no x-api-key)
- Catalog gpt-image-2 alias → upstream modelVersion "2" (GPT Image 2)
- Shorter 408 retry budget so clients fail fast with clear message
- Unit suite 34/34
* fix(adobe-firefly): always send x-arp-session-id on generate (fixes 408)
Root cause of Bearer JWT → HTTP 408 colligo "system under load":
submit only set x-arp-session-id when sherlockToken was present in a
cookie paste. JWT-only credentials never sent the header, and Adobe
soft-blocks those requests with instant 408 (x-colligo-timeout:0.0).
A/B against a real user IMS token:
- det nonce + synthetic ARP → 200
- random nonce + synthetic ARP → 200
- det nonce without ARP → 408
Match adobe2api / GPT2Image-Pro:
- buildAdobeSubmitNonce = sha256(user_id + prompt[:256])
- buildAdobeArpSessionId = base64({sid, ftr}) synthetic session
- buildAdobeSubmitHeaders always sets both headers
Live adobeFireflyGenerateImage end-to-end: submit + poll → S3 presigned URL.
* fix(adobe-firefly): drop literal cred fallbacks + type-clean tests
Addresses pre-merge review feedback on #8006:
- Removes the `|| "literal"` fallback after resolvePublicCred() in
adobeFireflyApiKey()/adobeFireflyExpressClientId()/adobeFireflyBalanceApiKey()
(open-sse/services/adobeFireflyClient.ts). resolvePublicCred() already
always returns the decoded embedded default, so the literal fallback
was dead code that reproduced the exact env-or-literal anti-pattern
docs/security/PUBLIC_CREDS.md documents as BAD (Hard Rule #11).
- Replaces the 11 `@typescript-eslint/no-explicit-any` casts in
tests/unit/adobe-firefly.test.ts with concrete types
(Record<string, unknown>, Headers, Error-narrowing on the
assert.rejects predicate), matching the pattern already used
elsewhere in this suite. `no-explicit-any` is a hard ESLint error
under tests/ in this repo.
- Freezes file-size baseline entries for the new
open-sse/services/adobeFireflyClient.ts (1958 LOC, new-file cap 800,
mirrors the qoderCli.ts precedent for a legitimately large new
provider client), open-sse/config/imageRegistry.ts (800->821, new
adobe-firefly registry entry) and the +3 LOC growth in
src/lib/usage/providerLimits.ts (1000->1003).
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
---------
Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com>
Co-authored-by: Diego Rodrigues de Sa e Souza <diegosouza.pw@gmail.com>
Co-authored-by: artickc <artickc@users.noreply.github.com>
481 lines
14 KiB
TypeScript
481 lines
14 KiB
TypeScript
// Re-export service kinds from leaf module (avoids circular dep with providerSchema)
|
|
export type { ServiceKind } from "./serviceKinds";
|
|
export { SERVICE_KIND_VALUES } from "./serviceKinds";
|
|
|
|
export type RiskNoticeVariant = "oauth" | "webCookie" | "deprecated" | "embedded-service";
|
|
|
|
export interface ProviderRiskNoticeFields {
|
|
subscriptionRisk?: boolean;
|
|
riskNoticeVariant?: RiskNoticeVariant;
|
|
isEmbeddedService?: boolean;
|
|
}
|
|
|
|
import { NOAUTH_PROVIDERS } from "./providers/noauth";
|
|
export { supportsNoAuthProviderProxy } from "./providers/noauth";
|
|
import { OAUTH_PROVIDERS } from "./providers/oauth";
|
|
import { WEB_COOKIE_PROVIDERS, resolveWebProviderHost } from "./providers/web-cookie";
|
|
export { resolveWebProviderHost };
|
|
export type { WebProviderHostLink } from "./providers/web-cookie";
|
|
import { APIKEY_PROVIDERS } from "./providers/apikey";
|
|
import { LOCAL_PROVIDERS } from "./providers/local";
|
|
import { SEARCH_PROVIDERS } from "./providers/search";
|
|
import { AUDIO_ONLY_PROVIDERS } from "./providers/audio";
|
|
import { UPSTREAM_PROXY_PROVIDERS } from "./providers/upstream-proxy";
|
|
import { CLOUD_AGENT_PROVIDERS } from "./providers/cloud-agent";
|
|
import { SYSTEM_PROVIDERS } from "./providers/system";
|
|
|
|
export const FREE_PROVIDERS = {};
|
|
|
|
// No-auth Providers
|
|
|
|
export const FREE_APIKEY_PROVIDER_IDS = new Set([
|
|
"qoder",
|
|
"mimocode",
|
|
"opencode",
|
|
"dahl",
|
|
// codebuddy-cn is OAuth-primary but the Tencent gateway also accepts a direct
|
|
// API key (Authorization: Bearer). Admit it through the same managed-provider
|
|
// gate so POST /api/providers accepts the dual-auth shape.
|
|
"codebuddy-cn",
|
|
// auggie is a fully local, credential-less CLI passthrough (auth handled by
|
|
// `auggie login` outside OmniRoute). Admitted here purely so POST /api/providers
|
|
// accepts an optional connection row for display/priority/testStatus tracking —
|
|
// no apiKey is ever required or sent upstream.
|
|
"auggie",
|
|
]);
|
|
|
|
export function supportsApiKeyOnFreeProvider(providerId: unknown): boolean {
|
|
return typeof providerId === "string" && FREE_APIKEY_PROVIDER_IDS.has(providerId);
|
|
}
|
|
|
|
// Web / Cookie Providers
|
|
|
|
// API Key Providers
|
|
|
|
// Sub-categories within APIKEY_PROVIDERS (used by dashboard and catalog views).
|
|
export const IMAGE_ONLY_PROVIDER_IDS = new Set([
|
|
"nanobanana",
|
|
"fal-ai",
|
|
"stability-ai",
|
|
"black-forest-labs",
|
|
"recraft",
|
|
"topaz",
|
|
"segmind",
|
|
"freepik",
|
|
]);
|
|
|
|
export const AGGREGATOR_PROVIDER_IDS = new Set([
|
|
"openrouter",
|
|
"synthetic",
|
|
"kilo-gateway",
|
|
"aimlapi",
|
|
"novita",
|
|
"piapi",
|
|
"getgoapi",
|
|
"laozhang",
|
|
"vercel-ai-gateway",
|
|
"agentrouter",
|
|
"thebai",
|
|
"fenayai",
|
|
"empower",
|
|
"poe",
|
|
"chutes",
|
|
"hackclub",
|
|
"freetheai",
|
|
"g4f-groq",
|
|
"g4f-gemini",
|
|
"g4f-pollinations",
|
|
"g4f-ollama",
|
|
"g4f-nvidia",
|
|
]);
|
|
|
|
export const ENTERPRISE_CLOUD_PROVIDER_IDS = new Set([
|
|
"azure-openai",
|
|
"azure-ai",
|
|
"bedrock",
|
|
"watsonx",
|
|
"oci",
|
|
"sap",
|
|
"vertex",
|
|
"vertex-partner",
|
|
"databricks",
|
|
"datarobot",
|
|
"clarifai",
|
|
"snowflake",
|
|
"heroku",
|
|
"modal",
|
|
]);
|
|
|
|
export const VIDEO_PROVIDER_IDS = new Set([
|
|
"runwayml",
|
|
"veoaifree-web",
|
|
"pollinations",
|
|
"minimax",
|
|
"together",
|
|
"replicate",
|
|
"haiper",
|
|
"leonardo",
|
|
"segmind",
|
|
"novita",
|
|
]);
|
|
|
|
// IDE Providers: editors with built-in AI subscription (separate section in UI).
|
|
// These providers live in OAUTH_PROVIDERS but render under "IDE Providers"
|
|
// instead of "OAuth Providers" to avoid visual duplication.
|
|
export const IDE_PROVIDER_IDS = new Set(["cursor", "zed", "trae"]);
|
|
|
|
export const EMBEDDING_RERANK_PROVIDER_IDS = new Set(["voyage-ai", "jina-ai"]);
|
|
|
|
// Local / Self-Hosted Providers
|
|
|
|
// Search Providers
|
|
|
|
// Audio Only Providers
|
|
|
|
export const OPENAI_COMPATIBLE_PREFIX = "openai-compatible-";
|
|
export const ANTHROPIC_COMPATIBLE_PREFIX = "anthropic-compatible-";
|
|
export const CLAUDE_CODE_COMPATIBLE_PREFIX = "anthropic-compatible-cc-";
|
|
|
|
export function isOpenAICompatibleProvider(providerId: unknown): providerId is string {
|
|
return typeof providerId === "string" && providerId.startsWith(OPENAI_COMPATIBLE_PREFIX);
|
|
}
|
|
|
|
export function isAnthropicCompatibleProvider(providerId: unknown): providerId is string {
|
|
return typeof providerId === "string" && providerId.startsWith(ANTHROPIC_COMPATIBLE_PREFIX);
|
|
}
|
|
|
|
export function isClaudeCodeCompatibleProvider(providerId: unknown): providerId is string {
|
|
return typeof providerId === "string" && providerId.startsWith(CLAUDE_CODE_COMPATIBLE_PREFIX);
|
|
}
|
|
|
|
export function isLocalProvider(providerId: unknown): boolean {
|
|
return (
|
|
typeof providerId === "string" &&
|
|
Object.prototype.hasOwnProperty.call(LOCAL_PROVIDERS, providerId)
|
|
);
|
|
}
|
|
|
|
export const SELF_HOSTED_CHAT_PROVIDER_IDS = new Set([
|
|
"ollama-local",
|
|
"lm-studio",
|
|
"vllm",
|
|
"lemonade",
|
|
"llamafile",
|
|
"llama-cpp",
|
|
"triton",
|
|
"docker-model-runner",
|
|
"xinference",
|
|
"oobabooga",
|
|
]);
|
|
|
|
export function isSelfHostedChatProvider(providerId: unknown): boolean {
|
|
return typeof providerId === "string" && SELF_HOSTED_CHAT_PROVIDER_IDS.has(providerId);
|
|
}
|
|
|
|
// Providers with heterogeneous/no-key auth that don't fit the NOAUTH_PROVIDERS
|
|
// registry (e.g. free-tier gateways where a key is accepted but not required).
|
|
// Kept as a Set (not an || chain) to keep providerAllowsOptionalApiKey's
|
|
// cyclomatic complexity flat as this list grows — see g4f.space (#6650).
|
|
const EXPLICIT_OPTIONAL_APIKEY_PROVIDER_IDS = new Set([
|
|
"searxng-search",
|
|
"pollinations",
|
|
"copilot-web",
|
|
"hackclub",
|
|
"g4f-groq",
|
|
"g4f-gemini",
|
|
"g4f-pollinations",
|
|
"g4f-ollama",
|
|
"g4f-nvidia",
|
|
"huggingchat",
|
|
"gitlawb",
|
|
"gitlawb-gmi",
|
|
]);
|
|
|
|
export function providerAllowsOptionalApiKey(providerId: unknown): boolean {
|
|
return (
|
|
// ponytail: any noAuth provider auto-qualifies — no per-provider maintenance
|
|
(typeof providerId === "string" && providerId in NOAUTH_PROVIDERS) ||
|
|
(typeof providerId === "string" && EXPLICIT_OPTIONAL_APIKEY_PROVIDER_IDS.has(providerId)) ||
|
|
isLocalProvider(providerId) ||
|
|
isSelfHostedChatProvider(providerId) ||
|
|
isOpenAICompatibleProvider(providerId) ||
|
|
isAnthropicCompatibleProvider(providerId)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Providers explicitly excluded from bulk API key add — auth is heterogeneous,
|
|
* OAuth-based, multi-field, or requires manual setup per connection.
|
|
*/
|
|
const BULK_API_KEY_EXCLUDED = new Set([
|
|
"vertex",
|
|
"vertex-partner",
|
|
"ollama-local",
|
|
"grok-web",
|
|
"perplexity-web",
|
|
"blackbox-web",
|
|
"muse-spark-web",
|
|
"deepseek-web",
|
|
"inner-ai",
|
|
"qoder",
|
|
"google-pse-search",
|
|
"command-code",
|
|
"azure",
|
|
]);
|
|
|
|
export function supportsBulkApiKey(providerId: unknown): boolean {
|
|
if (typeof providerId !== "string" || !providerId) return false;
|
|
if (BULK_API_KEY_EXCLUDED.has(providerId)) return false;
|
|
if (isLocalProvider(providerId)) return false;
|
|
if (isSelfHostedChatProvider(providerId)) return false;
|
|
if (isClaudeCodeCompatibleProvider(providerId)) return false;
|
|
return true;
|
|
}
|
|
|
|
// ── System Providers (virtual, not user-connectable) ──────────────────────────
|
|
|
|
const _PROVIDER_SECTIONS = [
|
|
NOAUTH_PROVIDERS,
|
|
OAUTH_PROVIDERS,
|
|
APIKEY_PROVIDERS,
|
|
WEB_COOKIE_PROVIDERS,
|
|
LOCAL_PROVIDERS,
|
|
SEARCH_PROVIDERS,
|
|
AUDIO_ONLY_PROVIDERS,
|
|
UPSTREAM_PROXY_PROVIDERS,
|
|
CLOUD_AGENT_PROVIDERS,
|
|
SYSTEM_PROVIDERS,
|
|
] as const;
|
|
|
|
let _aiProviders: Record<string, any> | null = null;
|
|
|
|
function getOrCreateAiProviders(): Record<string, any> {
|
|
if (!_aiProviders) {
|
|
_aiProviders = {};
|
|
for (const section of _PROVIDER_SECTIONS) {
|
|
Object.assign(_aiProviders, section);
|
|
}
|
|
}
|
|
return _aiProviders;
|
|
}
|
|
|
|
let _ALIAS_TO_ID: Record<string, string> | null = null;
|
|
|
|
function getOrCreateAliasToId(): Record<string, string> {
|
|
if (!_ALIAS_TO_ID) {
|
|
_ALIAS_TO_ID = {};
|
|
for (const section of _PROVIDER_SECTIONS) {
|
|
for (const p of Object.values(section)) {
|
|
if ((p as any).alias) _ALIAS_TO_ID[(p as any).alias] = (p as any).id;
|
|
}
|
|
}
|
|
}
|
|
return _ALIAS_TO_ID;
|
|
}
|
|
|
|
let _ID_TO_ALIAS: Record<string, string> | null = null;
|
|
|
|
function getOrCreateIdToAlias(): Record<string, string> {
|
|
if (!_ID_TO_ALIAS) {
|
|
_ID_TO_ALIAS = {};
|
|
for (const section of _PROVIDER_SECTIONS) {
|
|
for (const p of Object.values(section)) {
|
|
_ID_TO_ALIAS[(p as any).id] = (p as any).alias || (p as any).id;
|
|
}
|
|
}
|
|
}
|
|
return _ID_TO_ALIAS;
|
|
}
|
|
|
|
export function getProviderById(id: string) {
|
|
return (
|
|
(NOAUTH_PROVIDERS as Record<string, any>)[id] ??
|
|
(OAUTH_PROVIDERS as Record<string, any>)[id] ??
|
|
(APIKEY_PROVIDERS as Record<string, any>)[id] ??
|
|
(WEB_COOKIE_PROVIDERS as Record<string, any>)[id] ??
|
|
(LOCAL_PROVIDERS as Record<string, any>)[id] ??
|
|
(SEARCH_PROVIDERS as Record<string, any>)[id] ??
|
|
(AUDIO_ONLY_PROVIDERS as Record<string, any>)[id] ??
|
|
(UPSTREAM_PROXY_PROVIDERS as Record<string, any>)[id] ??
|
|
(CLOUD_AGENT_PROVIDERS as Record<string, any>)[id] ??
|
|
(SYSTEM_PROVIDERS as Record<string, any>)[id] ??
|
|
undefined
|
|
);
|
|
}
|
|
|
|
export const AI_PROVIDERS = new Proxy({} as Record<string, any>, {
|
|
get(_, key) {
|
|
if (key === "then") return undefined;
|
|
return typeof key === "string" ? getOrCreateAiProviders()[key] : undefined;
|
|
},
|
|
ownKeys() {
|
|
return Reflect.ownKeys(getOrCreateAiProviders());
|
|
},
|
|
has(_, key) {
|
|
return key in getOrCreateAiProviders();
|
|
},
|
|
getOwnPropertyDescriptor(_, key) {
|
|
const obj = getOrCreateAiProviders();
|
|
if (typeof key === "string" && key in obj) {
|
|
return { configurable: true, enumerable: true, value: obj[key] };
|
|
}
|
|
return undefined;
|
|
},
|
|
});
|
|
|
|
export type AiProviderId =
|
|
| keyof typeof NOAUTH_PROVIDERS
|
|
| keyof typeof OAUTH_PROVIDERS
|
|
| keyof typeof APIKEY_PROVIDERS
|
|
| keyof typeof WEB_COOKIE_PROVIDERS
|
|
| keyof typeof LOCAL_PROVIDERS
|
|
| keyof typeof SEARCH_PROVIDERS
|
|
| keyof typeof AUDIO_ONLY_PROVIDERS
|
|
| keyof typeof UPSTREAM_PROXY_PROVIDERS
|
|
| keyof typeof CLOUD_AGENT_PROVIDERS
|
|
| keyof typeof SYSTEM_PROVIDERS;
|
|
|
|
export type AiProviderDefinition =
|
|
| (typeof NOAUTH_PROVIDERS)[keyof typeof NOAUTH_PROVIDERS]
|
|
| (typeof OAUTH_PROVIDERS)[keyof typeof OAUTH_PROVIDERS]
|
|
| (typeof APIKEY_PROVIDERS)[keyof typeof APIKEY_PROVIDERS]
|
|
| (typeof WEB_COOKIE_PROVIDERS)[keyof typeof WEB_COOKIE_PROVIDERS]
|
|
| (typeof LOCAL_PROVIDERS)[keyof typeof LOCAL_PROVIDERS]
|
|
| (typeof SEARCH_PROVIDERS)[keyof typeof SEARCH_PROVIDERS]
|
|
| (typeof AUDIO_ONLY_PROVIDERS)[keyof typeof AUDIO_ONLY_PROVIDERS]
|
|
| (typeof UPSTREAM_PROXY_PROVIDERS)[keyof typeof UPSTREAM_PROXY_PROVIDERS]
|
|
| (typeof CLOUD_AGENT_PROVIDERS)[keyof typeof CLOUD_AGENT_PROVIDERS]
|
|
| (typeof SYSTEM_PROVIDERS)[keyof typeof SYSTEM_PROVIDERS];
|
|
|
|
// Auth methods
|
|
export const AUTH_METHODS = {
|
|
oauth: { id: "oauth", name: "OAuth", icon: "lock" },
|
|
apikey: { id: "apikey", name: "API Key", icon: "key" },
|
|
};
|
|
|
|
export function getProviderByAlias(alias: string): AiProviderDefinition | null {
|
|
for (const section of _PROVIDER_SECTIONS) {
|
|
for (const provider of Object.values(section)) {
|
|
if (provider.alias === alias || provider.id === alias) {
|
|
return provider as AiProviderDefinition;
|
|
}
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// Helper: Get provider ID from alias
|
|
export function resolveProviderId(aliasOrId: string): string {
|
|
const provider = getProviderByAlias(aliasOrId);
|
|
return provider?.id || aliasOrId;
|
|
}
|
|
|
|
export function getProviderAlias(providerId: string): string {
|
|
const provider = getProviderById(providerId);
|
|
return provider?.alias || providerId;
|
|
}
|
|
|
|
export const ALIAS_TO_ID = new Proxy({} as Record<string, string>, {
|
|
get(_, key) {
|
|
return typeof key === "string" ? getOrCreateAliasToId()[key] : undefined;
|
|
},
|
|
ownKeys() {
|
|
return Reflect.ownKeys(getOrCreateAliasToId());
|
|
},
|
|
has(_, key) {
|
|
return key in getOrCreateAliasToId();
|
|
},
|
|
getOwnPropertyDescriptor(_, key) {
|
|
const obj = getOrCreateAliasToId();
|
|
if (typeof key === "string" && key in obj) {
|
|
return { configurable: true, enumerable: true, value: obj[key] };
|
|
}
|
|
return undefined;
|
|
},
|
|
});
|
|
|
|
export const ID_TO_ALIAS = new Proxy({} as Record<string, string>, {
|
|
get(_, key) {
|
|
return typeof key === "string" ? getOrCreateIdToAlias()[key] : undefined;
|
|
},
|
|
ownKeys() {
|
|
return Reflect.ownKeys(getOrCreateIdToAlias());
|
|
},
|
|
has(_, key) {
|
|
return key in getOrCreateIdToAlias();
|
|
},
|
|
getOwnPropertyDescriptor(_, key) {
|
|
const obj = getOrCreateIdToAlias();
|
|
if (typeof key === "string" && key in obj) {
|
|
return { configurable: true, enumerable: true, value: obj[key] };
|
|
}
|
|
return undefined;
|
|
},
|
|
});
|
|
|
|
// Providers that support usage/quota API
|
|
export const USAGE_SUPPORTED_PROVIDERS = [
|
|
"antigravity",
|
|
"agy",
|
|
"kiro",
|
|
"amazon-q",
|
|
"github",
|
|
"codex",
|
|
"claude",
|
|
"cursor",
|
|
"qoder",
|
|
"kimi-coding",
|
|
"kimi-coding-apikey",
|
|
"glm",
|
|
"glm-cn",
|
|
"zai",
|
|
"glmt",
|
|
"opencode-go",
|
|
"ollama-cloud",
|
|
"minimax",
|
|
"minimax-cn",
|
|
"crof",
|
|
"nanogpt",
|
|
"deepseek",
|
|
"xiaomi-mimo",
|
|
"vertex",
|
|
"vertex-partner",
|
|
"codebuddy-cn",
|
|
// PromptQL playground credits (getCreditSummary → USD micros)
|
|
"promptql",
|
|
"pql",
|
|
// Adobe Firefly web (cookie/JWT as apikey) — GET firefly.adobe.io/v1/credits/balance
|
|
"adobe-firefly",
|
|
"firefly",
|
|
"hyperagent",
|
|
"ha",
|
|
];
|
|
|
|
// ── Zod validation at module load (Phase 7.2) ──
|
|
|
|
// Re-export the extracted data catalogs so external importers of providers.ts are unchanged.
|
|
export {
|
|
NOAUTH_PROVIDERS,
|
|
OAUTH_PROVIDERS,
|
|
WEB_COOKIE_PROVIDERS,
|
|
APIKEY_PROVIDERS,
|
|
LOCAL_PROVIDERS,
|
|
SEARCH_PROVIDERS,
|
|
AUDIO_ONLY_PROVIDERS,
|
|
UPSTREAM_PROXY_PROVIDERS,
|
|
CLOUD_AGENT_PROVIDERS,
|
|
SYSTEM_PROVIDERS,
|
|
};
|
|
|
|
import { validateProviders } from "../validation/providerSchema";
|
|
|
|
validateProviders(NOAUTH_PROVIDERS, "NOAUTH_PROVIDERS");
|
|
validateProviders(OAUTH_PROVIDERS, "OAUTH_PROVIDERS");
|
|
validateProviders(APIKEY_PROVIDERS, "APIKEY_PROVIDERS");
|
|
validateProviders(WEB_COOKIE_PROVIDERS, "WEB_COOKIE_PROVIDERS");
|
|
validateProviders(LOCAL_PROVIDERS, "LOCAL_PROVIDERS");
|
|
validateProviders(SEARCH_PROVIDERS, "SEARCH_PROVIDERS");
|
|
validateProviders(AUDIO_ONLY_PROVIDERS, "AUDIO_ONLY_PROVIDERS");
|
|
validateProviders(UPSTREAM_PROXY_PROVIDERS, "UPSTREAM_PROXY_PROVIDERS");
|
|
validateProviders(CLOUD_AGENT_PROVIDERS, "CLOUD_AGENT_PROVIDERS");
|