mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-03 23:03:09 +00:00
feat(cli): vendor initial hyperframes cloud client codegen (#1109)
* feat(cli): vendor initial hyperframes cloud client codegen Generated by experiment-framework/scripts/generate_hyperframes_cli_client.py (see heygen-com/experiment-framework#37896). Sets up the baseline for the sync workflow to diff against on future spec changes. The follow-up PR adds the orchestration layer (zip + upload + poll + download) and the user-facing 'hyperframes cloud render/list/get/delete' commands on top of this generated client. The fallow ignore pattern is necessary because the generated request() method is intentionally a single switch that handles all 5 endpoints in one place; refactoring it here would just be re-introduced on the next codegen run. * chore(cli): regenerate cloud client with mimeType parameter on multipart uploads Adds optional mimeType arg to uploadAsset (and any future multipart endpoints). Without it, FormData sends application/octet-stream which is correct for the documented media surface (png/jpeg/mp4/etc.) but ambiguous for the private-beta zip uploads the cloud render flow uses. Callers that pass `mimeType: "application/zip"` tag the multipart part with the right Content-Type so downstream proxies, WAFs, and any future server-side change that keys off the part MIME (instead of the current magic-byte detection) all see the intended type. Addresses review feedback on heygen-com/experiment-framework#37896. Generated by scripts/generate_hyperframes_cli_client.py with the matching update to the multipart emit path.
This commit is contained in:
@@ -0,0 +1,305 @@
|
||||
/**
|
||||
* AUTO-GENERATED from experiment-framework/openapi/external-api.json.
|
||||
* DO NOT EDIT MANUALLY. Re-run
|
||||
* `python scripts/generate_hyperframes_cli_client.py` in
|
||||
* experiment-framework to regenerate.
|
||||
*/
|
||||
import type {
|
||||
CreateHyperframesRenderRequest,
|
||||
CreateHyperframesRenderResponse,
|
||||
DeleteHyperframesRenderResponse,
|
||||
HyperframesRenderDetail,
|
||||
UploadAssetV3Response,
|
||||
} from "./types.js";
|
||||
|
||||
export type AuthHeaders = Record<string, string>;
|
||||
|
||||
/**
|
||||
* Caller-provided context. Keep the shape narrow so the cli/src/auth/
|
||||
* module stays the single owner of credential resolution.
|
||||
*/
|
||||
export interface HyperframesCloudClientOptions {
|
||||
/** Base URL like "https://api.heygen.com" (no trailing slash). */
|
||||
baseUrl: string;
|
||||
/**
|
||||
* Return the auth headers to attach to every request. Called once per
|
||||
* request so callers can refresh OAuth tokens transparently.
|
||||
*/
|
||||
getAuthHeaders: () => Promise<AuthHeaders> | AuthHeaders;
|
||||
/** Override fetch (used by tests). */
|
||||
fetchImpl?: typeof fetch;
|
||||
}
|
||||
|
||||
/**
|
||||
* Standard error envelope used by /v3 endpoints. See StandardAPIError in
|
||||
* types.ts for the field shape.
|
||||
*/
|
||||
export class HyperframesApiError extends Error {
|
||||
readonly status: number;
|
||||
readonly code?: string;
|
||||
readonly param?: string | null;
|
||||
readonly docUrl?: string | null;
|
||||
readonly raw: unknown;
|
||||
|
||||
constructor(opts: {
|
||||
status: number;
|
||||
message: string;
|
||||
code?: string;
|
||||
param?: string | null;
|
||||
docUrl?: string | null;
|
||||
raw?: unknown;
|
||||
}) {
|
||||
super(opts.message);
|
||||
this.name = "HyperframesApiError";
|
||||
this.status = opts.status;
|
||||
this.code = opts.code;
|
||||
this.param = opts.param;
|
||||
this.docUrl = opts.docUrl;
|
||||
this.raw = opts.raw;
|
||||
}
|
||||
}
|
||||
|
||||
interface RequestOptions {
|
||||
method: string;
|
||||
path: string;
|
||||
query?: Record<string, string | number | undefined>;
|
||||
body?: unknown;
|
||||
multipart?: FormData;
|
||||
idempotencyKey?: string;
|
||||
signal?: AbortSignal;
|
||||
/**
|
||||
* When true (default), the ``data`` wrapper of the standard /v3
|
||||
* response envelope is unwrapped before returning. List endpoints set
|
||||
* this to false so callers can read ``has_more`` / ``next_token``.
|
||||
*/
|
||||
unwrapData?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Typed client for the HyperFrames cloud-render API. Auto-generated; do
|
||||
* not hand-edit. Submit new endpoints by adding them to
|
||||
* scripts/generate_hyperframes_cli_client.py in experiment-framework.
|
||||
*/
|
||||
export class HyperframesCloudClient {
|
||||
private readonly baseUrl: string;
|
||||
private readonly getAuthHeaders: () => Promise<AuthHeaders> | AuthHeaders;
|
||||
private readonly fetchImpl: typeof fetch;
|
||||
|
||||
constructor(opts: HyperframesCloudClientOptions) {
|
||||
this.baseUrl = opts.baseUrl.replace(/\/+$/, "");
|
||||
this.getAuthHeaders = opts.getAuthHeaders;
|
||||
this.fetchImpl = opts.fetchImpl ?? fetch;
|
||||
}
|
||||
|
||||
private async request<T>(opts: RequestOptions): Promise<T> {
|
||||
const url = this.buildUrl(opts.path, opts.query);
|
||||
const auth = await this.getAuthHeaders();
|
||||
const headers: Record<string, string> = { ...auth };
|
||||
let body: BodyInit | undefined;
|
||||
|
||||
if (opts.multipart) {
|
||||
body = opts.multipart;
|
||||
// fetch sets the multipart boundary automatically; do NOT set
|
||||
// Content-Type here or the upload will be rejected.
|
||||
} else if (opts.body !== undefined) {
|
||||
headers["content-type"] = "application/json";
|
||||
body = JSON.stringify(opts.body);
|
||||
}
|
||||
|
||||
if (opts.idempotencyKey) {
|
||||
headers["Idempotency-Key"] = opts.idempotencyKey;
|
||||
}
|
||||
|
||||
const res = await this.fetchImpl(url, {
|
||||
method: opts.method,
|
||||
headers,
|
||||
body,
|
||||
signal: opts.signal,
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
throw await this.toApiError(res);
|
||||
}
|
||||
|
||||
// 204 No Content
|
||||
if (res.status === 204) {
|
||||
return undefined as T;
|
||||
}
|
||||
|
||||
const text = await res.text();
|
||||
if (!text) {
|
||||
return undefined as T;
|
||||
}
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = JSON.parse(text);
|
||||
} catch (err) {
|
||||
throw new HyperframesApiError({
|
||||
status: res.status,
|
||||
message: `Invalid JSON response: ${(err as Error).message}`,
|
||||
raw: text.slice(0, 500),
|
||||
});
|
||||
}
|
||||
// The /v3 envelope is {data: T, ...}. Unwrap when present and the
|
||||
// call site asked for it (the default) so consumers read the inner
|
||||
// payload directly. List endpoints opt out so they can read
|
||||
// ``has_more`` / ``next_token``.
|
||||
const unwrap = opts.unwrapData !== false;
|
||||
if (
|
||||
unwrap &&
|
||||
parsed &&
|
||||
typeof parsed === "object" &&
|
||||
"data" in (parsed as Record<string, unknown>)
|
||||
) {
|
||||
const envelope = parsed as { data: T };
|
||||
return envelope.data;
|
||||
}
|
||||
return parsed as T;
|
||||
}
|
||||
|
||||
private buildUrl(path: string, query?: Record<string, string | number | undefined>): string {
|
||||
const url = new URL(this.baseUrl + path);
|
||||
if (query) {
|
||||
for (const [k, v] of Object.entries(query)) {
|
||||
if (v === undefined) continue;
|
||||
url.searchParams.set(k, String(v));
|
||||
}
|
||||
}
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
private async toApiError(res: Response): Promise<HyperframesApiError> {
|
||||
let parsed: unknown;
|
||||
try {
|
||||
parsed = await res.json();
|
||||
} catch {
|
||||
parsed = undefined;
|
||||
}
|
||||
const err =
|
||||
parsed && typeof parsed === "object" && "error" in (parsed as Record<string, unknown>)
|
||||
? ((parsed as Record<string, unknown>).error as Record<string, unknown> | undefined)
|
||||
: undefined;
|
||||
return new HyperframesApiError({
|
||||
status: res.status,
|
||||
message:
|
||||
(err && typeof err.message === "string" && err.message) ||
|
||||
`HTTP ${res.status} ${res.statusText}`,
|
||||
code: err && typeof err.code === "string" ? err.code : undefined,
|
||||
param:
|
||||
err && (typeof err.param === "string" || err.param === null)
|
||||
? (err.param as string | null)
|
||||
: undefined,
|
||||
docUrl:
|
||||
err && (typeof err.doc_url === "string" || err.doc_url === null)
|
||||
? (err.doc_url as string | null)
|
||||
: undefined,
|
||||
raw: parsed,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload Asset
|
||||
*
|
||||
* Uploads a file (image, video, audio, or PDF) and returns an asset_id for use in other endpoints. Max 32 MB. Supported types: png, jpeg, mp4, webm, mp3, wav, pdf.
|
||||
*/
|
||||
async uploadAsset(args: {
|
||||
file: Blob | Buffer | Uint8Array;
|
||||
filename: string;
|
||||
mimeType?: string;
|
||||
idempotencyKey?: string;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<UploadAssetV3Response> {
|
||||
const fd = new FormData();
|
||||
const blobOpts = args.mimeType ? { type: args.mimeType } : undefined;
|
||||
const blob =
|
||||
args.file instanceof Blob
|
||||
? args.file
|
||||
: new Blob([args.file as unknown as BlobPart], blobOpts);
|
||||
fd.append("file", blob, args.filename);
|
||||
return await this.request<UploadAssetV3Response>({
|
||||
method: "POST",
|
||||
path: "/v3/assets",
|
||||
multipart: fd,
|
||||
idempotencyKey: args.idempotencyKey,
|
||||
signal: args.signal,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HyperFrames Render
|
||||
*
|
||||
* Renders a HyperFrames composition (an HTML+JS+assets project bundled as a .zip) into a video. Submit the project via `url`, `asset_id` (pre-uploaded via POST /v3/assets), or inline `base64`. Returns a `render_id` to poll via GET /v3/hyperframes/renders/{render_id}.
|
||||
*/
|
||||
async createRender(args: {
|
||||
body: CreateHyperframesRenderRequest;
|
||||
idempotencyKey?: string;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<CreateHyperframesRenderResponse> {
|
||||
return await this.request<CreateHyperframesRenderResponse>({
|
||||
method: "POST",
|
||||
path: "/v3/hyperframes/renders",
|
||||
body: args.body,
|
||||
idempotencyKey: args.idempotencyKey,
|
||||
signal: args.signal,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* List HyperFrames Renders
|
||||
*
|
||||
* Returns a cursor-paginated list of HyperFrames renders in the account, newest first.
|
||||
*/
|
||||
async listRenders(args: { limit?: number; token?: string; signal?: AbortSignal }): Promise<{
|
||||
data?: Array<HyperframesRenderDetail>;
|
||||
has_more?: boolean;
|
||||
next_token?: string | null;
|
||||
}> {
|
||||
const query: Record<string, string | number | undefined> = {
|
||||
limit: args.limit,
|
||||
token: args.token,
|
||||
};
|
||||
return await this.request<{
|
||||
data?: Array<HyperframesRenderDetail>;
|
||||
has_more?: boolean;
|
||||
next_token?: string | null;
|
||||
}>({
|
||||
method: "GET",
|
||||
path: "/v3/hyperframes/renders",
|
||||
query,
|
||||
unwrapData: false,
|
||||
signal: args.signal,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get HyperFrames Render
|
||||
*
|
||||
* Returns full details for a single HyperFrames render, including status and signed video_url when complete.
|
||||
*/
|
||||
async getRender(args: {
|
||||
render_id: string;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<HyperframesRenderDetail> {
|
||||
return await this.request<HyperframesRenderDetail>({
|
||||
method: "GET",
|
||||
path: `/v3/hyperframes/renders/${encodeURIComponent(args.render_id)}`,
|
||||
signal: args.signal,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete HyperFrames Render
|
||||
*
|
||||
* Soft-deletes a HyperFrames render. Subsequent GETs return 404.
|
||||
*/
|
||||
async deleteRender(args: {
|
||||
render_id: string;
|
||||
signal?: AbortSignal;
|
||||
}): Promise<DeleteHyperframesRenderResponse> {
|
||||
return await this.request<DeleteHyperframesRenderResponse>({
|
||||
method: "DELETE",
|
||||
path: `/v3/hyperframes/renders/${encodeURIComponent(args.render_id)}`,
|
||||
signal: args.signal,
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* AUTO-GENERATED from experiment-framework/openapi/external-api.json.
|
||||
* DO NOT EDIT MANUALLY. Re-run
|
||||
* `python scripts/generate_hyperframes_cli_client.py` in
|
||||
* experiment-framework to regenerate.
|
||||
*/
|
||||
|
||||
// Component schemas reachable from the cloud-render endpoint set.
|
||||
// Add a new path/method to TARGET_ENDPOINTS in
|
||||
// scripts/generate_hyperframes_cli_client.py to extend this list.
|
||||
|
||||
/**
|
||||
* Asset input via base64-encoded content.
|
||||
*/
|
||||
export interface AssetBase64 {
|
||||
/**
|
||||
* Input type discriminator
|
||||
*/
|
||||
type: "base64";
|
||||
/**
|
||||
* MIME type of the encoded content (e.g. "image/png")
|
||||
*/
|
||||
media_type: string;
|
||||
/**
|
||||
* Base64-encoded file content
|
||||
*/
|
||||
data: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset input via HeyGen asset ID from the asset upload endpoint.
|
||||
*/
|
||||
export interface AssetId {
|
||||
/**
|
||||
* Input type discriminator
|
||||
*/
|
||||
type: "asset_id";
|
||||
/**
|
||||
* HeyGen asset ID from the asset upload endpoint
|
||||
*/
|
||||
asset_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asset input via publicly accessible HTTPS URL.
|
||||
*/
|
||||
export interface AssetUrl {
|
||||
/**
|
||||
* Input type discriminator
|
||||
*/
|
||||
type: "url";
|
||||
/**
|
||||
* Publicly accessible HTTPS URL for the asset
|
||||
*/
|
||||
url: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Request body for POST /v3/hyperframes/renders.
|
||||
*/
|
||||
export interface CreateHyperframesRenderRequest {
|
||||
/**
|
||||
* HyperFrames composition .zip — provide as {type: 'url', url: '...'}, {type:
|
||||
* 'asset_id', asset_id: '...'} (pre-uploaded via POST /v3/assets), or {type:
|
||||
* 'base64', media_type: 'application/zip', data: '...'}. Zip must contain
|
||||
* index.html at the root (or the path you set in `composition`).
|
||||
*/
|
||||
project: AssetUrl | AssetId | AssetBase64;
|
||||
/**
|
||||
* Output frames per second. Defaults to 30 if not provided.
|
||||
*/
|
||||
fps?: number | null;
|
||||
/**
|
||||
* Render quality preset; higher quality is slower.
|
||||
*/
|
||||
quality?: "draft" | "standard" | "high";
|
||||
/**
|
||||
* Output container/codec.
|
||||
*/
|
||||
format?: "mp4" | "webm" | "mov";
|
||||
/**
|
||||
* Optional resolution preset. If omitted, the composition's own declared
|
||||
* dimensions are used.
|
||||
*/
|
||||
resolution?:
|
||||
| "landscape"
|
||||
| "portrait"
|
||||
| "landscape-4k"
|
||||
| "portrait-4k"
|
||||
| "square"
|
||||
| "square-4k"
|
||||
| null;
|
||||
/**
|
||||
* Entry HTML file relative to the project root (e.g. compositions/intro.html).
|
||||
* Defaults to index.html when omitted.
|
||||
*/
|
||||
composition?: string | null;
|
||||
/**
|
||||
* Optional overrides for the composition's data-composition-variables. Use
|
||||
* this to parameterise a single composition across multiple renders.
|
||||
*/
|
||||
variables?: Record<string, unknown> | null;
|
||||
/**
|
||||
* Free-text label for the render; echoed back in detail responses.
|
||||
*/
|
||||
title?: string | null;
|
||||
/**
|
||||
* Opaque client tracking ID, echoed back in webhook payloads.
|
||||
*/
|
||||
callback_id?: string | null;
|
||||
/**
|
||||
* Per-request HTTPS webhook URL the render fires when it terminates.
|
||||
*/
|
||||
callback_url?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response for POST /v3/hyperframes/renders.
|
||||
*/
|
||||
export interface CreateHyperframesRenderResponse {
|
||||
/**
|
||||
* HyperFrames render identifier — poll GET /v3/hyperframes/renders/{render_id}
|
||||
* for status.
|
||||
*/
|
||||
render_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response for DELETE /v3/hyperframes/renders/{render_id}.
|
||||
*/
|
||||
export interface DeleteHyperframesRenderResponse {
|
||||
/**
|
||||
* ID of the deleted render.
|
||||
*/
|
||||
render_id: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detailed HyperFrames render resource.
|
||||
*/
|
||||
export interface HyperframesRenderDetail {
|
||||
/**
|
||||
* Unique render identifier.
|
||||
*/
|
||||
render_id: string;
|
||||
/**
|
||||
* Current lifecycle state.
|
||||
*/
|
||||
status: HyperframesRenderStatus;
|
||||
/**
|
||||
* Caller-supplied free-text label.
|
||||
*/
|
||||
title?: string | null;
|
||||
/**
|
||||
* Caller-supplied client tracking ID.
|
||||
*/
|
||||
callback_id?: string | null;
|
||||
/**
|
||||
* Presigned download URL for the rendered video. Present only when status is
|
||||
* 'completed'.
|
||||
*/
|
||||
video_url?: string | null;
|
||||
/**
|
||||
* Presigned download URL for the auto-generated thumbnail.
|
||||
*/
|
||||
thumbnail_url?: string | null;
|
||||
/**
|
||||
* Video duration in seconds; null until completed.
|
||||
*/
|
||||
duration?: number | null;
|
||||
/**
|
||||
* Frames per second the render was created at.
|
||||
*/
|
||||
fps?: number | null;
|
||||
/**
|
||||
* Render quality preset.
|
||||
*/
|
||||
quality?: "draft" | "standard" | "high" | null;
|
||||
/**
|
||||
* Output container/codec.
|
||||
*/
|
||||
format: "mp4" | "webm" | "mov";
|
||||
/**
|
||||
* Resolution preset, if one was set.
|
||||
*/
|
||||
resolution?:
|
||||
| "landscape"
|
||||
| "portrait"
|
||||
| "landscape-4k"
|
||||
| "portrait-4k"
|
||||
| "square"
|
||||
| "square-4k"
|
||||
| null;
|
||||
/**
|
||||
* Composition entry file path.
|
||||
*/
|
||||
composition?: string | null;
|
||||
/**
|
||||
* Unix timestamp when the render was created.
|
||||
*/
|
||||
created_at?: number | null;
|
||||
/**
|
||||
* Unix timestamp when the render terminated. Null until status is 'completed'
|
||||
* or 'failed'.
|
||||
*/
|
||||
completed_at?: number | null;
|
||||
/**
|
||||
* Error description. Present only when status is 'failed'.
|
||||
*/
|
||||
failure_message?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Lifecycle status of a HyperFrames render.
|
||||
*/
|
||||
export type HyperframesRenderStatus = "queued" | "rendering" | "completed" | "failed";
|
||||
|
||||
export interface StandardAPIError {
|
||||
/**
|
||||
* Machine-readable error code
|
||||
*/
|
||||
code: string;
|
||||
/**
|
||||
* Human-readable error message
|
||||
*/
|
||||
message: string;
|
||||
/**
|
||||
* Which request field caused the error
|
||||
*/
|
||||
param?: string | null;
|
||||
/**
|
||||
* Link to error documentation
|
||||
*/
|
||||
doc_url?: string | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Response from uploading an asset via POST /v3/assets.
|
||||
*/
|
||||
export interface UploadAssetV3Response {
|
||||
/**
|
||||
* Unique asset identifier for use in other endpoints like POST
|
||||
* /v3/video-agents
|
||||
*/
|
||||
asset_id: string;
|
||||
/**
|
||||
* Public URL of the uploaded asset
|
||||
*/
|
||||
url: string;
|
||||
/**
|
||||
* Detected MIME type of the file
|
||||
*/
|
||||
mime_type: string;
|
||||
/**
|
||||
* File size in bytes
|
||||
*/
|
||||
size_bytes: number;
|
||||
}
|
||||
Reference in New Issue
Block a user