Skip to content

feat(media-use): reach HeyGen through a host app's gateway (HEYGEN_API_BASE) - #5181

Merged
Vitozhu04 merged 4 commits into
mainfrom
wenbo/media-use-host-heygen-gateway
Oct 7, 2026
Merged

Vitozhu04 merged 4 commits into
mainfrom
wenbo/media-use-host-heygen-gateway

Conversation

@Vitozhu04

@Vitozhu04 Vitozhu04 commented Oct 7, 2026 •

Copy link
Copy Markdown
Collaborator

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_BASE when it is set
    (heygenBase()), else HeyGen's public API. Plain HTTP needs $HEYGEN_ALLOW_HTTP=1, the CLI's own rule. A host
    gateway (that base with its own $HEYGEN_API_KEY) wins over a host OAuth $HEYGEN_ACCESS_TOKEN, since the host
    pays for every call through it.
  • SKILL.md and references/setup-providers.md: only when HEYGEN_API_BASE is set, no CLI install or sign-in
    and no OAuth-allowance pitch, prefer that host's own HeyGen tools, relay a refused call's message as written and stop.
    Without HEYGEN_API_BASE every instruction reads as on main, so an agent outside such a host (Claude Code with a
    HeyGen 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.
  • A base outside heygen.com gets 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, so resolve
already went through it; the audio engine's TTS called https://api.heygen.com/v3 directly 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, 0
    fail (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.
  • Through a logging stand-in on a clean HEYGEN_CONFIG_DIR: resolve --type bgm|sfx|image|icon|voice reached
    /v3/audio/sounds, /v3/assets/search, /v3/voices and /v3/voices/speech on the stand-in with the host's key;
    heygen-tts.mjs --list made 0 calls there before this change.
  • scripts/lint-skills.ts (33 skill files, 354 markdown files), check-skill-mirror, oxlint and oxfmt on the changed
    files pass.

What I did NOT exercise

  • Windows and Linux.
  • A real Desktop release consuming this: the desktop pin bump follows this PR.

🤖 Generated with Claude Code

…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>
@mintlify

mintlify Bot commented Oct 7, 2026 •

Copy link
Copy Markdown

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
hyperframes 🟢 Ready View Preview Oct 7, 2026, 3:05 PM

💡 Tip: Enable Automations to automatically generate PRs for you.

…manifest

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at 0668a9f3.

What I checked

Base handling (heygen.mjs).

  • With HEYGEN_API_BASE unset or blank, heygenBase() returns the public https://api.heygen.com/v3.
  • A set base has its trailing slashes stripped and gets /v3 appended. That matches a gateway that serves /v3/... at its origin.
  • Plain HTTP throws unless HEYGEN_ALLOW_HTTP=1.
  • heygenJSON is the only place that builds a request URL. tts.mjs goes through it via deps.heygenJSON ?? heygenJSON. Nothing else in media-use still reads HEYGEN_BASE directly.

Credential precedence.

  • hostGatewayKey() wins only when both HEYGEN_API_BASE and HEYGEN_API_KEY are set, and then sends X-Api-Key.
  • A host that blanks HEYGEN_ACCESS_TOKEN to "" falls through correctly, because if (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

  1. Stored credentials follow a custom base. When HEYGEN_API_BASE is set but HEYGEN_API_KEY is not, resolveCredential falls through to the OAuth token, .env or ~/.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 own HEYGEN_API_KEY.
  2. A gateway may not forward every route media-use calls. heygen-voice.mjs uses POST /voices/clone and DELETE /voices/{id}. A host that forwards only an allowlist of generation routes will refuse those. setup-providers.md says resolve "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.
  3. Naming. This is the first time the public repo names the Desktop app (in SKILL.md, setup-providers.md and the heygen.mjs comment). 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>
@Vitozhu04

Copy link
Copy Markdown
Collaborator Author

Thanks Rames. At 7d385440c:

  • Stored credentials and a non-HeyGen base: a HEYGEN_API_BASE outside heygen.com now gets no stored or host OAuth credential, only a key set for it; new test.
  • Voice clone / delete through Desktop: feat(studio): edit and style text in the preview #3077 now forwards POST /v3/voices/clone and DELETE /v3/voices/{id} (4251ee5ae).
  • Naming Desktop: the public text no longer names HyperFrames Desktop. While checking that, I also scoped the host guidance to HEYGEN_API_BASE alone: the earlier wording ("a host app's own HeyGen media tools first") could steer an agent in plain Claude Code with a HeyGen MCP connector away from today's CLI path. The README / CLAUDE.md / overview / general-video / capability-menu copies are back to main's wording.

media-use tests 117/117, lint-skills, mirror and manifest pass.

🤖 Addressed by Claude Code

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_BASE is set, and the compressed copies are back to main's wording.
  • Credentials and non-HeyGen bases: resolveCredential now refuses the access token, the env key and the stored credentials file when the base isn't heygen.com or *.heygen.com. new URL(...).hostname plus endsWith(".heygen.com") rules out lookalike hosts such as evilheygen.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.mjs passes 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>
@Vitozhu04

Copy link
Copy Markdown
Collaborator Author

Thanks Rames, fixed at f3b411f87 (review).

  • loadEnvFromDir now skips HEYGEN_API_BASE and HEYGEN_ALLOW_HTTP: a host app sets them in the environment it spawns, never in a project file, so a project's .env can no longer move the base and take the shell HEYGEN_API_KEY with it.
  • New test: a .env holding both lines plus an ordinary variable, with a shell HEYGEN_API_KEY set, loads the ordinary variable, leaves both host-only ones unset and heygenBase() at https://api.heygen.com/v3. It fails on 7d385440c and passes now.
  • node --test over the audio lib and the TTS/voice scripts: 118 pass, 0 fail. skills-manifest.json regenerated (--check in sync); oxfmt and oxlint clean on the changed files.

@jrusso1020 jrusso1020 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved at f3b411f87. My changes request at 7d385440c is resolved.

  • loadEnvFromDir now skips HEYGEN_API_BASE and HEYGEN_ALLOW_HTTP (HOST_ONLY), so a project's .env can no longer choose where the shell's HEYGEN_API_KEY goes. Every other key still loads, and the shell still wins.
  • I re-ran my original repro: a project .env holding only HEYGEN_API_BASE=https://proxy.example.com, with HEYGEN_API_KEY=hg_shell_real in the shell. It now gives heygenBase() = https://api.heygen.com/v3 and resolveCredential() = 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 in heygen.test.mjs).
  • heygen.mjs is still the only code that reads HEYGEN_API_BASE. The other mentions are in docs.
  • CI at this head has no failures. Everything is either success or skipped by path.

— Rames

@Vitozhu04
Vitozhu04 added this pull request to the merge queue Oct 7, 2026
Merged via the queue into main with commit 21d14b2 Oct 7, 2026
57 checks passed
@Vitozhu04
Vitozhu04 deleted the wenbo/media-use-host-heygen-gateway branch October 7, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants