mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-11 14:50:02 +00:00
feat(media-use): usage telemetry for HeyGen conversion (#2130)
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import { track } from "./telemetry.mjs";
|
||||
|
||||
// v0.3.0 is the first CLI that can use an OAuth session; v0.1.x/0.2.x reject it
|
||||
// ("heygen-cli can't use OAuth yet"), and OAuth is what the free-usage path
|
||||
// needs — so anything below this can't authenticate for free usage at all.
|
||||
@@ -20,6 +22,14 @@ const ACTIONABLE_MESSAGES = new Set([
|
||||
]);
|
||||
|
||||
export function classifyHeygenError(err) {
|
||||
return classifyHeygenErrorResult(err).message;
|
||||
}
|
||||
|
||||
export function classifyHeygenErrorCode(err) {
|
||||
return classifyHeygenErrorResult(err).code;
|
||||
}
|
||||
|
||||
function classifyHeygenErrorResult(err) {
|
||||
const detail = heygenErrorDetail(err);
|
||||
const text = [err?.stderr, err?.stdout, err?.message, detail]
|
||||
.map((value) => textOf(value))
|
||||
@@ -33,7 +43,7 @@ export function classifyHeygenError(err) {
|
||||
// embeds the `heygen ...` command line — sending users to reinstall a CLI they
|
||||
// just ran successfully. Keep this narrow.
|
||||
if (err?.code === "ENOENT" || lower.includes("command not found")) {
|
||||
return HEYGEN_NOT_FOUND_MESSAGE;
|
||||
return { code: "not_found", message: HEYGEN_NOT_FOUND_MESSAGE };
|
||||
}
|
||||
|
||||
if (
|
||||
@@ -50,24 +60,63 @@ export function classifyHeygenError(err) {
|
||||
lower.includes("auth required") ||
|
||||
lower.includes("authentication required")
|
||||
) {
|
||||
return HEYGEN_NOT_AUTHENTICATED_MESSAGE;
|
||||
return { code: "not_authenticated", message: HEYGEN_NOT_AUTHENTICATED_MESSAGE };
|
||||
}
|
||||
|
||||
const version = firstSemver(text);
|
||||
if (version && versionLessThan(version, HEYGEN_MIN_VERSION)) {
|
||||
return HEYGEN_OUTDATED_MESSAGE;
|
||||
return { code: "outdated", message: HEYGEN_OUTDATED_MESSAGE };
|
||||
}
|
||||
|
||||
return detail;
|
||||
if (
|
||||
lower.includes("rate limit") ||
|
||||
lower.includes("quota") ||
|
||||
lower.includes("insufficient credit") ||
|
||||
lower.includes("too many requests") ||
|
||||
lower.includes("throttled") ||
|
||||
/\b429\b/.test(lower)
|
||||
) {
|
||||
return { code: "rate_limited", message: detail };
|
||||
}
|
||||
|
||||
return { code: "other", message: detail };
|
||||
}
|
||||
|
||||
export function reportHeygenFailure(err, context) {
|
||||
const message = classifyHeygenError(err);
|
||||
// reportHeygenFailure's callers (voice-provider.mjs, heygen-search.mjs) are
|
||||
// synchronous and several layers below the CLI's process.exit() calls, so
|
||||
// they can't await this tracking call themselves. Stash each attempt's
|
||||
// promise here so a caller closer to exit (resolve.mjs) can join it first —
|
||||
// same "awaited so a short-lived run flushes it" discipline telemetry.mjs's
|
||||
// track() already documents, just reachable from a sync call site.
|
||||
const pendingFailureTracking = new Set();
|
||||
|
||||
export function reportHeygenFailure(err, context, trackEvent = track) {
|
||||
const { code, message } = classifyHeygenErrorResult(err);
|
||||
if (ACTIONABLE_MESSAGES.has(message)) {
|
||||
console.error(message);
|
||||
} else {
|
||||
console.error(`media-use: \`${context}\` failed: ${message}`);
|
||||
}
|
||||
try {
|
||||
const tracked = Promise.resolve(
|
||||
trackEvent("media_use_provider_error", { provider: "heygen", reason: code }),
|
||||
).catch(() => {});
|
||||
pendingFailureTracking.add(tracked);
|
||||
void tracked.finally(() => pendingFailureTracking.delete(tracked));
|
||||
return tracked;
|
||||
} catch {
|
||||
// Telemetry must never affect the provider failure path.
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
|
||||
// Awaits every provider-error track fired since the last flush, so a caller
|
||||
// about to process.exit() doesn't orphan one mid-request (both are separate,
|
||||
// non-keepalive HTTP connections with no ordering guarantee otherwise).
|
||||
// Never rejects: each tracked promise already swallows its own failure.
|
||||
export async function flushHeygenFailureTracking() {
|
||||
if (pendingFailureTracking.size === 0) return;
|
||||
await Promise.all(pendingFailureTracking);
|
||||
}
|
||||
|
||||
export function firstSemver(text) {
|
||||
|
||||
Reference in New Issue
Block a user