From 76c3b3b8d3d295f4f278ac82b4483ccf4fb59dab Mon Sep 17 00:00:00 2001 From: Diego Rodrigues de Sa e Souza <8016841+diegosouzapw@users.noreply.github.com> Date: Fri, 17 Jul 2026 11:49:50 -0300 Subject: [PATCH] feat: add Freepik (Magnific Mystic) image generation provider (#6654) (#7597) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(providers): add Freepik (Magnific Mystic) image generation provider (#6654) Adds an official, API-key-based Freepik image-gen provider using the Mystic endpoint (POST /v1/ai/mystic -> async task_id -> GET /v1/ai/mystic/{id} polling), modeled on the existing leonardo.ts generationId adapter pattern. - open-sse/config/providers/registry/freepik/index.ts: new registry module (kept separate to avoid pushing the frozen imageRegistry.ts over the file-size cap) with the 6 real Mystic style models (realism, fluid, zen, flexible, super_real, editorial_portraits) — not the "Flux/Imagen3" list from the original feature request, which independent research showed was stale. - open-sse/handlers/imageGeneration/providers/freepik.ts: submit+poll adapter; all error paths route through sanitizeErrorMessage() (Hard Rule #12), configurable poll interval/timeout via body.poll_interval_ms / poll_timeout_ms for fast, deterministic tests. - Registered in providers.ts (IMAGE_ONLY_PROVIDER_IDS) and apikey/specialty-media.ts (catalog metadata), with the corrected free-tier note (one-time ~€5 credit, not a recurring "100/month" allotment). Drops the "100 free credits/month" and "Flux/Imagen3 selectable models" claims from the original issue - verification showed the free tier is a one-time ~€5 API credit and Imagen 3 only underlies the `fluid` style, not a separately selectable model. Domain: api.freepik.com is still live as of this writing despite Freepik's April-2026 API-docs rebrand to Magnific (docs.freepik.com -> docs.magnific.com); noted inline for future re-verification. Closes #6654 * test: align APIKEY_PROVIDERS count to 171 after freepik + release merge (#7597) --- .../6654-freepik-pikaso-image-provider.md | 1 + open-sse/config/imageRegistry.ts | 2 + .../providers/registry/freepik/index.ts | 26 ++ open-sse/handlers/imageGeneration.ts | 11 + .../imageGeneration/providers/freepik.ts | 284 ++++++++++++++++++ src/shared/constants/providers.ts | 1 + .../providers/apikey/specialty-media.ts | 12 + tests/unit/freepik-image-handler.test.ts | 163 ++++++++++ tests/unit/providers-constants-split.test.ts | 10 +- 9 files changed, 505 insertions(+), 5 deletions(-) create mode 100644 changelog.d/features/6654-freepik-pikaso-image-provider.md create mode 100644 open-sse/config/providers/registry/freepik/index.ts create mode 100644 open-sse/handlers/imageGeneration/providers/freepik.ts create mode 100644 tests/unit/freepik-image-handler.test.ts diff --git a/changelog.d/features/6654-freepik-pikaso-image-provider.md b/changelog.d/features/6654-freepik-pikaso-image-provider.md new file mode 100644 index 0000000000..9804299d2d --- /dev/null +++ b/changelog.d/features/6654-freepik-pikaso-image-provider.md @@ -0,0 +1 @@ +- feat(providers): add Freepik (Magnific Mystic) API-key image generation provider — async submit/poll flow with realism/fluid/zen/flexible/super_real/editorial_portraits models (#6654) diff --git a/open-sse/config/imageRegistry.ts b/open-sse/config/imageRegistry.ts index 8c61ef02c1..3aa06dc047 100644 --- a/open-sse/config/imageRegistry.ts +++ b/open-sse/config/imageRegistry.ts @@ -7,6 +7,7 @@ import { LMARENA_DIRECT_IMAGE_MODELS } from "./providers/registry/lmarena/directModels.ts"; import { KIE_IMAGE_MODELS } from "./providers/registry/kie/imageModels.ts"; +import { FREEPIK_IMAGE_PROVIDER } from "./providers/registry/freepik/index.ts"; interface ImageModelEntry { id: string; @@ -365,6 +366,7 @@ export const IMAGE_PROVIDERS: Record = { ], supportedSizes: ["1024x1024", "1024x1792", "1792x1024"], }, + freepik: FREEPIK_IMAGE_PROVIDER, sdwebui: { id: "sdwebui", baseUrl: "http://localhost:7860/sdapi/v1/txt2img", diff --git a/open-sse/config/providers/registry/freepik/index.ts b/open-sse/config/providers/registry/freepik/index.ts new file mode 100644 index 0000000000..7b99c71168 --- /dev/null +++ b/open-sse/config/providers/registry/freepik/index.ts @@ -0,0 +1,26 @@ +/** + * Freepik (Magnific Mystic) image provider registry entry. + * Extracted into its own module to keep open-sse/config/imageRegistry.ts + * under the file-size cap (god-file decomposition; semantic split). + */ +export const FREEPIK_IMAGE_PROVIDER = { + id: "freepik", + // Freepik rebranded its API docs to Magnific in April 2026; the Mystic + // endpoint itself still lives under api.freepik.com as of this writing + // (docs.freepik.com redirects to docs.magnific.com, but the API host + // has not moved). Re-verify against live docs if this ever 404s. + baseUrl: "https://api.freepik.com/v1/ai/mystic", + statusUrl: "https://api.freepik.com/v1/ai/mystic", + authType: "apikey", + authHeader: "x-freepik-api-key", + format: "freepik-image", // custom: async submit task_id, then poll GET /{task_id} + models: [ + { id: "realism", name: "Mystic Realism" }, + { id: "fluid", name: "Mystic Fluid (Imagen 3)" }, + { id: "zen", name: "Mystic Zen" }, + { id: "flexible", name: "Mystic Flexible" }, + { id: "super_real", name: "Mystic Super Real" }, + { id: "editorial_portraits", name: "Mystic Editorial Portraits" }, + ], + supportedSizes: ["1024x1024", "1024x1792", "1792x1024"], +}; diff --git a/open-sse/handlers/imageGeneration.ts b/open-sse/handlers/imageGeneration.ts index 3b8cfc6899..b8474a425a 100644 --- a/open-sse/handlers/imageGeneration.ts +++ b/open-sse/handlers/imageGeneration.ts @@ -57,6 +57,7 @@ import { handleImagen3ImageGeneration } from "./imageGeneration/providers/imagen import { handleIdeogramImageGeneration } from "./imageGeneration/providers/ideogram.ts"; import { handleHaiperImageGeneration } from "./imageGeneration/providers/haiper.ts"; import { handleLeonardoImageGeneration } from "./imageGeneration/providers/leonardo.ts"; +import { handleFreepikImageGeneration } from "./imageGeneration/providers/freepik.ts"; import { handleChatGptWebImageGeneration, extractMarkdownImageUrls, @@ -534,6 +535,16 @@ export async function handleImageGeneration({ log, }); } + if (providerConfig.format === "freepik-image") { + return handleFreepikImageGeneration({ + model, + provider, + providerConfig, + body, + credentials, + log, + }); + } if (providerConfig.format === "nvidia-nim") { return handleNvidiaNimImageGeneration({ diff --git a/open-sse/handlers/imageGeneration/providers/freepik.ts b/open-sse/handlers/imageGeneration/providers/freepik.ts new file mode 100644 index 0000000000..750ca2f664 --- /dev/null +++ b/open-sse/handlers/imageGeneration/providers/freepik.ts @@ -0,0 +1,284 @@ +// Freepik (Magnific Mystic) image generation adapter. +// Async submit->poll flow modeled on leonardo.ts's generationId pattern: +// POST /v1/ai/mystic returns { data: { task_id, status } }, then +// GET /v1/ai/mystic/{task_id} is polled until status is COMPLETED/FAILED. +// Docs: https://docs.magnific.com/api-reference/mystic (Freepik rebranded to +// Magnific in April 2026; both `api.freepik.com` and the newer +// `api.magnific.com` domain/header pair are in circulation during the +// transition, so the base URL and auth header both come from providerConfig +// rather than being hardcoded here). + +import { saveCallLog } from "@/lib/usageDb"; +import { sleep } from "../../../utils/sleep.ts"; +import { sanitizeErrorMessage } from "../../../utils/error.ts"; + +const DEFAULT_POLL_INTERVAL_MS = 4000; +const DEFAULT_POLL_TIMEOUT_MS = 180000; + +function normalizePositiveNumber(value: unknown, fallback: number): number { + const n = Number(value); + if (!Number.isFinite(n) || n <= 0) return fallback; + return Math.floor(n); +} + +interface FreepikProviderConfig { + baseUrl: string; + statusUrl?: string; + authHeader?: string; +} + +interface FreepikCredentials { + apiKey?: string; +} + +interface FreepikGenerationParams { + model: string; + provider: string; + providerConfig: FreepikProviderConfig; + body: Record; + credentials: FreepikCredentials; + log?: { info: (tag: string, msg: string) => void; error: (tag: string, msg: string) => void }; +} + +interface FreepikImageResult { + success: boolean; + status?: number; + error?: string; + data?: { created: number; data: Array<{ b64_json: string }> }; +} + +function freepikAuthHeader(providerConfig: FreepikProviderConfig, token: string) { + const headerName = providerConfig.authHeader || "x-freepik-api-key"; + return { [headerName]: token }; +} + +async function logAndFail(params: { + provider: string; + model: string; + startTime: number; + status: number; + error: string; +}): Promise { + const { provider, model, startTime, status, error } = params; + const sanitized = sanitizeErrorMessage(error); + saveCallLog({ + method: "POST", + path: "/v1/images/generations", + status, + model: `${provider}/${model}`, + provider, + duration: Date.now() - startTime, + error: sanitized.slice(0, 500), + }).catch(() => {}); + return { success: false, status, error: sanitized }; +} + +async function submitMysticTask(params: { + providerConfig: FreepikProviderConfig; + token: string; + model: string; + prompt: string; + body: Record; +}) { + const { providerConfig, token, model, prompt, body } = params; + return fetch(providerConfig.baseUrl, { + method: "POST", + headers: { + "Content-Type": "application/json", + ...freepikAuthHeader(providerConfig, token), + }, + body: JSON.stringify({ + prompt, + model: model || "realism", + resolution: typeof body.resolution === "string" ? body.resolution : "1k", + aspect_ratio: typeof body.aspect_ratio === "string" ? body.aspect_ratio : "square_1_1", + }), + }); +} + +async function pollMysticTask(params: { + providerConfig: FreepikProviderConfig; + token: string; + taskId: string; +}): Promise<{ status: string; imageUrl?: string }> { + const { providerConfig, token, taskId } = params; + const statusBase = providerConfig.statusUrl || providerConfig.baseUrl; + const res = await fetch(`${statusBase}/${taskId}`, { + headers: { ...freepikAuthHeader(providerConfig, token) }, + }); + const json = await res.json(); + const task = json?.data || json; + const status = typeof task?.status === "string" ? task.status : "IN_PROGRESS"; + const generated = Array.isArray(task?.generated) ? task.generated : []; + return { status, imageUrl: typeof generated[0] === "string" ? generated[0] : undefined }; +} + +async function downloadGeneratedImage(imageUrl: string): Promise< + { ok: true; b64: string } | { ok: false; status: number; error: string } +> { + const imgRes = await fetch(imageUrl); + if (!imgRes.ok) { + return { ok: false, status: imgRes.status, error: `Failed to download image: ${imgRes.status}` }; + } + const buf = await imgRes.arrayBuffer(); + return { ok: true, b64: Buffer.from(buf).toString("base64") }; +} + +async function resolveCompletedResult(params: { + provider: string; + model: string; + startTime: number; + imageUrl?: string; +}): Promise { + const { provider, model, startTime, imageUrl } = params; + if (!imageUrl) { + return logAndFail({ + provider, + model, + startTime, + status: 502, + error: "Freepik Mystic completed without a generated image URL", + }); + } + const downloaded = await downloadGeneratedImage(imageUrl); + if (!downloaded.ok) { + return { success: false, status: downloaded.status, error: downloaded.error }; + } + saveCallLog({ + method: "POST", + path: "/v1/images/generations", + status: 200, + model: `${provider}/${model}`, + provider, + duration: Date.now() - startTime, + }).catch(() => {}); + return { + success: true, + data: { created: Math.floor(Date.now() / 1000), data: [{ b64_json: downloaded.b64 }] }, + }; +} + +async function pollUntilDone(params: { + providerConfig: FreepikProviderConfig; + token: string; + taskId: string; + provider: string; + model: string; + startTime: number; + pollIntervalMs: number; + pollTimeoutMs: number; +}): Promise { + const { providerConfig, token, taskId, provider, model, startTime, pollIntervalMs, pollTimeoutMs } = + params; + const deadline = Date.now() + pollTimeoutMs; + + while (Date.now() < deadline) { + await sleep(pollIntervalMs); + const { status, imageUrl } = await pollMysticTask({ providerConfig, token, taskId }); + + if (status === "COMPLETED") { + return resolveCompletedResult({ provider, model, startTime, imageUrl }); + } + if (status === "FAILED") { + return logAndFail({ + provider, + model, + startTime, + status: 502, + error: "Freepik Mystic image generation failed", + }); + } + } + + return logAndFail({ + provider, + model, + startTime, + status: 504, + error: "Freepik Mystic image generation timed out", + }); +} + +async function submitAndGetTaskId(params: { + providerConfig: FreepikProviderConfig; + token: string; + model: string; + prompt: string; + body: Record; + provider: string; + startTime: number; +}): Promise<{ taskId: string } | { failed: FreepikImageResult }> { + const { providerConfig, token, model, prompt, body, provider, startTime } = params; + const res = await submitMysticTask({ providerConfig, token, model, prompt, body }); + if (!res.ok) { + const errorText = await res.text(); + return { failed: await logAndFail({ provider, model, startTime, status: res.status, error: errorText }) }; + } + + const submitJson = await res.json(); + const taskId = submitJson?.data?.task_id || submitJson?.task_id; + if (!taskId) { + return { + failed: await logAndFail({ + provider, + model, + startTime, + status: 502, + error: "Freepik Mystic did not return a task_id", + }), + }; + } + return { taskId }; +} + +export async function handleFreepikImageGeneration({ + model, + provider, + providerConfig, + body, + credentials, + log, +}: FreepikGenerationParams): Promise { + const startTime = Date.now(); + const token = credentials?.apiKey || ""; + const prompt = typeof body.prompt === "string" ? body.prompt : String(body.prompt ?? ""); + const pollIntervalMs = normalizePositiveNumber(body.poll_interval_ms, DEFAULT_POLL_INTERVAL_MS); + const pollTimeoutMs = normalizePositiveNumber(body.poll_timeout_ms, DEFAULT_POLL_TIMEOUT_MS); + if (log) { + log.info("IMAGE", `${provider}/${model} (freepik-mystic) | prompt: "${prompt.slice(0, 60)}..."`); + } + + try { + const submitted = await submitAndGetTaskId({ + providerConfig, + token, + model, + prompt, + body, + provider, + startTime, + }); + if ("failed" in submitted) return submitted.failed; + + return await pollUntilDone({ + providerConfig, + token, + taskId: submitted.taskId, + provider, + model, + startTime, + pollIntervalMs, + pollTimeoutMs, + }); + } catch (err) { + const message = (err as Error)?.message || String(err); + if (log) log.error("IMAGE", `${provider} freepik error: ${sanitizeErrorMessage(message)}`); + return logAndFail({ + provider, + model, + startTime, + status: 502, + error: `Image provider error: ${message}`, + }); + } +} diff --git a/src/shared/constants/providers.ts b/src/shared/constants/providers.ts index df179946c9..f509444f74 100644 --- a/src/shared/constants/providers.ts +++ b/src/shared/constants/providers.ts @@ -58,6 +58,7 @@ export const IMAGE_ONLY_PROVIDER_IDS = new Set([ "black-forest-labs", "recraft", "topaz", + "freepik", ]); export const AGGREGATOR_PROVIDER_IDS = new Set([ diff --git a/src/shared/constants/providers/apikey/specialty-media.ts b/src/shared/constants/providers/apikey/specialty-media.ts index 15ddf2d598..4ca6609217 100644 --- a/src/shared/constants/providers/apikey/specialty-media.ts +++ b/src/shared/constants/providers/apikey/specialty-media.ts @@ -83,6 +83,18 @@ 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)", + icon: "image", + color: "#1B9E7F", + textIcon: "FP", + website: "https://freepik.com", + authHint: "Get API key at freepik.com/developers (Mystic image endpoint)", + hasFree: true, + freeNote: "One-time ~€5 API credit for new accounts; pay-per-use afterward.", + }, suno: { id: "suno", alias: "suno", diff --git a/tests/unit/freepik-image-handler.test.ts b/tests/unit/freepik-image-handler.test.ts new file mode 100644 index 0000000000..610445e5af --- /dev/null +++ b/tests/unit/freepik-image-handler.test.ts @@ -0,0 +1,163 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import dns from "node:dns"; + +import { handleImageGeneration } from "../../open-sse/handlers/imageGeneration.ts"; +import { IMAGE_PROVIDERS } from "../../open-sse/config/imageRegistry.ts"; +import { APIKEY_PROVIDERS } from "../../src/shared/constants/providers.ts"; +import { IMAGE_ONLY_PROVIDER_IDS } from "../../src/shared/constants/providers.ts"; + +// Stub DNS for fetchRemoteImage/direct-fetch DNS-rebinding guards, mirroring +// tests/unit/nanobanana-image-handler.test.ts. +const originalDnsLookup = dns.promises.lookup; +(dns.promises as { lookup: unknown }).lookup = (async ( + _hostname: string, + options?: { all?: boolean } +) => { + const record = { address: "203.0.113.1", family: 4 }; + return options && options.all ? [record] : record; +}) as typeof dns.promises.lookup; +process.on("exit", () => { + (dns.promises as { lookup: unknown }).lookup = originalDnsLookup; +}); + +test("freepik provider is registered (registry shape)", () => { + assert.ok(APIKEY_PROVIDERS.freepik, "freepik should be in APIKEY_PROVIDERS"); + assert.equal(APIKEY_PROVIDERS.freepik.id, "freepik"); + assert.ok(IMAGE_ONLY_PROVIDER_IDS.has("freepik"), "freepik should be in IMAGE_ONLY_PROVIDER_IDS"); + + const provider = IMAGE_PROVIDERS.freepik; + assert.ok(provider, "freepik should be in IMAGE_PROVIDERS"); + assert.equal(provider.format, "freepik-image"); + assert.equal(provider.authType, "apikey"); + assert.equal(provider.authHeader, "x-freepik-api-key"); + assert.ok(provider.models.some((m) => m.id === "realism")); + assert.ok(provider.models.some((m) => m.id === "fluid")); +}); + +test("handleImageGeneration(freepik): async submit+poll returns b64_json payload", async () => { + const originalFetch = globalThis.fetch; + let pollCount = 0; + + globalThis.fetch = (async (url: string, options: { headers?: Record; body?: string } = {}) => { + const u = String(url); + + if (u === "https://api.freepik.com/v1/ai/mystic") { + assert.equal(options.headers?.["x-freepik-api-key"], "test-key"); + const parsed = JSON.parse(options.body as string); + assert.equal(parsed.prompt, "a red panda astronaut"); + assert.equal(parsed.model, "realism"); + return new Response( + JSON.stringify({ data: { task_id: "task-freepik-1", status: "CREATED" } }), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + + if (u === "https://api.freepik.com/v1/ai/mystic/task-freepik-1") { + pollCount += 1; + if (pollCount < 2) { + return new Response( + JSON.stringify({ data: { task_id: "task-freepik-1", status: "IN_PROGRESS" } }), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + return new Response( + JSON.stringify({ + data: { + task_id: "task-freepik-1", + status: "COMPLETED", + generated: ["https://cdn.example.com/freepik-result.png"], + }, + }), + { status: 200, headers: { "content-type": "application/json" } } + ); + } + + if (u === "https://cdn.example.com/freepik-result.png") { + return new Response(new Uint8Array([0x89, 0x50, 0x4e, 0x47]), { status: 200 }); + } + + throw new Error(`Unexpected URL: ${u}`); + }) as typeof fetch; + + try { + const result = await handleImageGeneration({ + body: { + model: "freepik/realism", + prompt: "a red panda astronaut", + poll_interval_ms: 1, + }, + credentials: { apiKey: "test-key" }, + log: null, + }); + + assert.equal(result.success, true); + assert.equal(result.data.data.length, 1); + assert.equal(result.data.data[0].b64_json, "iVBORw=="); + assert.equal(pollCount, 2); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("handleImageGeneration(freepik): FAILED status returns sanitized 502 error", async () => { + const originalFetch = globalThis.fetch; + + globalThis.fetch = (async (url: string) => { + const u = String(url); + if (u === "https://api.freepik.com/v1/ai/mystic") { + return new Response(JSON.stringify({ data: { task_id: "task-fail", status: "CREATED" } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + } + if (u === "https://api.freepik.com/v1/ai/mystic/task-fail") { + return new Response(JSON.stringify({ data: { task_id: "task-fail", status: "FAILED" } }), { + status: 200, + headers: { "content-type": "application/json" }, + }); + } + throw new Error(`Unexpected URL: ${u}`); + }) as typeof fetch; + + try { + const result = await handleImageGeneration({ + body: { model: "freepik/realism", prompt: "broken prompt", poll_interval_ms: 1 }, + credentials: { apiKey: "test-key" }, + log: null, + }); + + assert.equal(result.success, false); + assert.equal(result.status, 502); + assert.match(result.error, /Freepik Mystic image generation failed/); + // Hard Rule #12: error responses must never leak a raw stack trace / file path. + assert.ok(!result.error.includes("at /")); + } finally { + globalThis.fetch = originalFetch; + } +}); + +test("handleImageGeneration(freepik): submit error response is sanitized, not raw upstream body", async () => { + const originalFetch = globalThis.fetch; + + globalThis.fetch = (async () => { + // Simulate an upstream error body containing something that looks like a + // stack trace / absolute source path, to prove sanitizeErrorMessage runs. + const stackyBody = "Error: boom\n at /srv/app/handlers/mystic.ts:42:10"; + return new Response(stackyBody, { status: 500 }); + }) as typeof fetch; + + try { + const result = await handleImageGeneration({ + body: { model: "freepik/realism", prompt: "x" }, + credentials: { apiKey: "test-key" }, + log: null, + }); + + assert.equal(result.success, false); + assert.equal(result.status, 500); + assert.ok(!result.error.includes("/srv/app/handlers/mystic.ts")); + } finally { + globalThis.fetch = originalFetch; + } +}); diff --git a/tests/unit/providers-constants-split.test.ts b/tests/unit/providers-constants-split.test.ts index 666baaae27..925873d763 100644 --- a/tests/unit/providers-constants-split.test.ts +++ b/tests/unit/providers-constants-split.test.ts @@ -37,12 +37,12 @@ test("barrel still exports every catalog + key helpers", () => { } }); -test("APIKEY_PROVIDERS merges the 6 family files into 170 entries (no loss / no dup)", async () => { +test("APIKEY_PROVIDERS merges the 6 family files into 171 entries (no loss / no dup)", async () => { const keys = Object.keys((P as Record).APIKEY_PROVIDERS); - assert.equal(keys.length, 170); - assert.equal(new Set(keys).size, 170, "duplicate keys after spread-merge"); + assert.equal(keys.length, 171); + assert.equal(new Set(keys).size, 171, "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 170. + // strict partition (every provider in exactly one), so the sum must be exactly 171. const families: [string, string][] = [ ["gateways", "APIKEY_PROVIDERS_GATEWAYS"], ["frontier-labs", "APIKEY_PROVIDERS_FRONTIER"], @@ -62,7 +62,7 @@ test("APIKEY_PROVIDERS merges the 6 family files into 170 entries (no loss / no seen.add(k); } } - assert.equal(famTotal, 170, "families must partition all 170 providers"); + assert.equal(famTotal, 171, "families must partition all 171 providers"); }); test("AI_PROVIDERS Proxy aggregates all sections; lookups resolve", () => {