fix(providers): rename Freepik slug to Magnific and validate Magnific API keys (#10594)

Canonical provider id renamed freepik → magnific (Magnific Mystic official API), with a permanent redirect + runtime alias so old freepik/<model> traffic and /dashboard/providers/freepik URLs keep working. Existing provider=freepik connection rows are rewritten to magnific by migration 160.

Closes #10604.

Validated in an isolated worktree boarded onto origin/release/v3.8.50 (0 conflicts, 138 files):
- Focused suite: 54/54 tests pass (magnific-image-handler, provider-validation-image-only, provider-alias-uniqueness, redirects-cli-renames).
- check-file-size, check-changelog-integrity: OK.
- typecheck:core: clean.
- check-complexity / check-cognitive-complexity: OK, both under baseline.

Co-authored-by: RaviTharuma <RaviTharuma@users.noreply.github.com>
This commit is contained in:
Ravi Tharuma
2026-08-21 03:15:48 +02:00
committed by GitHub
parent f71f3da08b
commit 2cd14b1696
139 changed files with 5734 additions and 4000 deletions

View File

@@ -1,21 +1,12 @@
// 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";
@@ -70,6 +61,10 @@ export const PROVIDER_CONNECTION_FAMILY_ALIASES: Readonly<Record<string, readonl
alibaba: ["alibaba-cn"],
"kimi-coding": ["kimi-coding-apikey"],
xai: ["xai-oauth", "xao"],
// magnific is the canonical (post-rebrand) slug; freepik stays a legacy
// alias so old URLs and pre-migration connection rows keep working.
magnific: ["freepik"],
freepik: ["magnific"],
};
export function getProviderConnectionFamilyIds(providerId: unknown): readonly string[] {
@@ -90,7 +85,7 @@ export const IMAGE_ONLY_PROVIDER_IDS = new Set([
"recraft",
"topaz",
"segmind",
"freepik",
"magnific",
"deepai",
]);
@@ -390,18 +385,6 @@ export const AI_PROVIDERS = new Proxy({} as Record<string, any>, {
},
});
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]

View File

@@ -85,15 +85,16 @@ export const APIKEY_PROVIDERS_SPECIALTY = {
website: "https://ideogram.ai",
authHint: "Get API key at ideogram.ai/docs/api",
},
freepik: {
id: "freepik",
alias: "fpk",
name: "Freepik (Mystic)",
magnific: {
id: "magnific",
alias: "freepik",
name: "Magnific",
icon: "image",
color: "#1B9E7F",
textIcon: "FP",
website: "https://freepik.com",
authHint: "Get API key at freepik.com/developers (Mystic image endpoint)",
textIcon: "MG",
website: "https://www.magnific.com",
authHint:
"Get an API key at magnific.com/user/api-keys (header x-magnific-api-key). Legacy Freepik developer keys still work.",
hasFree: true,
freeNote: "One-time ~€5 API credit for new accounts; pay-per-use afterward.",
},

View File

@@ -71,6 +71,8 @@ export const rtkConfigSchema = z
trustProjectFilters: z.boolean().optional(),
rawOutputRetention: rtkRawOutputRetentionSchema.optional(),
rawOutputMaxBytes: z.number().int().min(1024).max(10_000_000).optional(),
rawOutputMaxFiles: z.number().int().min(1).max(10_000_000).optional(),
rawOutputMaxAgeDays: z.number().int().min(1).max(3650).optional(),
enableGrouping: z.boolean().optional(),
groupingThreshold: z.number().int().min(2).max(100).optional(),
stripCodeComments: z.boolean().optional(),