Repository navigation
feat(media-use): reach HeyGen through a host app's gateway (HEYGEN_API_BASE) - #5181
Conversation
…I_BASE) - The audio engine sends every HeyGen call to $HEYGEN_API_BASE when set, as the heygen CLI does; plain HTTP needs $HEYGEN_ALLOW_HTTP=1, the CLI's own rule. A host gateway (that base with its own key) wins over a host OAuth token. - The skill says: inside a host that gives HeyGen access, no CLI install or sign-in, prefer the host's own HeyGen tools, relay a refused call's message as written and do not switch providers on your own. HyperFrames Desktop sets these variables when a HeyGen API key is saved in its Settings, so media-use's TTS, catalog search and avatar calls are paid by that key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Preview deployment for your docs. Learn more about Mintlify Previews.
💡 Tip: Enable Automations to automatically generate PRs for you. |
…manifest Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jrusso1020
left a comment
There was a problem hiding this comment.
Approved at 0668a9f3.
What I checked
Base handling (heygen.mjs).
- With
HEYGEN_API_BASEunset or blank,heygenBase()returns the publichttps://api.heygen.com/v3. - A set base has its trailing slashes stripped and gets
/v3appended. That matches a gateway that serves/v3/...at its origin. - Plain HTTP throws unless
HEYGEN_ALLOW_HTTP=1. heygenJSONis the only place that builds a request URL.tts.mjsgoes through it viadeps.heygenJSON ?? heygenJSON. Nothing else in media-use still readsHEYGEN_BASEdirectly.
Credential precedence.
hostGatewayKey()wins only when bothHEYGEN_API_BASEandHEYGEN_API_KEYare set, and then sendsX-Api-Key.- A host that blanks
HEYGEN_ACCESS_TOKENto""falls through correctly, becauseif (accessToken)is falsy. - With no base set, the old order is unchanged.
Manifest. Skills: manifest in sync passes, so the three hashes match the content. Skills: project-native lint + mirror and Test: skills pass too.
Tests. node --test skills/media-use/audio/scripts/lib/heygen.test.mjs passes 14 of 14 locally. No check is failing at this head.
Non-blocking
- Stored credentials follow a custom base. When
HEYGEN_API_BASEis set butHEYGEN_API_KEYis not,resolveCredentialfalls through to the OAuth token,.envor~/.heygen/credentials, and those go to whatever host the base names. That matches the heygen CLI, and anyone who can set the env already controls the run. Still, the SKILL now teaches agents that a set base means "host access", so this case is easier to hit by accident. A cheap guard: only send stored credentials when the base's host ends in.heygen.com, and otherwise require the host's ownHEYGEN_API_KEY. - A gateway may not forward every route media-use calls.
heygen-voice.mjsusesPOST /voices/cloneandDELETE /voices/{id}. A host that forwards only an allowlist of generation routes will refuse those.setup-providers.mdsaysresolve"voice" and TTS "all go through the host". A line saying a host may refuse some routes, and that the refusal should be relayed as written, would keep agents from treating it as a key problem. - Naming. This is the first time the public repo names the Desktop app (in
SKILL.md,setup-providers.mdand theheygen.mjscomment). That's fine if it's public. If not, "a host app" already carries the meaning.
— Rames
… HeyGen credentials stay on HeyGen hosts - The "use the host's own tools first / no CLI sign-in" guidance is now conditional on HEYGEN_API_BASE alone, so an agent outside such a host (Claude Code with a HeyGen MCP connector, say) keeps today's media-use journey. The compressed copies in README, CLAUDE.md, the prompting overview, general-video and the capability menu are back to main's wording. - A base outside heygen.com gets no stored or host OAuth credential, only a key set for it (review on #5181). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks Rames. At
media-use tests 117/117, lint-skills, mirror and manifest pass. 🤖 Addressed by Claude Code |
jrusso1020
left a comment
There was a problem hiding this comment.
Requesting changes at 7d385440c: one path still sends a person's own key to a host that isn't HeyGen.
Both follow-ups landed as described:
- Host-only guidance: the "use the host's tools, no CLI sign-in" guidance now applies only when
HEYGEN_API_BASEis set, and the compressed copies are back to main's wording. - Credentials and non-HeyGen bases:
resolveCredentialnow refuses the access token, the env key and the stored credentials file when the base isn'theygen.comor*.heygen.com.new URL(...).hostnameplusendsWith(".heygen.com")rules out lookalike hosts such asevilheygen.com, and the new test covers both sides.
Blocker: a project .env can set HEYGEN_API_BASE, and the gateway branch then sends the shell's HEYGEN_API_KEY to that host
hostGatewayKey() runs before the new heygenOwnBase() check, and it pairs any HEYGEN_API_BASE with any HEYGEN_API_KEY. The two values don't have to come from the same place. loadEnvFromDir copies a nearby .env into process.env. It is called from process.cwd() in heygen-tts.mjs and heygen-voice.mjs, and from the project directory in audio.mjs. So a cloned project whose .env holds only HEYGEN_API_BASE=https://proxy.example.com redirects the calls, and the person's own key from their shell rides along.
I reproduced it against this head: shell HEYGEN_API_KEY=hg_shell_real, a project .env with only that base line, then loadEnvFromDir(project):
base https://proxy.example.com/v3
cred {"headers":{"X-Api-Key":"hg_shell_real"}}
Before this PR, a .env couldn't move the base at all, so this path is new. It also contradicts the commit's own rule that "HeyGen credentials stay on HeyGen hosts".
The smallest fix: HEYGEN_API_BASE and HEYGEN_ALLOW_HTTP describe the host process, and a host app sets them in the environment it spawns, never in a project file. So loadEnvFromDir should skip those two names, and a test should pin it: a .env containing them leaves heygenBase() at https://api.heygen.com/v3. A .env that sets both base and key would then send its own key to its own host, which is harmless.
Verified
node --test skills/media-use/audio/scripts/lib/heygen.test.mjspasses 15 of 15.- PR checks: 26 pass and the rest are skipped by path filters.
I'll re-approve as soon as that lands.
— Rames
…HTTP loadEnvFromDir copied HEYGEN_API_BASE from a project file into process.env, and the host gateway path then sent the person's shell HEYGEN_API_KEY to that host. A host app sets HEYGEN_API_BASE and HEYGEN_ALLOW_HTTP in the environment it spawns, never in a project file, so the .env loader skips both. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks Rames, fixed at
|
jrusso1020
left a comment
There was a problem hiding this comment.
Approved at f3b411f87. My changes request at 7d385440c is resolved.
loadEnvFromDirnow skipsHEYGEN_API_BASEandHEYGEN_ALLOW_HTTP(HOST_ONLY), so a project's.envcan no longer choose where the shell'sHEYGEN_API_KEYgoes. Every other key still loads, and the shell still wins.- I re-ran my original repro: a project
.envholding onlyHEYGEN_API_BASE=https://proxy.example.com, withHEYGEN_API_KEY=hg_shell_realin the shell. It now givesheygenBase()=https://api.heygen.com/v3andresolveCredential()=null. Before the fix it sent the shell key to the proxy. - The new test fails on
7d385440c(15 of 16) and passes here (16 of 16 inheygen.test.mjs). heygen.mjsis still the only code that readsHEYGEN_API_BASE. The other mentions are in docs.- CI at this head has no failures. Everything is either success or skipped by path.
— Rames
What changed
media-use can reach HeyGen through a host app's gateway, the way the heygen CLI already can.
skills/media-use/audio/scripts/lib/heygen.mjs: every request goes to$HEYGEN_API_BASEwhen it is set(
heygenBase()), else HeyGen's public API. Plain HTTP needs$HEYGEN_ALLOW_HTTP=1, the CLI's own rule. A hostgateway (that base with its own
$HEYGEN_API_KEY) wins over a host OAuth$HEYGEN_ACCESS_TOKEN, since the hostpays for every call through it.
SKILL.mdandreferences/setup-providers.md: only whenHEYGEN_API_BASEis set, no CLI install or sign-inand no OAuth-allowance pitch, prefer that host's own HeyGen tools, relay a refused call's message as written and stop.
Without
HEYGEN_API_BASEevery instruction reads as on main, so an agent outside such a host (Claude Code with aHeyGen MCP connector, Codex, a plain terminal) keeps today's journey. The compressed copies in
CLAUDE.md,README.md, the prompting overview, general-video and the capability menu are unchanged from main.heygen.comgets no stored or host OAuth credential, only a key set for it.Why: HyperFrames Desktop saves a HeyGen API key in its Settings and hands each agent run a loopback gateway and a
token for it (the key never enters the agent's environment). The heygen CLI honours
HEYGEN_API_BASE, soresolvealready went through it; the audio engine's TTS called
https://api.heygen.com/v3directly with the token and failed.Desktop side: https://github.com/heygen-com/hyperframes-internal/pull/3077 · walkthrough board: https://www.heygenverse.com/a/13eab341-1d25-4955-9a08-f6e6112e580e
What I measured
node --test skills/media-use/audio/scripts/lib/*.test.mjs heygen-tts.test.mjs heygen-voice.test.mjs: 117 pass, 0fail (112 on main; the five new tests fail on main's code). They pin the default base, a canary base, the HTTP rule,
the gateway's precedence over a host OAuth token, a request reaching a local base with the host's key, and no
stored credential leaving for a non-HeyGen base.
HEYGEN_CONFIG_DIR:resolve --type bgm|sfx|image|icon|voicereached/v3/audio/sounds,/v3/assets/search,/v3/voicesand/v3/voices/speechon the stand-in with the host's key;heygen-tts.mjs --listmade 0 calls there before this change.scripts/lint-skills.ts(33 skill files, 354 markdown files),check-skill-mirror, oxlint and oxfmt on the changedfiles pass.
What I did NOT exercise
🤖 Generated with Claude Code