mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
fix(media-use): accept renamed codex image_generation feature flag
The codex image provider's fail-fast gate required an `imagegenext` row in
`codex features list`. Codex CLI 0.145 renamed that flag to `image_generation`
and dropped the old row, so the gate rejected every up-to-date CLI:
media-use: codex image upsell unavailable: codex imagegenext unavailable
(upgrade Codex CLI)
`resolve --type image --provider codex` returned that without ever attempting
a render, on hosts where image generation works fine. Codex itself now warns
`[features].imagegenext is deprecated. Use [features].image_generation`.
Resolve the flag name from the feature list instead of hardcoding it, and pass
whichever name the installed CLI exposes to `--enable`. The old name is
preferred when present, since CLIs that still list it reject the new one.
`codexUnavailableReason()` now returns `{ flag }` / `{ reason }` so the
resolved name reaches the exec.
Verified against codex-cli 0.145.0: `resolve --type image --provider codex`
generates and freezes an image. New unit tests pin both flag layouts so a
future rename fails loudly instead of silently disabling the provider.
This commit is contained in:
@@ -8,9 +8,15 @@ import { join } from "node:path";
|
|||||||
// no key (CLI-only). The image UPSELL behind local mflux; skipped by --local-only.
|
// no key (CLI-only). The image UPSELL behind local mflux; skipped by --local-only.
|
||||||
//
|
//
|
||||||
// Retrieval mirrors illo-skill rather than trusting the model to save a file:
|
// Retrieval mirrors illo-skill rather than trusting the model to save a file:
|
||||||
// `--enable imagegenext` makes the built-in tool drop the rendered artifact into
|
// `--enable image_generation` makes the built-in tool drop the rendered artifact
|
||||||
// $CODEX_HOME/generated_images/, and we fetch the freshest file that postdates
|
// into $CODEX_HOME/generated_images/, and we fetch the freshest file that
|
||||||
// this run. The save-to-path instruction is only a best-effort verify-first.
|
// postdates this run. The save-to-path instruction is only a best-effort
|
||||||
|
// verify-first.
|
||||||
|
//
|
||||||
|
// The flag was called `imagegenext` until Codex CLI ~0.145, which renamed it to
|
||||||
|
// `image_generation` and dropped the old row from `codex features list`. Both
|
||||||
|
// the gate below and the --enable arg track the new name, with a fallback to
|
||||||
|
// the old one so older CLIs keep working.
|
||||||
|
|
||||||
const TIMEOUT_MS = 600000; // codex exec round-trips the sub; first-run tool spin-up is slow
|
const TIMEOUT_MS = 600000; // codex exec round-trips the sub; first-run tool spin-up is slow
|
||||||
const MTIME_SKEW_MS = 2000; // tolerate mtime granularity / clock skew (illo uses 2s)
|
const MTIME_SKEW_MS = 2000; // tolerate mtime granularity / clock skew (illo uses 2s)
|
||||||
@@ -53,10 +59,19 @@ function codexRun(args) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Name of the feature flag this CLI uses for the extended image tool, or null if
|
||||||
|
// it exposes neither. Newer CLIs list only `image_generation`; older ones list
|
||||||
|
// `imagegenext` alongside it and reject the new name on --enable.
|
||||||
|
export function imageGenFlag(feats) {
|
||||||
|
if (/\bimagegenext\b/.test(feats)) return "imagegenext";
|
||||||
|
if (/\bimage_generation\b/.test(feats)) return "image_generation";
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// Fail-fast host check (mirrors illo): don't burn a minutes-long exec when Codex
|
// Fail-fast host check (mirrors illo): don't burn a minutes-long exec when Codex
|
||||||
// isn't usable. Returns null when ready, else a human reason. imagegenext ships
|
// isn't usable. Returns { flag } when ready, else { reason }. The flag ships
|
||||||
// default-disabled ("under development"), so we check the ROW is present (the
|
// default-disabled, so we check the ROW is present (the capability signal) — the
|
||||||
// capability signal) — the exec enables it per-render with --enable.
|
// exec enables it per-render with --enable.
|
||||||
function codexUnavailableReason() {
|
function codexUnavailableReason() {
|
||||||
try {
|
try {
|
||||||
const which = process.platform === "win32" ? "where" : "which";
|
const which = process.platform === "win32" ? "where" : "which";
|
||||||
@@ -65,7 +80,10 @@ function codexUnavailableReason() {
|
|||||||
// A shell alias (e.g. `codex → /Applications/Codex.app/...`) is NOT enough:
|
// A shell alias (e.g. `codex → /Applications/Codex.app/...`) is NOT enough:
|
||||||
// aliases live only in the interactive shell, so a spawned subprocess's PATH
|
// aliases live only in the interactive shell, so a spawned subprocess's PATH
|
||||||
// lookup can't see them. Symlink the real binary onto PATH.
|
// lookup can't see them. Symlink the real binary onto PATH.
|
||||||
return 'codex CLI not reachable on PATH (a shell alias won\'t work — spawned processes can\'t see aliases; symlink the real binary onto PATH, e.g. ln -s "$(readlink -f "$(command -v codex)")" ~/.local/bin/codex)';
|
return {
|
||||||
|
reason:
|
||||||
|
'codex CLI not reachable on PATH (a shell alias won\'t work — spawned processes can\'t see aliases; symlink the real binary onto PATH, e.g. ln -s "$(readlink -f "$(command -v codex)")" ~/.local/bin/codex)',
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// Auth marker: presence of the credentials file, NOT `codex login status`.
|
// Auth marker: presence of the credentials file, NOT `codex login status`.
|
||||||
// That command prints "Logged in using ChatGPT" only to a human stream
|
// That command prints "Logged in using ChatGPT" only to a human stream
|
||||||
@@ -75,18 +93,18 @@ function codexUnavailableReason() {
|
|||||||
// auth.json is the durable, TTY-independent signal; token validity is proven
|
// auth.json is the durable, TTY-independent signal; token validity is proven
|
||||||
// by the exec itself, which fails cleanly if the login is stale.
|
// by the exec itself, which fails cleanly if the login is stale.
|
||||||
const authPath = join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
const authPath = join(process.env.CODEX_HOME || join(homedir(), ".codex"), "auth.json");
|
||||||
if (!existsSync(authPath)) return "codex not logged in (run: codex login)";
|
if (!existsSync(authPath)) return { reason: "codex not logged in (run: codex login)" };
|
||||||
const feats = codexRun(["features", "list"]);
|
const feats = codexRun(["features", "list"]);
|
||||||
if (feats == null) return "could not read `codex features list`";
|
if (feats == null) return { reason: "could not read `codex features list`" };
|
||||||
if (!/\bimage_generation\b/.test(feats)) return "codex image_generation feature unavailable";
|
const flag = imageGenFlag(feats);
|
||||||
if (!/\bimagegenext\b/.test(feats)) return "codex imagegenext unavailable (upgrade Codex CLI)";
|
if (!flag) return { reason: "codex image generation feature unavailable (upgrade Codex CLI)" };
|
||||||
return null;
|
return { flag };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function codexImageGenerate(intent) {
|
export async function codexImageGenerate(intent) {
|
||||||
const unavailable = codexUnavailableReason();
|
const { reason, flag } = codexUnavailableReason();
|
||||||
if (unavailable) {
|
if (reason) {
|
||||||
console.error(`media-use: codex image upsell unavailable: ${unavailable}`);
|
console.error(`media-use: codex image upsell unavailable: ${reason}`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const outPath = join(tmpdir(), `media-use-codex-${process.pid}-${Date.now()}.png`);
|
const outPath = join(tmpdir(), `media-use-codex-${process.pid}-${Date.now()}.png`);
|
||||||
@@ -112,7 +130,7 @@ export async function codexImageGenerate(intent) {
|
|||||||
"workspace-write",
|
"workspace-write",
|
||||||
"--skip-git-repo-check",
|
"--skip-git-repo-check",
|
||||||
"--enable",
|
"--enable",
|
||||||
"imagegenext",
|
flag,
|
||||||
"-",
|
"-",
|
||||||
],
|
],
|
||||||
{ input: prompt, encoding: "utf8", timeout: TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] },
|
{ input: prompt, encoding: "utf8", timeout: TIMEOUT_MS, stdio: ["pipe", "pipe", "pipe"] },
|
||||||
@@ -123,7 +141,7 @@ export async function codexImageGenerate(intent) {
|
|||||||
);
|
);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Verify-first (save-to-path may have worked), else fetch the imagegenext artifact.
|
// Verify-first (save-to-path may have worked), else fetch the image-tool artifact.
|
||||||
const produced =
|
const produced =
|
||||||
existsSync(outPath) && statSync(outPath).size > 0 ? outPath : freshestGeneratedImage(started);
|
existsSync(outPath) && statSync(outPath).size > 0 ? outPath : freshestGeneratedImage(started);
|
||||||
if (!produced) return null;
|
if (!produced) return null;
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import test from "node:test";
|
||||||
|
import assert from "node:assert";
|
||||||
|
import { imageGenFlag } from "./codex-provider.mjs";
|
||||||
|
|
||||||
|
// Real `codex features list` output is three space-padded columns:
|
||||||
|
// <name> <stage> <enabled>
|
||||||
|
const OLD_CLI = [
|
||||||
|
"image_detail_original removed false",
|
||||||
|
"image_generation stable true",
|
||||||
|
"imagegenext under development false",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
const NEW_CLI = [
|
||||||
|
"image_detail_original removed false",
|
||||||
|
"image_generation stable true",
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
test("older CLIs keep the old flag — they reject --enable image_generation", () => {
|
||||||
|
assert.equal(imageGenFlag(OLD_CLI), "imagegenext");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("Codex CLI >=0.145 dropped the imagegenext row; fall back to its new name", () => {
|
||||||
|
// The regression this guards: gating on `imagegenext` alone made the provider
|
||||||
|
// report "unavailable" on every up-to-date CLI, without attempting a render.
|
||||||
|
assert.equal(imageGenFlag(NEW_CLI), "image_generation");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("no image feature at all is unavailable, not a silent default", () => {
|
||||||
|
assert.equal(imageGenFlag("apps stable true\nhooks stable true"), null);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("substring lookalikes do not count as the feature row", () => {
|
||||||
|
assert.equal(imageGenFlag("image_generation_v2 stable true"), null);
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user