mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-14 10:52:17 +03:00
fix(codex): fail over image generation for imported free plans (#11948)
Boarded with #11954/#11953/#11951/#11952 in one combined worktree: typecheck:core, check:file-size, check:changelog-integrity, check:complexity, check:cognitive-complexity, check:cycles all green; 85/85 focused tests pass. Verified both halves of the gap directly: isCodexFreePlan() (open-sse/executors/codex/tools.ts) only checks workspacePlanType, while codexImport.ts normalizes the JWT plan into providerSpecificData.chatgptPlanType — confirmed imported free-plan accounts would bypass the existing guard. Thanks for tracing the full import-to-guard path.
This commit is contained in:
@@ -26,7 +26,14 @@ export const CODEX_HOSTED_TOOL_TYPES: ReadonlySet<string> = new Set([
|
||||
// dropped for free-plan accounts (mirrors CLIProxyAPI's isCodexFreePlanAuth).
|
||||
export function isCodexFreePlan(providerSpecificData: unknown): boolean {
|
||||
if (!providerSpecificData || typeof providerSpecificData !== "object") return false;
|
||||
const plan = (providerSpecificData as { workspacePlanType?: unknown }).workspacePlanType;
|
||||
const data = providerSpecificData as {
|
||||
workspacePlanType?: unknown;
|
||||
chatgptPlanType?: unknown;
|
||||
};
|
||||
const plan =
|
||||
typeof data.workspacePlanType === "string"
|
||||
? data.workspacePlanType
|
||||
: data.chatgptPlanType;
|
||||
return typeof plan === "string" && plan.trim().toLowerCase() === "free";
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ import { getAntigravityEnvelopeUserAgent } from "../services/antigravityIdentity
|
||||
import { kieExecutor } from "../executors/kie.ts";
|
||||
import { mapImageSize } from "../translator/image/sizeMapper.ts";
|
||||
import { getCodexClientVersion, getCodexUserAgent } from "../config/codexClient.ts";
|
||||
import { isCodexFreePlan } from "../executors/codex/tools.ts";
|
||||
import { saveCallLog } from "@/lib/usageDb";
|
||||
import { sleep } from "../utils/sleep.ts";
|
||||
import {
|
||||
@@ -2480,6 +2481,18 @@ async function handleCodexImageGeneration({
|
||||
});
|
||||
}
|
||||
|
||||
if (isCodexFreePlan(credentials?.providerSpecificData)) {
|
||||
return saveImageErrorResult({
|
||||
provider,
|
||||
model,
|
||||
status: 403,
|
||||
startTime,
|
||||
error: "Codex image_generation is unavailable on free-plan accounts",
|
||||
path: logPath,
|
||||
retryable: true,
|
||||
});
|
||||
}
|
||||
|
||||
const workspaceId =
|
||||
credentials?.providerSpecificData &&
|
||||
typeof credentials.providerSpecificData === "object" &&
|
||||
|
||||
@@ -12,6 +12,9 @@ const { normalizeCodexTools, isCodexFreePlan } = await import("../../open-sse/ex
|
||||
test("isCodexFreePlan detects workspacePlanType === 'free' (case-insensitive)", () => {
|
||||
assert.equal(isCodexFreePlan({ workspacePlanType: "free" }), true);
|
||||
assert.equal(isCodexFreePlan({ workspacePlanType: "FREE" }), true);
|
||||
assert.equal(isCodexFreePlan({ chatgptPlanType: "free" }), true);
|
||||
assert.equal(isCodexFreePlan({ chatgptPlanType: "FREE" }), true);
|
||||
assert.equal(isCodexFreePlan({ workspacePlanType: "team", chatgptPlanType: "free" }), false);
|
||||
assert.equal(isCodexFreePlan({ workspacePlanType: "team" }), false);
|
||||
assert.equal(isCodexFreePlan({ workspacePlanType: "" }), false);
|
||||
assert.equal(isCodexFreePlan({}), false);
|
||||
|
||||
@@ -2028,6 +2028,32 @@ test("handleImageGeneration (codex) forwards size and maps GPT-Image quality to
|
||||
}
|
||||
});
|
||||
|
||||
test("handleImageGeneration (codex) skips imported free-plan accounts and marks them retryable", async () => {
|
||||
const originalFetch = globalThis.fetch;
|
||||
let fetchCalls = 0;
|
||||
globalThis.fetch = async () => {
|
||||
fetchCalls += 1;
|
||||
throw new Error("free-plan image request should not reach upstream");
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await handleImageGeneration({
|
||||
body: { model: "codex/gpt-5.6-terra", prompt: "kitten" },
|
||||
credentials: {
|
||||
accessToken: "codex-token",
|
||||
providerSpecificData: { chatgptPlanType: "free" },
|
||||
},
|
||||
log: null,
|
||||
});
|
||||
assert.equal(result.success, false);
|
||||
assert.equal(result.status, 403);
|
||||
assert.equal(result.retryable, true);
|
||||
assert.equal(fetchCalls, 0);
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
});
|
||||
|
||||
// #8307 — some ChatGPT accounts can run Codex but lack entitlement for the specific
|
||||
// requested image model, and the upstream 400 for that exact case is retryable on a
|
||||
// sibling account: executeImageWithCredentialFallback (route.ts) already retries on
|
||||
|
||||
Reference in New Issue
Block a user