mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-27 01:22:10 +03:00
Compare commits
1 Commits
fix/11296-
...
fix/11526-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d2abc313e |
@@ -1 +0,0 @@
|
||||
- **fix(kie):** correct 12 more KIE Market catalog ids that were sent to `createTask` unchanged but diverge from KIE's documented upstream `model` values — GPT Image 2 T2I/I2I (drops the `gpt/` prefix), GPT Image 1.5 T2I/I2I (`gpt-image/` namespace), Seedream 5.0 Lite T2I/I2I (drops the `.0`), all 4 Flux 2 variants (`flux-2/` namespace, generic variant renamed `flex`), and Wan 2.7 Image / Image Pro (dash instead of dot) — each verified individually against the literal example request published on docs.kie.ai. `#11326`'s "everything else already matches" claim was wrong a second time (#11296); `z-image/4.0-*`/`z-image/4.5-*` and `flux/kontext` remain open, documented as unresolved in `KIE_MARKET_UPSTREAM_MODEL_IDS`'s comment pending further verification.
|
||||
@@ -0,0 +1 @@
|
||||
- **fix(sse):** cap the upstream headers-wait phase for STREAMING requests to a client-realistic ceiling (110s, under Codex's own ~120s hard client-abort window) instead of the flat 10-minute `FETCH_TIMEOUT_MS` default — that default was 5x longer than the body-phase readiness watchdog's own adaptive bound, so a request whose upstream never returned any response at all (not even headers, e.g. a stalled NVIDIA target behind a tool-heavy Responses→Chat translation) kept the client connection alive on keepalives only, guaranteeing the client's own patience ran out first with an opaque 499 instead of OmniRoute detecting and failing the stall fast. Non-streaming requests are unaffected — they keep the existing flat default (`open-sse/utils/fetchStartTimeoutPolicy.ts`) (#11526)
|
||||
@@ -1,5 +1,6 @@
|
||||
import { HTTP_STATUS, FETCH_TIMEOUT_MS } from "../config/constants.ts";
|
||||
import { getRegistryEntry } from "../config/providerRegistry.ts";
|
||||
import { resolveFetchStartTimeout } from "../utils/fetchStartTimeoutPolicy.ts";
|
||||
import {
|
||||
resolveAlternateFormat,
|
||||
type AlternateFormat,
|
||||
@@ -902,9 +903,24 @@ export class BaseExecutor {
|
||||
clampNestedThinkingBudget(transformedBody, thinkingBudgetClampedMax);
|
||||
}
|
||||
|
||||
// Timeout only covers response start; stream stalls are handled downstream.
|
||||
// #11526: streaming requests cap the headers-wait phase to a client-realistic
|
||||
// ceiling (see fetchStartTimeoutPolicy.ts) — non-streaming keeps the flat default.
|
||||
// Declared outside the try/catch below so the catch's TIMEOUT log (on the
|
||||
// error path) reports the same effective value the fetch actually used.
|
||||
const fetchStartTimeoutPolicy = resolveFetchStartTimeout({
|
||||
baseTimeoutMs: this.getTimeoutMs(),
|
||||
stream,
|
||||
});
|
||||
const fetchStartTimeoutMs = fetchStartTimeoutPolicy.timeoutMs;
|
||||
if (fetchStartTimeoutPolicy.capped) {
|
||||
log?.debug?.(
|
||||
"TIMEOUT",
|
||||
`fetch-start timeout capped ${fetchStartTimeoutPolicy.baseTimeoutMs}ms -> ${fetchStartTimeoutMs}ms (streaming)`
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
// Timeout only covers response start; stream stalls are handled downstream.
|
||||
const fetchStartTimeoutMs = this.getTimeoutMs();
|
||||
const fetchWithStartTimeout = async (requestUrl: string, requestOptions: RequestInit) => {
|
||||
// GHSA-4f49: guard here (not only next to the first buildUrl) so retries
|
||||
// and fallback URLs are validated too, before any bytes leave the host.
|
||||
@@ -1713,7 +1729,7 @@ export class BaseExecutor {
|
||||
// Distinguish timeout errors from other abort errors
|
||||
const err = error instanceof Error ? error : new Error(String(error));
|
||||
if (err.name === "TimeoutError") {
|
||||
log?.warn?.("TIMEOUT", `Fetch timeout after ${this.getTimeoutMs()}ms on ${url}`);
|
||||
log?.warn?.("TIMEOUT", `Fetch timeout after ${fetchStartTimeoutMs}ms on ${url}`);
|
||||
}
|
||||
lastError = err;
|
||||
if (!skipUpstreamRetry && urlIndex + 1 < fallbackCount) {
|
||||
|
||||
@@ -92,65 +92,19 @@ interface KieImageOptions {
|
||||
}
|
||||
|
||||
// KIE Market catalog ids are namespaced for OmniRoute's catalog
|
||||
// (`<vendor>/<model>`), but the KIE Market createTask API expects
|
||||
// (`google-imagen/<model>`), but the KIE Market createTask API expects
|
||||
// vendor-specific upstream ids that do not follow a single consistent
|
||||
// pattern. Every entry below was confirmed individually against the literal
|
||||
// example request JSON published on docs.kie.ai (never inferred by pattern —
|
||||
// see #11326's false "everything else already matches" claim and #11296's
|
||||
// follow-up correction):
|
||||
// - google-imagen: nano-banana-2 and nano-banana-pro drop the vendor
|
||||
// namespace entirely; nano-banana and nano-banana-edit use a `google/`
|
||||
// prefix instead of `google-imagen/` (docs.kie.ai/market/google/*).
|
||||
// - gpt: gpt-image-2-* drops the `gpt/` namespace entirely
|
||||
// (docs.kie.ai/market/gpt/gpt-image-2-*); gpt-image-1.5-* uses a
|
||||
// `gpt-image/` namespace instead of `gpt/gpt-image-1.5-`, and keeps the
|
||||
// dot in "1.5" (docs.kie.ai/market/gpt-image/1-5-*).
|
||||
// - seedream: 5.0-lite-* drops the ".0" — real id is `5-lite-*`
|
||||
// (docs.kie.ai/market/seedream/5-lite-text-to-image); seedream 4.5 (T2I
|
||||
// and edit) already matches byte-for-byte.
|
||||
// - flux: `flux/2-*` uses a `flux-2/` namespace (dash, not slash); the
|
||||
// generic (non-"pro") variant is named `flex` upstream, not `2`
|
||||
// (docs.kie.ai/market/flux2/pro-*, .../flex-*).
|
||||
// - wan: `wan/2.7-*` keeps the dot in our catalog, but KIE's documented
|
||||
// enum uses a dash — real id is `wan/2-7-*`
|
||||
// (docs.kie.ai/market/wan/2-7-image[-pro]).
|
||||
// - ideogram (v3-text-to-image, v3-edit, v3-remix), qwen, qwen2, and
|
||||
// grok-imagine already match byte-for-byte
|
||||
// (docs.kie.ai/market/{ideogram,qwen,qwen2,grok-imagine}/*).
|
||||
// ideogram/v3-reframe has no dedicated docs.kie.ai page as of this sweep
|
||||
// (its 3 siblings above are all direct id matches, so it is assumed
|
||||
// correct by pattern, not independently confirmed).
|
||||
// Two catalog entries remain UNRESOLVED after this sweep and are
|
||||
// deliberately left untouched pending a follow-up (see #11296 discussion):
|
||||
// - z-image/4.0-text-to-image and z-image/4.5-text-to-image: the only
|
||||
// documented Z-Image Market page (docs.kie.ai/market/z-image/z-image)
|
||||
// shows a single fixed `model` enum value `"z-image"` with no
|
||||
// version-specific id or "version" input field found — unclear whether
|
||||
// both catalog ids should collapse to the same upstream call.
|
||||
// - flux/kontext: no `docs.kie.ai/market/flux2/kontext` (or similar)
|
||||
// Market page exists; Flux Kontext is documented under the separate
|
||||
// `/flux-kontext-api/*` docs tree with its own endpoint
|
||||
// (`POST /api/v1/flux/kontext/generate`, models `flux-kontext-pro`/
|
||||
// `flux-kontext-max`), not the Market `createTask` flow this map feeds.
|
||||
// This entry may be miscatalogued as `isMarket: true` and need a
|
||||
// dedicated reroute rather than an id rewrite.
|
||||
// pattern (confirmed against docs.kie.ai/market/google/* — see #11225,
|
||||
// #11296): nano-banana-2 and nano-banana-pro drop the vendor namespace
|
||||
// entirely, while nano-banana and nano-banana-edit use a `google/` prefix
|
||||
// instead of `google-imagen/`. Every other KIE Market namespace (seedream,
|
||||
// flux, ideogram, qwen, wan, grok-imagine, gpt) already matches its real
|
||||
// upstream id byte-for-byte, so this map stays scoped to google-imagen.
|
||||
export const KIE_MARKET_UPSTREAM_MODEL_IDS: ReadonlyMap<string, string> = new Map([
|
||||
["google-imagen/nano-banana", "google/nano-banana"],
|
||||
["google-imagen/nano-banana-2", "nano-banana-2"],
|
||||
["google-imagen/nano-banana-pro", "nano-banana-pro"],
|
||||
["google-imagen/nano-banana-edit", "google/nano-banana-edit"],
|
||||
["gpt/gpt-image-2-text-to-image", "gpt-image-2-text-to-image"],
|
||||
["gpt/gpt-image-2-image-to-image", "gpt-image-2-image-to-image"],
|
||||
["gpt/gpt-image-1.5-text-to-image", "gpt-image/1.5-text-to-image"],
|
||||
["gpt/gpt-image-1.5-image-to-image", "gpt-image/1.5-image-to-image"],
|
||||
["seedream/5.0-lite-text-to-image", "seedream/5-lite-text-to-image"],
|
||||
["seedream/5.0-lite-image-to-image", "seedream/5-lite-image-to-image"],
|
||||
["flux/2-pro-text-to-image", "flux-2/pro-text-to-image"],
|
||||
["flux/2-pro-image-to-image", "flux-2/pro-image-to-image"],
|
||||
["flux/2-text-to-image", "flux-2/flex-text-to-image"],
|
||||
["flux/2-image-to-image", "flux-2/flex-image-to-image"],
|
||||
["wan/2.7-image", "wan/2-7-image"],
|
||||
["wan/2.7-image-pro", "wan/2-7-image-pro"],
|
||||
]);
|
||||
|
||||
export function resolveKieMarketUpstreamModelId(publicModelId: string): string {
|
||||
|
||||
52
open-sse/utils/fetchStartTimeoutPolicy.ts
Normal file
52
open-sse/utils/fetchStartTimeoutPolicy.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
// #11526: the fetch-start (headers-wait) phase had no ceiling comparable to a
|
||||
// real client's patience for STREAMING requests — it inherited the flat,
|
||||
// non-adaptive FETCH_TIMEOUT_MS (default 600_000ms / 10 minutes), five times
|
||||
// longer than Codex's own ~120s hard client-abort window. When an upstream
|
||||
// never returns a response at all (not even headers), OmniRoute kept the
|
||||
// connection open with nothing but keepalives, guaranteeing the client gave
|
||||
// up first with an opaque 499 instead of OmniRoute detecting the stall and
|
||||
// failing fast/over within a client-realistic window.
|
||||
//
|
||||
// This mirrors the adaptive philosophy of streamReadinessPolicy.ts's
|
||||
// resolveStreamReadinessTimeout (which already protects the BODY phase, after
|
||||
// headers arrive) but inverted: instead of bumping a small base timeout up for
|
||||
// heavy payloads, it caps an oversized base timeout down for the HEADERS
|
||||
// phase of streaming requests specifically. Non-streaming requests are left
|
||||
// on the existing flat default — providers that are legitimately slow to
|
||||
// accept a connection (but not streaming SSE) are unaffected.
|
||||
|
||||
export type FetchStartTimeoutPolicyInput = {
|
||||
baseTimeoutMs: number;
|
||||
/** Only streaming requests are capped — non-streaming keeps the flat default. */
|
||||
stream?: boolean | null;
|
||||
capMs?: number;
|
||||
};
|
||||
|
||||
export type FetchStartTimeoutPolicyResult = {
|
||||
timeoutMs: number;
|
||||
baseTimeoutMs: number;
|
||||
/** True when the base timeout was reduced by the streaming cap. */
|
||||
capped: boolean;
|
||||
};
|
||||
|
||||
// Codex's documented hard client-abort window for a stalled turn (nothing but
|
||||
// keepalives in flight) is ~120s. Keep the cap safely under that so OmniRoute's
|
||||
// own headers-phase watchdog always fires before the client gives up on its own.
|
||||
export const CODEX_CLIENT_ABORT_MS = 120_000;
|
||||
export const DEFAULT_FETCH_START_TIMEOUT_CAP_MS = 110_000;
|
||||
|
||||
export function resolveFetchStartTimeout(
|
||||
input: FetchStartTimeoutPolicyInput
|
||||
): FetchStartTimeoutPolicyResult {
|
||||
const baseTimeoutMs = Math.max(0, Math.floor(input.baseTimeoutMs || 0));
|
||||
if (baseTimeoutMs <= 0 || !input.stream) {
|
||||
return { timeoutMs: baseTimeoutMs, baseTimeoutMs, capped: false };
|
||||
}
|
||||
|
||||
const capMs = Math.max(0, Math.floor(input.capMs ?? DEFAULT_FETCH_START_TIMEOUT_CAP_MS));
|
||||
if (capMs <= 0 || baseTimeoutMs <= capMs) {
|
||||
return { timeoutMs: baseTimeoutMs, baseTimeoutMs, capped: false };
|
||||
}
|
||||
|
||||
return { timeoutMs: capMs, baseTimeoutMs, capped: true };
|
||||
}
|
||||
63
tests/unit/issue-11526-repro.test.ts
Normal file
63
tests/unit/issue-11526-repro.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { resolveStreamReadinessTimeout } from "../../open-sse/utils/streamReadinessPolicy.ts";
|
||||
import {
|
||||
resolveFetchStartTimeout,
|
||||
CODEX_CLIENT_ABORT_MS,
|
||||
} from "../../open-sse/utils/fetchStartTimeoutPolicy.ts";
|
||||
import { getUpstreamTimeoutConfig } from "../../src/shared/utils/runtimeTimeouts.ts";
|
||||
|
||||
function items(count: number): Array<{ role: string; content: string }> {
|
||||
return Array.from({ length: count }, (_, index) => ({
|
||||
role: "user",
|
||||
content: `message ${index}`,
|
||||
}));
|
||||
}
|
||||
|
||||
function tools(count: number): Array<{ type: string; name: string }> {
|
||||
return Array.from({ length: count }, (_, index) => ({ type: "function", name: `tool_${index}` }));
|
||||
}
|
||||
|
||||
test("issue #11526: body-phase readiness watchdog stays comfortably under Codex's ~120s patience for the reported tool-heavy payload shape", () => {
|
||||
const result = resolveStreamReadinessTimeout({
|
||||
baseTimeoutMs: 80_000,
|
||||
provider: "nvidia",
|
||||
model: "some-nvidia-model",
|
||||
body: { input: items(68), tools: tools(16) },
|
||||
});
|
||||
|
||||
assert.ok(
|
||||
result.timeoutMs < CODEX_CLIENT_ABORT_MS,
|
||||
`body-phase watchdog (${result.timeoutMs}ms) must stay under Codex's ~120s patience`
|
||||
);
|
||||
});
|
||||
|
||||
test("issue #11526 (fixed): headers-phase watchdog for STREAMING requests is bounded under Codex's ~120s patience", () => {
|
||||
const { fetchTimeoutMs } = getUpstreamTimeoutConfig({});
|
||||
// Default FETCH_TIMEOUT_MS (600000ms) is still the flat non-streaming baseline —
|
||||
// the fix does not touch that default, it caps how much of it a STREAMING
|
||||
// request's headers-wait phase is allowed to consume.
|
||||
assert.equal(fetchTimeoutMs, 600_000);
|
||||
|
||||
const streaming = resolveFetchStartTimeout({ baseTimeoutMs: fetchTimeoutMs, stream: true });
|
||||
assert.ok(
|
||||
streaming.timeoutMs <= CODEX_CLIENT_ABORT_MS,
|
||||
`headers-phase watchdog for streaming requests (${streaming.timeoutMs}ms) must not exceed a realistic client abort window (${CODEX_CLIENT_ABORT_MS}ms)`
|
||||
);
|
||||
assert.ok(streaming.capped, "expected the oversized default to be capped for streaming requests");
|
||||
});
|
||||
|
||||
test("issue #11526 scope guard: non-streaming requests keep the flat FETCH_TIMEOUT_MS default", () => {
|
||||
const { fetchTimeoutMs } = getUpstreamTimeoutConfig({});
|
||||
const nonStreaming = resolveFetchStartTimeout({ baseTimeoutMs: fetchTimeoutMs, stream: false });
|
||||
|
||||
assert.equal(nonStreaming.timeoutMs, fetchTimeoutMs);
|
||||
assert.equal(nonStreaming.capped, false);
|
||||
});
|
||||
|
||||
test("issue #11526 scope guard: a base timeout already under the cap is left untouched for streaming requests", () => {
|
||||
const result = resolveFetchStartTimeout({ baseTimeoutMs: 30_000, stream: true });
|
||||
|
||||
assert.equal(result.timeoutMs, 30_000);
|
||||
assert.equal(result.capped, false);
|
||||
});
|
||||
@@ -1,88 +0,0 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { mkdtempSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
|
||||
process.env.DATA_DIR = mkdtempSync(join(tmpdir(), "omniroute-kie-11296-probe-"));
|
||||
|
||||
const { handleImageGeneration } = await import(
|
||||
"../../open-sse/handlers/imageGeneration.ts"
|
||||
);
|
||||
|
||||
interface CapturedCreate {
|
||||
url: string;
|
||||
body: Record<string, unknown>;
|
||||
}
|
||||
|
||||
async function captureCreateTaskModel(publicModel: string): Promise<string> {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let captured: CapturedCreate | undefined;
|
||||
|
||||
globalThis.fetch = (async (url: unknown, options: { body?: unknown } = {}) => {
|
||||
const stringUrl = String(url);
|
||||
|
||||
if (stringUrl === "https://api.kie.ai/api/v1/jobs/createTask") {
|
||||
captured = {
|
||||
url: stringUrl,
|
||||
body: JSON.parse(String(options.body ?? "{}")) as Record<string, unknown>,
|
||||
};
|
||||
return new Response(JSON.stringify({ code: 200, data: { taskId: "kie-probe-task-1" } }), {
|
||||
status: 200,
|
||||
headers: { "content-type": "application/json" },
|
||||
});
|
||||
}
|
||||
|
||||
if (stringUrl.startsWith("https://api.kie.ai/api/v1/jobs/recordInfo")) {
|
||||
return new Response(
|
||||
JSON.stringify({
|
||||
code: 200,
|
||||
data: {
|
||||
state: "success",
|
||||
resultJson: JSON.stringify({ resultUrls: ["https://example.com/kie-probe.png"] }),
|
||||
},
|
||||
}),
|
||||
{ status: 200, headers: { "content-type": "application/json" } }
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(`Unexpected URL: ${stringUrl}`);
|
||||
}) as typeof globalThis.fetch;
|
||||
|
||||
try {
|
||||
await handleImageGeneration({
|
||||
body: { model: publicModel, prompt: "probe prompt", size: "1024x1024", n: 1 },
|
||||
credentials: { apiKey: "test-kie-key" },
|
||||
log: null,
|
||||
});
|
||||
assert.ok(captured, "expected a createTask request to be captured");
|
||||
return String(captured.body.model);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
}
|
||||
|
||||
test("#11296: GPT Image 2 T2I sends KIE's real upstream id (no namespace prefix)", async () => {
|
||||
const sentModel = await captureCreateTaskModel("kie/gpt/gpt-image-2-text-to-image");
|
||||
assert.equal(sentModel, "gpt-image-2-text-to-image");
|
||||
});
|
||||
|
||||
test("#11296: GPT Image 2 I2I sends KIE's real upstream id (no namespace prefix)", async () => {
|
||||
const sentModel = await captureCreateTaskModel("kie/gpt/gpt-image-2-image-to-image");
|
||||
assert.equal(sentModel, "gpt-image-2-image-to-image");
|
||||
});
|
||||
|
||||
test("#11296: GPT Image 1.5 T2I sends KIE's real 'gpt-image/' namespace", async () => {
|
||||
const sentModel = await captureCreateTaskModel("kie/gpt/gpt-image-1.5-text-to-image");
|
||||
assert.equal(sentModel, "gpt-image/1.5-text-to-image");
|
||||
});
|
||||
|
||||
test("#11296: Seedream 5.0 Lite T2I sends KIE's real id without the '.0'", async () => {
|
||||
const sentModel = await captureCreateTaskModel("kie/seedream/5.0-lite-text-to-image");
|
||||
assert.equal(sentModel, "seedream/5-lite-text-to-image");
|
||||
});
|
||||
|
||||
test("#11296: Flux 2 Pro T2I sends KIE's real 'flux-2/' namespace (dash, not slash)", async () => {
|
||||
const sentModel = await captureCreateTaskModel("kie/flux/2-pro-text-to-image");
|
||||
assert.equal(sentModel, "flux-2/pro-text-to-image");
|
||||
});
|
||||
@@ -103,21 +103,13 @@ function resolveLiveKieMarketCatalog() {
|
||||
}));
|
||||
}
|
||||
|
||||
test("KIE Market resolver changes exactly the documented mismatched ids in the live market catalog", () => {
|
||||
test("KIE Market resolver changes exactly the 4 google-imagen ids in the live market catalog", () => {
|
||||
const roundTrips = resolveLiveKieMarketCatalog();
|
||||
const changed = roundTrips.filter(({ publicModelId, upstreamModelId }) => {
|
||||
return upstreamModelId !== publicModelId;
|
||||
});
|
||||
|
||||
assert.deepEqual(changed, [
|
||||
{
|
||||
publicModelId: "seedream/5.0-lite-text-to-image",
|
||||
upstreamModelId: "seedream/5-lite-text-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "seedream/5.0-lite-image-to-image",
|
||||
upstreamModelId: "seedream/5-lite-image-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "google-imagen/nano-banana-2",
|
||||
upstreamModelId: "nano-banana-2",
|
||||
@@ -134,71 +126,19 @@ test("KIE Market resolver changes exactly the documented mismatched ids in the l
|
||||
publicModelId: "google-imagen/nano-banana-edit",
|
||||
upstreamModelId: "google/nano-banana-edit",
|
||||
},
|
||||
{
|
||||
publicModelId: "flux/2-pro-image-to-image",
|
||||
upstreamModelId: "flux-2/pro-image-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "flux/2-pro-text-to-image",
|
||||
upstreamModelId: "flux-2/pro-text-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "flux/2-image-to-image",
|
||||
upstreamModelId: "flux-2/flex-image-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "flux/2-text-to-image",
|
||||
upstreamModelId: "flux-2/flex-text-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "gpt/gpt-image-1.5-text-to-image",
|
||||
upstreamModelId: "gpt-image/1.5-text-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "gpt/gpt-image-1.5-image-to-image",
|
||||
upstreamModelId: "gpt-image/1.5-image-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "gpt/gpt-image-2-text-to-image",
|
||||
upstreamModelId: "gpt-image-2-text-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "gpt/gpt-image-2-image-to-image",
|
||||
upstreamModelId: "gpt-image-2-image-to-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "wan/2.7-image",
|
||||
upstreamModelId: "wan/2-7-image",
|
||||
},
|
||||
{
|
||||
publicModelId: "wan/2.7-image-pro",
|
||||
upstreamModelId: "wan/2-7-image-pro",
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
const REWRITTEN_MARKET_IDS = new Set([
|
||||
const REWRITTEN_GOOGLE_IMAGEN_MARKET_IDS = new Set([
|
||||
"google-imagen/nano-banana",
|
||||
"google-imagen/nano-banana-2",
|
||||
"google-imagen/nano-banana-pro",
|
||||
"google-imagen/nano-banana-edit",
|
||||
"gpt/gpt-image-2-text-to-image",
|
||||
"gpt/gpt-image-2-image-to-image",
|
||||
"gpt/gpt-image-1.5-text-to-image",
|
||||
"gpt/gpt-image-1.5-image-to-image",
|
||||
"seedream/5.0-lite-text-to-image",
|
||||
"seedream/5.0-lite-image-to-image",
|
||||
"flux/2-pro-text-to-image",
|
||||
"flux/2-pro-image-to-image",
|
||||
"flux/2-text-to-image",
|
||||
"flux/2-image-to-image",
|
||||
"wan/2.7-image",
|
||||
"wan/2.7-image-pro",
|
||||
]);
|
||||
|
||||
test("KIE Market resolver preserves every other live market catalog id byte-identically", () => {
|
||||
for (const { publicModelId, upstreamModelId } of resolveLiveKieMarketCatalog()) {
|
||||
if (!REWRITTEN_MARKET_IDS.has(publicModelId)) {
|
||||
if (!REWRITTEN_GOOGLE_IMAGEN_MARKET_IDS.has(publicModelId)) {
|
||||
assert.equal(
|
||||
upstreamModelId,
|
||||
publicModelId,
|
||||
@@ -208,8 +148,8 @@ test("KIE Market resolver preserves every other live market catalog id byte-iden
|
||||
}
|
||||
});
|
||||
|
||||
test("KIE Market resolver keeps exactly the explicit upstream id mappings (#11296)", () => {
|
||||
assert.equal(KIE_MARKET_UPSTREAM_MODEL_IDS.size, 16);
|
||||
test("KIE Market resolver keeps exactly the explicit google-imagen upstream id mappings (#11296)", () => {
|
||||
assert.equal(KIE_MARKET_UPSTREAM_MODEL_IDS.size, 4);
|
||||
});
|
||||
|
||||
test("KIE Market resolver passes an unknown namespaced id through byte-identically", () => {
|
||||
@@ -269,78 +209,6 @@ test("KIE Market createTask sends the KIE upstream id for Nano Banana Edit (#112
|
||||
);
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the unprefixed upstream id for GPT Image 2 T2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/gpt/gpt-image-2-text-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "gpt-image-2-text-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the unprefixed upstream id for GPT Image 2 I2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/gpt/gpt-image-2-image-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "gpt-image-2-image-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the 'gpt-image/' namespace for GPT Image 1.5 T2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/gpt/gpt-image-1.5-text-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "gpt-image/1.5-text-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the 'gpt-image/' namespace for GPT Image 1.5 I2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/gpt/gpt-image-1.5-image-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "gpt-image/1.5-image-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask drops the '.0' for Seedream 5.0 Lite T2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/seedream/5.0-lite-text-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "seedream/5-lite-text-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask drops the '.0' for Seedream 5.0 Lite I2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/seedream/5.0-lite-image-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "seedream/5-lite-image-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the 'flux-2/' namespace for Flux 2 Pro T2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/flux/2-pro-text-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "flux-2/pro-text-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the 'flux-2/' namespace for Flux 2 Pro I2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/flux/2-pro-image-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "flux-2/pro-image-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the 'flux-2/flex-' name for Flux 2 T2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/flux/2-text-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "flux-2/flex-text-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the 'flux-2/flex-' name for Flux 2 I2I (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/flux/2-image-to-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "flux-2/flex-image-to-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the dash-separated id for Wan 2.7 Image (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/wan/2.7-image");
|
||||
|
||||
assert.equal(captured.create.body.model, "wan/2-7-image");
|
||||
});
|
||||
|
||||
test("KIE Market createTask sends the dash-separated id for Wan 2.7 Image Pro (#11296)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/wan/2.7-image-pro");
|
||||
|
||||
assert.equal(captured.create.body.model, "wan/2-7-image-pro");
|
||||
});
|
||||
|
||||
test("KIE Market createTask leaves genuinely namespaced upstream ids untouched (#11225 control)", async () => {
|
||||
const captured = await runKieMarketGeneration("kie/seedream/4.5-text-to-image");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user