From b26f126323c83fca99555e7b897fcda048ccd098 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Tue, 16 Jun 2026 15:48:26 +0000 Subject: [PATCH 1/6] fix(client): send content-type header for requests with an omitted optional body --- src/client.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/client.ts b/src/client.ts index 112e7a7..87039d6 100644 --- a/src/client.ts +++ b/src/client.ts @@ -816,11 +816,19 @@ export class RunwayML { return () => controller.abort(); } - private buildBody({ options: { body, headers: rawHeaders } }: { options: FinalRequestOptions }): { + private buildBody({ options }: { options: FinalRequestOptions }): { bodyHeaders: HeadersLike; body: BodyInit | undefined; } { + const { body, headers: rawHeaders } = options; if (!body) { + // A resource method always passes a `body` key when its operation defines a + // request body, even if the caller omitted an optional body param. Keep the + // content-type for those, and only elide it for operations with no body at + // all (e.g. GET/DELETE). + if (body == null && 'body' in options) { + return this.#encoder({ body, headers: buildHeaders([rawHeaders]) }); + } return { bodyHeaders: undefined, body: undefined }; } const headers = buildHeaders([rawHeaders]); From 05ae79c87f5bfc60545c5e8271a9f19c29728085 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:10:19 +0000 Subject: [PATCH 2/6] feat(api): Recipes, aleph2 range --- .stats.yml | 4 +- src/resources/organization.ts | 12 +++- src/resources/video-to-video.ts | 72 +++++++++++++++++++--- tests/api-resources/video-to-video.test.ts | 12 +++- 4 files changed, 87 insertions(+), 13 deletions(-) diff --git a/.stats.yml b/.stats.yml index dde3b6e..dd1d209 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-b85d7ddcdd90c3009ccbad953a0f5c1015b6f4acc6196ce47dfaff89873e8831.yml -openapi_spec_hash: c660abf954cb61f065c4f957966776bb +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-6a146f9f77a3e4eaef62a1ab9a0b7eb035224283be8d777abfd6494b209e092a.yml +openapi_spec_hash: e9df31877c3f5416b952f0b1c0a161a9 config_hash: dbcc649d22e217f477258caee20c63d2 diff --git a/src/resources/organization.ts b/src/resources/organization.ts index 3b6c23c..943c99a 100644 --- a/src/resources/organization.ts +++ b/src/resources/organization.ts @@ -139,6 +139,11 @@ export interface OrganizationRetrieveUsageResponse { | 'happyhorse_1_0' | 'aleph2' | 'product_swap' + | 'product_ad' + | 'multi_shot_video' + | 'product_ugc' + | 'marketing_stock_image' + | 'product_campaign_image' >; results: Array; @@ -207,7 +212,12 @@ export namespace OrganizationRetrieveUsageResponse { | 'klingO3_4k' | 'happyhorse_1_0' | 'aleph2' - | 'product_swap'; + | 'product_swap' + | 'product_ad' + | 'multi_shot_video' + | 'product_ugc' + | 'marketing_stock_image' + | 'product_campaign_image'; } } } diff --git a/src/resources/video-to-video.ts b/src/resources/video-to-video.ts index 3d742f4..4ebfaf3 100644 --- a/src/resources/video-to-video.ts +++ b/src/resources/video-to-video.ts @@ -15,7 +15,6 @@ export class VideoToVideo extends APIResource { * ```ts * const videoToVideo = await client.videoToVideo.create({ * model: 'aleph2', - * promptText: 'x', * videoUri: 'https://example.com/video.mp4', * }); * ``` @@ -46,12 +45,6 @@ export declare namespace VideoToVideoCreateParams { export interface Variant0 { model: 'aleph2'; - /** - * A non-empty string up to 1000 characters describing what should appear in the - * output. - */ - promptText: string; - /** * A HTTPS URL. */ @@ -68,6 +61,17 @@ export declare namespace VideoToVideoCreateParams { */ keyframes?: Array; + /** + * An optional string up to 1000 characters describing what should appear in the + * output. + */ + promptText?: string; + + /** + * @deprecated + */ + ratio?: string; + /** * If unspecified, a random number is chosen. Varying the seed integer is a way to * get different results for the same other request parameters. Using the same seed @@ -105,6 +109,33 @@ export declare namespace VideoToVideoCreateParams { * A HTTPS URL. */ uri: string; + + /** + * Optional edit window. When set, the edit applies only to this time range and the + * keyframe timestamp must fall within it. All keyframes must either set a range or + * none may. + */ + range?: UnionMember0.Range; + } + + export namespace UnionMember0 { + /** + * Optional edit window. When set, the edit applies only to this time range and the + * keyframe timestamp must fall within it. All keyframes must either set a range or + * none may. + */ + export interface Range { + /** + * End of the edit window (exclusive) in whole seconds from the start of the input + * video. + */ + end_seconds: number; + + /** + * Start of the edit window in whole seconds from the start of the input video. + */ + start_seconds: number; + } } export interface UnionMember1 { @@ -118,6 +149,33 @@ export declare namespace VideoToVideoCreateParams { * A HTTPS URL. */ uri: string; + + /** + * Optional edit window. When set, the edit applies only to this time range and the + * keyframe timestamp must fall within it. All keyframes must either set a range or + * none may. + */ + range?: UnionMember1.Range; + } + + export namespace UnionMember1 { + /** + * Optional edit window. When set, the edit applies only to this time range and the + * keyframe timestamp must fall within it. All keyframes must either set a range or + * none may. + */ + export interface Range { + /** + * End of the edit window (exclusive) in whole seconds from the start of the input + * video. + */ + end_seconds: number; + + /** + * Start of the edit window in whole seconds from the start of the input video. + */ + start_seconds: number; + } } } diff --git a/tests/api-resources/video-to-video.test.ts b/tests/api-resources/video-to-video.test.ts index 78a99d1..a5432d4 100644 --- a/tests/api-resources/video-to-video.test.ts +++ b/tests/api-resources/video-to-video.test.ts @@ -11,7 +11,6 @@ describe('resource videoToVideo', () => { test('create: only required params', async () => { const responsePromise = client.videoToVideo.create({ model: 'aleph2', - promptText: 'x', videoUri: 'https://example.com/video.mp4', }); const rawResponse = await responsePromise.asResponse(); @@ -26,10 +25,17 @@ describe('resource videoToVideo', () => { test('create: required and optional params', async () => { const response = await client.videoToVideo.create({ model: 'aleph2', - promptText: 'x', videoUri: 'https://example.com/video.mp4', contentModeration: { publicFigureThreshold: 'auto' }, - keyframes: [{ seconds: 0, uri: 'https://example.com/file' }], + keyframes: [ + { + seconds: 0, + uri: 'https://example.com/file', + range: { end_seconds: 1, start_seconds: 0 }, + }, + ], + promptText: 'x', + ratio: 'ratio', seed: 0, targetAspectRatio: '16:9', }); From a602a48b1a6eef9bdc845f09851fa58100c2d155 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:11:47 +0000 Subject: [PATCH 3/6] fix(api): remove range param --- .stats.yml | 4 +- src/resources/video-to-video.ts | 54 ---------------------- tests/api-resources/video-to-video.test.ts | 8 +--- 3 files changed, 3 insertions(+), 63 deletions(-) diff --git a/.stats.yml b/.stats.yml index dd1d209..a33a1d3 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-6a146f9f77a3e4eaef62a1ab9a0b7eb035224283be8d777abfd6494b209e092a.yml -openapi_spec_hash: e9df31877c3f5416b952f0b1c0a161a9 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-90e0cacea80527a3d73a53ef428dff73536da4a0380e2faea00e394db08a284d.yml +openapi_spec_hash: e84013ce9a3d7c14175ba3050cbb5bc1 config_hash: dbcc649d22e217f477258caee20c63d2 diff --git a/src/resources/video-to-video.ts b/src/resources/video-to-video.ts index 4ebfaf3..f907deb 100644 --- a/src/resources/video-to-video.ts +++ b/src/resources/video-to-video.ts @@ -109,33 +109,6 @@ export declare namespace VideoToVideoCreateParams { * A HTTPS URL. */ uri: string; - - /** - * Optional edit window. When set, the edit applies only to this time range and the - * keyframe timestamp must fall within it. All keyframes must either set a range or - * none may. - */ - range?: UnionMember0.Range; - } - - export namespace UnionMember0 { - /** - * Optional edit window. When set, the edit applies only to this time range and the - * keyframe timestamp must fall within it. All keyframes must either set a range or - * none may. - */ - export interface Range { - /** - * End of the edit window (exclusive) in whole seconds from the start of the input - * video. - */ - end_seconds: number; - - /** - * Start of the edit window in whole seconds from the start of the input video. - */ - start_seconds: number; - } } export interface UnionMember1 { @@ -149,33 +122,6 @@ export declare namespace VideoToVideoCreateParams { * A HTTPS URL. */ uri: string; - - /** - * Optional edit window. When set, the edit applies only to this time range and the - * keyframe timestamp must fall within it. All keyframes must either set a range or - * none may. - */ - range?: UnionMember1.Range; - } - - export namespace UnionMember1 { - /** - * Optional edit window. When set, the edit applies only to this time range and the - * keyframe timestamp must fall within it. All keyframes must either set a range or - * none may. - */ - export interface Range { - /** - * End of the edit window (exclusive) in whole seconds from the start of the input - * video. - */ - end_seconds: number; - - /** - * Start of the edit window in whole seconds from the start of the input video. - */ - start_seconds: number; - } } } diff --git a/tests/api-resources/video-to-video.test.ts b/tests/api-resources/video-to-video.test.ts index a5432d4..2d3fdf9 100644 --- a/tests/api-resources/video-to-video.test.ts +++ b/tests/api-resources/video-to-video.test.ts @@ -27,13 +27,7 @@ describe('resource videoToVideo', () => { model: 'aleph2', videoUri: 'https://example.com/video.mp4', contentModeration: { publicFigureThreshold: 'auto' }, - keyframes: [ - { - seconds: 0, - uri: 'https://example.com/file', - range: { end_seconds: 1, start_seconds: 0 }, - }, - ], + keyframes: [{ seconds: 0, uri: 'https://example.com/file' }], promptText: 'x', ratio: 'ratio', seed: 0, From 8509e2bf6a6350873e69186e01a6bf71121f0a1c Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:15:05 +0000 Subject: [PATCH 4/6] feat(client): add recipe endpoints to config --- .stats.yml | 6 +- api.md | 20 + src/client.ts | 33 ++ src/resources/index.ts | 15 + src/resources/recipes.ts | 547 ++++++++++++++++++++++++++++ tests/api-resources/recipes.test.ts | 162 ++++++++ 6 files changed, 780 insertions(+), 3 deletions(-) create mode 100644 src/resources/recipes.ts create mode 100644 tests/api-resources/recipes.test.ts diff --git a/.stats.yml b/.stats.yml index a33a1d3..2ac9ca5 100644 --- a/.stats.yml +++ b/.stats.yml @@ -1,4 +1,4 @@ -configured_endpoints: 43 -openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-90e0cacea80527a3d73a53ef428dff73536da4a0380e2faea00e394db08a284d.yml +configured_endpoints: 49 +openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/runwayml/runwayml-ae784dbc3407e86a6458d2848f2e9720eea6b3c534c3bbe101b614e3eb547dac.yml openapi_spec_hash: e84013ce9a3d7c14175ba3050cbb5bc1 -config_hash: dbcc649d22e217f477258caee20c63d2 +config_hash: 702846e1d30f519e56e425ca5455febe diff --git a/api.md b/api.md index 972ec7a..cc70b48 100644 --- a/api.md +++ b/api.md @@ -202,6 +202,26 @@ Methods: - client.realtimeSessions.retrieve(id) -> RealtimeSessionRetrieveResponse - client.realtimeSessions.delete(id) -> void +# Recipes + +Types: + +- RecipeMarketingStockImageResponse +- RecipeMultiShotVideoResponse +- RecipeProductAdResponse +- RecipeProductCampaignImageResponse +- RecipeProductSwapResponse +- RecipeProductUgcResponse + +Methods: + +- client.recipes.marketingStockImage({ ...params }) -> RecipeMarketingStockImageResponse +- client.recipes.multiShotVideo({ ...params }) -> RecipeMultiShotVideoResponse +- client.recipes.productAd({ ...params }) -> RecipeProductAdResponse +- client.recipes.productCampaignImage({ ...params }) -> RecipeProductCampaignImageResponse +- client.recipes.productSwap({ ...params }) -> RecipeProductSwapResponse +- client.recipes.productUgc({ ...params }) -> RecipeProductUgcResponse + # Voices Types: diff --git a/src/client.ts b/src/client.ts index 87039d6..40b43c9 100644 --- a/src/client.ts +++ b/src/client.ts @@ -77,6 +77,21 @@ import { RealtimeSessionRetrieveResponse, RealtimeSessions, } from './resources/realtime-sessions'; +import { + RecipeMarketingStockImageParams, + RecipeMarketingStockImageResponse, + RecipeMultiShotVideoParams, + RecipeMultiShotVideoResponse, + RecipeProductAdParams, + RecipeProductAdResponse, + RecipeProductCampaignImageParams, + RecipeProductCampaignImageResponse, + RecipeProductSwapParams, + RecipeProductSwapResponse, + RecipeProductUgcParams, + RecipeProductUgcResponse, + Recipes, +} from './resources/recipes'; import { SoundEffect, SoundEffectCreateParams, SoundEffectCreateResponse } from './resources/sound-effect'; import { SpeechToSpeech, @@ -942,6 +957,7 @@ export class RunwayML { avatarVideos: API.AvatarVideos = new API.AvatarVideos(this); documents: API.Documents = new API.Documents(this); realtimeSessions: API.RealtimeSessions = new API.RealtimeSessions(this); + recipes: API.Recipes = new API.Recipes(this); voices: API.Voices = new API.Voices(this); uploads: API.Uploads = new API.Uploads(this); workflows: API.Workflows = new API.Workflows(this); @@ -966,6 +982,7 @@ RunwayML.AvatarConversations = AvatarConversations; RunwayML.AvatarVideos = AvatarVideos; RunwayML.Documents = Documents; RunwayML.RealtimeSessions = RealtimeSessions; +RunwayML.Recipes = Recipes; RunwayML.Voices = Voices; RunwayML.Uploads = Uploads; RunwayML.Workflows = Workflows; @@ -1098,6 +1115,22 @@ export declare namespace RunwayML { type RealtimeSessionCreateParams as RealtimeSessionCreateParams, }; + export { + Recipes as Recipes, + type RecipeMarketingStockImageResponse as RecipeMarketingStockImageResponse, + type RecipeMultiShotVideoResponse as RecipeMultiShotVideoResponse, + type RecipeProductAdResponse as RecipeProductAdResponse, + type RecipeProductCampaignImageResponse as RecipeProductCampaignImageResponse, + type RecipeProductSwapResponse as RecipeProductSwapResponse, + type RecipeProductUgcResponse as RecipeProductUgcResponse, + type RecipeMarketingStockImageParams as RecipeMarketingStockImageParams, + type RecipeMultiShotVideoParams as RecipeMultiShotVideoParams, + type RecipeProductAdParams as RecipeProductAdParams, + type RecipeProductCampaignImageParams as RecipeProductCampaignImageParams, + type RecipeProductSwapParams as RecipeProductSwapParams, + type RecipeProductUgcParams as RecipeProductUgcParams, + }; + export { Voices as Voices, type VoiceCreateResponse as VoiceCreateResponse, diff --git a/src/resources/index.ts b/src/resources/index.ts index 7c4a343..f35b8fb 100644 --- a/src/resources/index.ts +++ b/src/resources/index.ts @@ -58,6 +58,21 @@ export { type RealtimeSessionRetrieveResponse, type RealtimeSessionCreateParams, } from './realtime-sessions'; +export { + Recipes, + type RecipeMarketingStockImageResponse, + type RecipeMultiShotVideoResponse, + type RecipeProductAdResponse, + type RecipeProductCampaignImageResponse, + type RecipeProductSwapResponse, + type RecipeProductUgcResponse, + type RecipeMarketingStockImageParams, + type RecipeMultiShotVideoParams, + type RecipeProductAdParams, + type RecipeProductCampaignImageParams, + type RecipeProductSwapParams, + type RecipeProductUgcParams, +} from './recipes'; export { SoundEffect, type SoundEffectCreateResponse, type SoundEffectCreateParams } from './sound-effect'; export { SpeechToSpeech, diff --git a/src/resources/recipes.ts b/src/resources/recipes.ts new file mode 100644 index 0000000..e5b6c9e --- /dev/null +++ b/src/resources/recipes.ts @@ -0,0 +1,547 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import { APIResource } from '../core/resource'; +import { APIPromise } from '../core/api-promise'; +import { RequestOptions } from '../internal/request-options'; + +export class Recipes extends APIResource { + /** + * Generate a polished marketing stock image from a text brief and optional brand + * logo image. + */ + marketingStockImage( + body: RecipeMarketingStockImageParams, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/v1/recipes/marketing_stock_image', { body, ...options }); + } + + /** + * Generate a multi-cut video from a story prompt (auto mode) or a custom shot list + * (custom mode). + */ + multiShotVideo( + body: RecipeMultiShotVideoParams, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/v1/recipes/multi_shot_video', { body, ...options }); + } + + /** + * Generate a cinematic product ad from product images, optional style references, + * product info, and creative direction. + */ + productAd(body: RecipeProductAdParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/recipes/product_ad', { body, ...options }); + } + + /** + * Generate four fashion campaign images from a product image and style brief. + */ + productCampaignImage( + body: RecipeProductCampaignImageParams, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/v1/recipes/product_campaign_image', { body, ...options }); + } + + /** + * Replace the product in a reference video with a new product, preserving camera + * motion, lighting, and scene composition. + */ + productSwap( + body: RecipeProductSwapParams, + options?: RequestOptions, + ): APIPromise { + return this._client.post('/v1/recipes/product_swap', { body, ...options }); + } + + /** + * Generate a vertical user-generated content ad from a character image, product + * image, product details, and optional creative direction. + */ + productUgc(body: RecipeProductUgcParams, options?: RequestOptions): APIPromise { + return this._client.post('/v1/recipes/product_ugc', { body, ...options }); + } +} + +export interface RecipeMarketingStockImageResponse { + /** + * The ID of the task that was created. Use this to retrieve the task later. + */ + id: string; +} + +export interface RecipeMultiShotVideoResponse { + /** + * The ID of the task that was created. Use this to retrieve the task later. + */ + id: string; +} + +export interface RecipeProductAdResponse { + /** + * The ID of the task that was created. Use this to retrieve the task later. + */ + id: string; +} + +export interface RecipeProductCampaignImageResponse { + /** + * The ID of the task that was created. Use this to retrieve the task later. + */ + id: string; +} + +export interface RecipeProductSwapResponse { + /** + * The ID of the task that was created. Use this to retrieve the task later. + */ + id: string; +} + +export interface RecipeProductUgcResponse { + /** + * The ID of the task that was created. Use this to retrieve the task later. + */ + id: string; +} + +export interface RecipeMarketingStockImageParams { + /** + * Marketing image brief. Describe the subject, audience, channel, desired mood, + * setting, and any constraints. + */ + prompt: string; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; + + /** + * Optional brand logo image to guide the generated marketing stock image. See + * [our docs](/assets/inputs#images) on image inputs. + */ + referenceImage?: RecipeMarketingStockImageParams.ReferenceImage; +} + +export namespace RecipeMarketingStockImageParams { + /** + * Optional brand logo image to guide the generated marketing stock image. See + * [our docs](/assets/inputs#images) on image inputs. + */ + export interface ReferenceImage { + /** + * A HTTPS URL. + */ + uri: string; + } +} + +export type RecipeMultiShotVideoParams = + | RecipeMultiShotVideoParams.Variant0 + | RecipeMultiShotVideoParams.Variant1; + +export declare namespace RecipeMultiShotVideoParams { + export interface Variant0 { + /** + * Workflow mode. `auto` decomposes a story prompt into exactly 5 shots. + */ + mode: 'auto'; + + /** + * Story prompt for auto mode. + */ + prompt: string; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; + + /** + * Whether to generate audio for the video. + */ + audio?: boolean; + + /** + * Total duration of the output video in seconds. Defaults to 10 seconds. + */ + duration?: 5 | 10 | 15; + + /** + * Optional image used as the first frame of the output video. See + * [our docs](/assets/inputs#images) on image inputs. + */ + firstFrame?: Variant0.FirstFrame; + + /** + * Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`, + * `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`, + * `1440:1440`) use the pro tier. Defaults to `1280:720`. + */ + ratio?: '1280:720' | '720:1280' | '960:960' | '1920:1080' | '1080:1920' | '1440:1440'; + } + + export namespace Variant0 { + /** + * Optional image used as the first frame of the output video. See + * [our docs](/assets/inputs#images) on image inputs. + */ + export interface FirstFrame { + /** + * A HTTPS URL. + */ + uri: string; + } + } + + export interface Variant1 { + /** + * Workflow mode. `custom` polishes a user-provided shot list of 3–5 shots. + */ + mode: 'custom'; + + /** + * Shot list for custom mode (3–5 shots). Per-shot durations must sum to + * `duration`. + */ + shots: Array; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; + + /** + * Whether to generate audio for the video. + */ + audio?: boolean; + + /** + * Total duration of the output video in seconds. Defaults to 10 seconds. + */ + duration?: 5 | 10 | 15; + + /** + * Optional image used as the first frame of the output video. See + * [our docs](/assets/inputs#images) on image inputs. + */ + firstFrame?: Variant1.FirstFrame; + + /** + * Output dimensions as width:height. 720p ratios (`1280:720`, `720:1280`, + * `960:960`) use the standard tier; 1080p ratios (`1920:1080`, `1080:1920`, + * `1440:1440`) use the pro tier. Defaults to `1280:720`. + */ + ratio?: '1280:720' | '720:1280' | '960:960' | '1920:1080' | '1080:1920' | '1440:1440'; + } + + export namespace Variant1 { + export interface Shot { + /** + * Duration of this shot in seconds. + */ + duration: number; + + /** + * Shot description prompt. + */ + prompt: string; + } + + /** + * Optional image used as the first frame of the output video. See + * [our docs](/assets/inputs#images) on image inputs. + */ + export interface FirstFrame { + /** + * A HTTPS URL. + */ + uri: string; + } + } +} + +export interface RecipeProductAdParams { + /** + * Product images (1–10). Multiple angles of the same product. All images inform + * product analysis and reference generation; only the first image is used as the + * primary product reference in the storyboard grid. See + * [our docs](/assets/inputs#images) on image inputs. + */ + productImages: Array; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; + + /** + * Whether to generate audio for the video. + */ + audio?: boolean; + + /** + * Duration of the output video in seconds (4–15). Defaults to 10 seconds. + */ + duration?: number; + + /** + * Optional product description and specifications to inform creative direction and + * which product elements to highlight. + */ + productInfo?: string; + + /** + * The resolution of the output video. + */ + ratio?: + | '1280:720' + | '720:1280' + | '960:960' + | '834:1112' + | '1920:1080' + | '1080:1920' + | '1440:1440' + | '1248:1664'; + + /** + * Optional style reference images (0–4). Defines the visual treatment (lighting, + * palette, mood). Treated as a moodboard when multiple are provided. + */ + styleImages?: Array; + + /** + * Optional creative direction describing brand voice, product framing, scene + * specifics, lighting, camera motion, and narrative. + */ + userConcept?: string; +} + +export namespace RecipeProductAdParams { + export interface ProductImage { + /** + * A HTTPS URL. + */ + uri: string; + } + + export interface StyleImage { + /** + * A HTTPS URL. + */ + uri: string; + } +} + +export interface RecipeProductCampaignImageParams { + /** + * Product image to preserve across the generated campaign. See + * [our docs](/assets/inputs#images) on image inputs. + */ + image: RecipeProductCampaignImageParams.Image; + + /** + * Style / creative brief for the fashion campaign, e.g. "High-key fashion + * editorial, gorpcore-meets-blokecore-meets-Y2K". + */ + prompt: string; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; +} + +export namespace RecipeProductCampaignImageParams { + /** + * Product image to preserve across the generated campaign. See + * [our docs](/assets/inputs#images) on image inputs. + */ + export interface Image { + /** + * A HTTPS URL. + */ + uri: string; + } +} + +export interface RecipeProductSwapParams { + /** + * Reference images of the new product (1–10). Supply multiple angles when the + * reference video shows the product from different views — optionally label each + * with `view` ("front", "side", or "back"). A single pre-composed reference sheet + * is also supported (omit `view`). See [our docs](/assets/inputs#images) on image + * inputs. + */ + newProductImages: Array; + + /** + * Image of the original product being swapped out. See + * [our docs](/assets/inputs#images) on image inputs. + */ + originalProductImage: RecipeProductSwapParams.OriginalProductImage; + + /** + * Reference video containing the product to swap. Duration must be between 1.8 and + * 15 seconds. See [our docs](/assets/inputs#videos) on video inputs. + */ + referenceVideo: RecipeProductSwapParams.ReferenceVideo; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; + + /** + * Whether to generate audio for the video. + */ + audio?: boolean; + + /** + * Duration of the output video in seconds (4–15). Defaults to 10 seconds. + */ + duration?: number; + + /** + * Output video resolution. Defaults to 720p. + */ + resolution?: '720p' | '1080p'; +} + +export namespace RecipeProductSwapParams { + export interface NewProductImage { + /** + * A HTTPS URL. + */ + uri: string; + + /** + * Optional view label for this reference (front, side, or back). Omit when + * supplying a single reference sheet or when view labels are unknown. + */ + view?: 'front' | 'side' | 'back'; + } + + /** + * Image of the original product being swapped out. See + * [our docs](/assets/inputs#images) on image inputs. + */ + export interface OriginalProductImage { + /** + * A HTTPS URL. + */ + uri: string; + } + + /** + * Reference video containing the product to swap. Duration must be between 1.8 and + * 15 seconds. See [our docs](/assets/inputs#videos) on video inputs. + */ + export interface ReferenceVideo { + /** + * A HTTPS URL. + */ + uri: string; + } +} + +export interface RecipeProductUgcParams { + /** + * Image of the character who will appear on camera in the UGC video. Aspect ratio + * (width / height) must be between 0.4 and 4. See + * [our docs](/assets/inputs#images) for image input requirements. + */ + characterImage: RecipeProductUgcParams.CharacterImage; + + /** + * Image of the product being promoted. Aspect ratio (width / height) must be + * between 0.4 and 4. See [our docs](/assets/inputs#images) for image input + * requirements. + */ + productImage: RecipeProductUgcParams.ProductImage; + + /** + * Workflow version. Use a dated version (e.g. "2026-06") to pin behavior, or + * "unsafe-latest" to track the newest stable version (may break without notice). + */ + version: '2026-06' | 'unsafe-latest'; + + /** + * Whether to generate audio for the video. + */ + audio?: boolean; + + /** + * Duration of the output video in seconds (4–15). Defaults to 15 seconds. + */ + duration?: number; + + /** + * Product details and creative brief — what the product is, key benefits, and any + * specifics the script should reference. + */ + productInfo?: string; + + /** + * The resolution of the output video. + */ + ratio?: '720:1280' | '1080:1920'; + + /** + * Optional creative direction for the UGC video — tone, voice register, specific + * message, or an entire dialog script. + */ + userConcept?: string; +} + +export namespace RecipeProductUgcParams { + /** + * Image of the character who will appear on camera in the UGC video. Aspect ratio + * (width / height) must be between 0.4 and 4. See + * [our docs](/assets/inputs#images) for image input requirements. + */ + export interface CharacterImage { + /** + * A HTTPS URL. + */ + uri: string; + } + + /** + * Image of the product being promoted. Aspect ratio (width / height) must be + * between 0.4 and 4. See [our docs](/assets/inputs#images) for image input + * requirements. + */ + export interface ProductImage { + /** + * A HTTPS URL. + */ + uri: string; + } +} + +export declare namespace Recipes { + export { + type RecipeMarketingStockImageResponse as RecipeMarketingStockImageResponse, + type RecipeMultiShotVideoResponse as RecipeMultiShotVideoResponse, + type RecipeProductAdResponse as RecipeProductAdResponse, + type RecipeProductCampaignImageResponse as RecipeProductCampaignImageResponse, + type RecipeProductSwapResponse as RecipeProductSwapResponse, + type RecipeProductUgcResponse as RecipeProductUgcResponse, + type RecipeMarketingStockImageParams as RecipeMarketingStockImageParams, + type RecipeMultiShotVideoParams as RecipeMultiShotVideoParams, + type RecipeProductAdParams as RecipeProductAdParams, + type RecipeProductCampaignImageParams as RecipeProductCampaignImageParams, + type RecipeProductSwapParams as RecipeProductSwapParams, + type RecipeProductUgcParams as RecipeProductUgcParams, + }; +} diff --git a/tests/api-resources/recipes.test.ts b/tests/api-resources/recipes.test.ts new file mode 100644 index 0000000..814acb0 --- /dev/null +++ b/tests/api-resources/recipes.test.ts @@ -0,0 +1,162 @@ +// File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. + +import RunwayML from '@runwayml/sdk'; + +const client = new RunwayML({ + apiKey: 'My API Key', + baseURL: process.env['TEST_API_BASE_URL'] ?? 'http://127.0.0.1:4010', +}); + +describe('resource recipes', () => { + test('marketingStockImage: only required params', async () => { + const responsePromise = client.recipes.marketingStockImage({ prompt: 'x', version: '2026-06' }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('marketingStockImage: required and optional params', async () => { + const response = await client.recipes.marketingStockImage({ + prompt: 'x', + version: '2026-06', + referenceImage: { uri: 'https://example.com/file' }, + }); + }); + + test('multiShotVideo: only required params', async () => { + const responsePromise = client.recipes.multiShotVideo({ + mode: 'auto', + prompt: 'x', + version: '2026-06', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('multiShotVideo: required and optional params', async () => { + const response = await client.recipes.multiShotVideo({ + mode: 'auto', + prompt: 'x', + version: '2026-06', + audio: true, + duration: 5, + firstFrame: { uri: 'https://example.com/file' }, + ratio: '1280:720', + }); + }); + + test('productAd: only required params', async () => { + const responsePromise = client.recipes.productAd({ + productImages: [{ uri: 'https://example.com/file' }], + version: '2026-06', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('productAd: required and optional params', async () => { + const response = await client.recipes.productAd({ + productImages: [{ uri: 'https://example.com/file' }], + version: '2026-06', + audio: true, + duration: 4, + productInfo: 'productInfo', + ratio: '1280:720', + styleImages: [{ uri: 'https://example.com/file' }], + userConcept: 'userConcept', + }); + }); + + test('productCampaignImage: only required params', async () => { + const responsePromise = client.recipes.productCampaignImage({ + image: { uri: 'https://example.com/file' }, + prompt: 'x', + version: '2026-06', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('productCampaignImage: required and optional params', async () => { + const response = await client.recipes.productCampaignImage({ + image: { uri: 'https://example.com/file' }, + prompt: 'x', + version: '2026-06', + }); + }); + + test('productSwap: only required params', async () => { + const responsePromise = client.recipes.productSwap({ + newProductImages: [{ uri: 'https://example.com/file' }], + originalProductImage: { uri: 'https://example.com/file' }, + referenceVideo: { uri: 'https://example.com/file' }, + version: '2026-06', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('productSwap: required and optional params', async () => { + const response = await client.recipes.productSwap({ + newProductImages: [{ uri: 'https://example.com/file', view: 'front' }], + originalProductImage: { uri: 'https://example.com/file' }, + referenceVideo: { uri: 'https://example.com/file' }, + version: '2026-06', + audio: true, + duration: 4, + resolution: '720p', + }); + }); + + test('productUgc: only required params', async () => { + const responsePromise = client.recipes.productUgc({ + characterImage: { uri: 'https://example.com/file' }, + productImage: { uri: 'https://example.com/file' }, + version: '2026-06', + }); + const rawResponse = await responsePromise.asResponse(); + expect(rawResponse).toBeInstanceOf(Response); + const response = await responsePromise; + expect(response).not.toBeInstanceOf(Response); + const dataAndResponse = await responsePromise.withResponse(); + expect(dataAndResponse.data).toBe(response); + expect(dataAndResponse.response).toBe(rawResponse); + }); + + test('productUgc: required and optional params', async () => { + const response = await client.recipes.productUgc({ + characterImage: { uri: 'https://example.com/file' }, + productImage: { uri: 'https://example.com/file' }, + version: '2026-06', + audio: true, + duration: 4, + productInfo: 'productInfo', + ratio: '720:1280', + userConcept: 'userConcept', + }); + }); +}); From 2806992850b4bacdd24afa5154e63cbcb2c06e52 Mon Sep 17 00:00:00 2001 From: Matt Basta Date: Wed, 17 Jun 2026 14:20:08 -0400 Subject: [PATCH 5/6] feat(client): Make recipes waitable --- src/resources/recipes.ts | 44 ++++++++++++++++++++++++++++------------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/src/resources/recipes.ts b/src/resources/recipes.ts index e5b6c9e..e64ca4f 100644 --- a/src/resources/recipes.ts +++ b/src/resources/recipes.ts @@ -1,8 +1,8 @@ // File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details. import { APIResource } from '../core/resource'; -import { APIPromise } from '../core/api-promise'; import { RequestOptions } from '../internal/request-options'; +import { APIPromiseWithAwaitableTask, wrapAsWaitableResource } from '../lib/polling'; export class Recipes extends APIResource { /** @@ -12,8 +12,10 @@ export class Recipes extends APIResource { marketingStockImage( body: RecipeMarketingStockImageParams, options?: RequestOptions, - ): APIPromise { - return this._client.post('/v1/recipes/marketing_stock_image', { body, ...options }); + ): APIPromiseWithAwaitableTask { + return wrapAsWaitableResource(this._client)( + this._client.post('/v1/recipes/marketing_stock_image', { body, ...options }), + ); } /** @@ -23,16 +25,23 @@ export class Recipes extends APIResource { multiShotVideo( body: RecipeMultiShotVideoParams, options?: RequestOptions, - ): APIPromise { - return this._client.post('/v1/recipes/multi_shot_video', { body, ...options }); + ): APIPromiseWithAwaitableTask { + return wrapAsWaitableResource(this._client)( + this._client.post('/v1/recipes/multi_shot_video', { body, ...options }), + ); } /** * Generate a cinematic product ad from product images, optional style references, * product info, and creative direction. */ - productAd(body: RecipeProductAdParams, options?: RequestOptions): APIPromise { - return this._client.post('/v1/recipes/product_ad', { body, ...options }); + productAd( + body: RecipeProductAdParams, + options?: RequestOptions, + ): APIPromiseWithAwaitableTask { + return wrapAsWaitableResource(this._client)( + this._client.post('/v1/recipes/product_ad', { body, ...options }), + ); } /** @@ -41,8 +50,10 @@ export class Recipes extends APIResource { productCampaignImage( body: RecipeProductCampaignImageParams, options?: RequestOptions, - ): APIPromise { - return this._client.post('/v1/recipes/product_campaign_image', { body, ...options }); + ): APIPromiseWithAwaitableTask { + return wrapAsWaitableResource(this._client)( + this._client.post('/v1/recipes/product_campaign_image', { body, ...options }), + ); } /** @@ -52,16 +63,23 @@ export class Recipes extends APIResource { productSwap( body: RecipeProductSwapParams, options?: RequestOptions, - ): APIPromise { - return this._client.post('/v1/recipes/product_swap', { body, ...options }); + ): APIPromiseWithAwaitableTask { + return wrapAsWaitableResource(this._client)( + this._client.post('/v1/recipes/product_swap', { body, ...options }), + ); } /** * Generate a vertical user-generated content ad from a character image, product * image, product details, and optional creative direction. */ - productUgc(body: RecipeProductUgcParams, options?: RequestOptions): APIPromise { - return this._client.post('/v1/recipes/product_ugc', { body, ...options }); + productUgc( + body: RecipeProductUgcParams, + options?: RequestOptions, + ): APIPromiseWithAwaitableTask { + return wrapAsWaitableResource(this._client)( + this._client.post('/v1/recipes/product_ugc', { body, ...options }), + ); } } From 04a6832f755b7b0077c8438c84a9dbff06e4fbe1 Mon Sep 17 00:00:00 2001 From: "stainless-app[bot]" <142633134+stainless-app[bot]@users.noreply.github.com> Date: Wed, 17 Jun 2026 18:20:39 +0000 Subject: [PATCH 6/6] release: 4.2.0 --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- src/version.ts | 2 +- 4 files changed, 19 insertions(+), 3 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 411256b..34a3350 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "4.1.0" + ".": "4.2.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d06330d..c92a015 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 4.2.0 (2026-06-17) + +Full Changelog: [v4.1.0...v4.2.0](https://github.com/runwayml/sdk-node/compare/v4.1.0...v4.2.0) + +### Features + +* **api:** Recipes, aleph2 range ([05ae79c](https://github.com/runwayml/sdk-node/commit/05ae79c87f5bfc60545c5e8271a9f19c29728085)) +* **client:** add recipe endpoints to config ([8509e2b](https://github.com/runwayml/sdk-node/commit/8509e2bf6a6350873e69186e01a6bf71121f0a1c)) +* **client:** Make recipes waitable ([2806992](https://github.com/runwayml/sdk-node/commit/2806992850b4bacdd24afa5154e63cbcb2c06e52)) + + +### Bug Fixes + +* **api:** remove range param ([a602a48](https://github.com/runwayml/sdk-node/commit/a602a48b1a6eef9bdc845f09851fa58100c2d155)) +* **client:** send content-type header for requests with an omitted optional body ([b26f126](https://github.com/runwayml/sdk-node/commit/b26f126323c83fca99555e7b897fcda048ccd098)) + ## 4.1.0 (2026-06-12) Full Changelog: [v4.0.0...v4.1.0](https://github.com/runwayml/sdk-node/compare/v4.0.0...v4.1.0) diff --git a/package.json b/package.json index b6efd39..f33eab9 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@runwayml/sdk", - "version": "4.1.0", + "version": "4.2.0", "description": "The official TypeScript library for the RunwayML API", "author": "RunwayML ", "types": "dist/index.d.ts", diff --git a/src/version.ts b/src/version.ts index be6bae1..f9ec1c7 100644 --- a/src/version.ts +++ b/src/version.ts @@ -1 +1 @@ -export const VERSION = '4.1.0'; // x-release-please-version +export const VERSION = '4.2.0'; // x-release-please-version