@nodaro/sdk 2.43.0 → 2.45.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-ETX3TLNB.cjs → chunk-22P4X3N5.cjs} +16 -5
- package/dist/chunk-22P4X3N5.cjs.map +1 -0
- package/dist/{chunk-JUON5NXA.js → chunk-33HT46A3.js} +13 -2
- package/dist/chunk-33HT46A3.js.map +1 -0
- package/dist/{chunk-V3KMBQMK.js → chunk-3Y5TV37H.js} +3 -3
- package/dist/{chunk-V3KMBQMK.js.map → chunk-3Y5TV37H.js.map} +1 -1
- package/dist/{chunk-F6GENF6R.cjs → chunk-55ZX6TE3.cjs} +12 -9
- package/dist/chunk-55ZX6TE3.cjs.map +1 -0
- package/dist/{chunk-2VJOHJUZ.cjs → chunk-5IXZLQNT.cjs} +2 -2
- package/dist/chunk-5IXZLQNT.cjs.map +1 -0
- package/dist/{chunk-IGMWFEFB.js → chunk-5MOJSYHI.js} +2 -2
- package/dist/chunk-5MOJSYHI.js.map +1 -0
- package/dist/{chunk-FQNCIP6K.js → chunk-7LODCIQK.js} +2 -2
- package/dist/chunk-7LODCIQK.js.map +1 -0
- package/dist/{chunk-OSX2GVZ2.cjs → chunk-7YHIFCJ4.cjs} +2 -2
- package/dist/chunk-7YHIFCJ4.cjs.map +1 -0
- package/dist/{chunk-6QFTWAQQ.cjs → chunk-A7HOOWKS.cjs} +26 -26
- package/dist/{chunk-6QFTWAQQ.cjs.map → chunk-A7HOOWKS.cjs.map} +1 -1
- package/dist/{chunk-DQAYIVVS.js → chunk-BQAW4RX2.js} +16 -5
- package/dist/chunk-BQAW4RX2.js.map +1 -0
- package/dist/{chunk-WK7C7TP6.js → chunk-CKHCSZCV.js} +2 -2
- package/dist/chunk-CKHCSZCV.js.map +1 -0
- package/dist/{chunk-PQ7OKM3E.cjs → chunk-FIMBPKIH.cjs} +2 -2
- package/dist/chunk-FIMBPKIH.cjs.map +1 -0
- package/dist/{chunk-RQH6IKDC.js → chunk-HKDIIFG2.js} +12 -9
- package/dist/chunk-HKDIIFG2.js.map +1 -0
- package/dist/{chunk-YSZM3N7Q.js → chunk-HPXE3NZW.js} +2 -2
- package/dist/{chunk-YSZM3N7Q.js.map → chunk-HPXE3NZW.js.map} +1 -1
- package/dist/{chunk-VADPSFLS.cjs → chunk-J6FHBQ4Z.cjs} +2 -2
- package/dist/chunk-J6FHBQ4Z.cjs.map +1 -0
- package/dist/{chunk-ZWPDWFN2.js → chunk-JB4GVP4Q.js} +2 -2
- package/dist/chunk-JB4GVP4Q.js.map +1 -0
- package/dist/{chunk-OIYMTGYN.cjs → chunk-JOXZ4ZIF.cjs} +2 -2
- package/dist/chunk-JOXZ4ZIF.cjs.map +1 -0
- package/dist/{chunk-BLA44UCM.js → chunk-KFZOJBI5.js} +2 -2
- package/dist/chunk-KFZOJBI5.js.map +1 -0
- package/dist/{chunk-FHVNTS4E.cjs → chunk-KMNSPC72.cjs} +2 -2
- package/dist/{chunk-FHVNTS4E.cjs.map → chunk-KMNSPC72.cjs.map} +1 -1
- package/dist/{chunk-ILG5DHQQ.js → chunk-M7VSFZMV.js} +2 -2
- package/dist/chunk-M7VSFZMV.js.map +1 -0
- package/dist/{chunk-Z7UZ3FD3.js → chunk-RBX3QTKB.js} +2 -2
- package/dist/{chunk-Z7UZ3FD3.js.map → chunk-RBX3QTKB.js.map} +1 -1
- package/dist/{chunk-VACAWIZT.js → chunk-RQY2WJD3.js} +14 -14
- package/dist/{chunk-VACAWIZT.js.map → chunk-RQY2WJD3.js.map} +1 -1
- package/dist/{chunk-7XOK277W.cjs → chunk-SF2CMKWA.cjs} +2 -2
- package/dist/{chunk-7XOK277W.cjs.map → chunk-SF2CMKWA.cjs.map} +1 -1
- package/dist/{chunk-EK672FFM.cjs → chunk-UIBYZNDR.cjs} +2 -2
- package/dist/chunk-UIBYZNDR.cjs.map +1 -0
- package/dist/{chunk-TMRRXQQL.cjs → chunk-WFWZBYBS.cjs} +3 -3
- package/dist/{chunk-TMRRXQQL.cjs.map → chunk-WFWZBYBS.cjs.map} +1 -1
- package/dist/{chunk-VHSMOBU7.cjs → chunk-WUM6CWTE.cjs} +13 -2
- package/dist/chunk-WUM6CWTE.cjs.map +1 -0
- package/dist/entries/apps.cjs +2 -2
- package/dist/entries/apps.d.cts +1 -1
- package/dist/entries/apps.d.ts +1 -1
- package/dist/entries/apps.js +1 -1
- package/dist/entries/cast-swap.cjs +2 -2
- package/dist/entries/cast-swap.d.cts +104 -30
- package/dist/entries/cast-swap.d.ts +104 -30
- package/dist/entries/cast-swap.js +1 -1
- package/dist/entries/characters.d.cts +1 -1
- package/dist/entries/characters.d.ts +1 -1
- package/dist/entries/client.cjs +18 -18
- package/dist/entries/client.d.cts +1 -1
- package/dist/entries/client.d.ts +1 -1
- package/dist/entries/client.js +13 -13
- package/dist/entries/core.cjs +5 -5
- package/dist/entries/core.js +1 -1
- package/dist/entries/creatures.d.cts +1 -1
- package/dist/entries/creatures.d.ts +1 -1
- package/dist/entries/credits.cjs +2 -2
- package/dist/entries/credits.d.cts +2 -2
- package/dist/entries/credits.d.ts +2 -2
- package/dist/entries/credits.js +1 -1
- package/dist/entries/edit.cjs +6 -6
- package/dist/entries/edit.d.cts +1 -1
- package/dist/entries/edit.d.ts +1 -1
- package/dist/entries/edit.js +1 -1
- package/dist/entries/locations.d.cts +1 -1
- package/dist/entries/locations.d.ts +1 -1
- package/dist/entries/media.cjs +2 -2
- package/dist/entries/media.d.cts +14 -3
- package/dist/entries/media.d.ts +14 -3
- package/dist/entries/media.js +1 -1
- package/dist/entries/nodes.cjs +2 -2
- package/dist/entries/nodes.d.cts +1 -1
- package/dist/entries/nodes.d.ts +1 -1
- package/dist/entries/nodes.js +1 -1
- package/dist/entries/objects.d.cts +1 -1
- package/dist/entries/objects.d.ts +1 -1
- package/dist/entries/picker-catalogs.cjs +2 -2
- package/dist/entries/picker-catalogs.d.cts +8 -0
- package/dist/entries/picker-catalogs.d.ts +8 -0
- package/dist/entries/picker-catalogs.js +1 -1
- package/dist/entries/scene3d.d.cts +2 -2
- package/dist/entries/scene3d.d.ts +2 -2
- package/dist/entries/templates.cjs +2 -2
- package/dist/entries/templates.d.cts +1 -1
- package/dist/entries/templates.d.ts +1 -1
- package/dist/entries/templates.js +1 -1
- package/dist/entries/trends.cjs +2 -2
- package/dist/entries/trends.d.cts +87 -12
- package/dist/entries/trends.d.ts +87 -12
- package/dist/entries/trends.js +1 -1
- package/dist/entries/tutorials.cjs +2 -2
- package/dist/entries/tutorials.d.cts +1 -1
- package/dist/entries/tutorials.d.ts +1 -1
- package/dist/entries/tutorials.js +1 -1
- package/dist/entries/workflows.cjs +2 -2
- package/dist/entries/workflows.d.cts +76 -5
- package/dist/entries/workflows.d.ts +76 -5
- package/dist/entries/workflows.js +1 -1
- package/dist/index.cjs +34 -34
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +13 -13
- package/dist/{nodes-B4ZEqx2V.d.ts → nodes-MThDit8l.d.ts} +1 -1
- package/dist/{nodes-DzBGNOUV.d.cts → nodes-Szl6HrZ_.d.cts} +1 -1
- package/package.json +3 -3
- package/dist/chunk-2VJOHJUZ.cjs.map +0 -1
- package/dist/chunk-BLA44UCM.js.map +0 -1
- package/dist/chunk-DQAYIVVS.js.map +0 -1
- package/dist/chunk-EK672FFM.cjs.map +0 -1
- package/dist/chunk-ETX3TLNB.cjs.map +0 -1
- package/dist/chunk-F6GENF6R.cjs.map +0 -1
- package/dist/chunk-FQNCIP6K.js.map +0 -1
- package/dist/chunk-IGMWFEFB.js.map +0 -1
- package/dist/chunk-ILG5DHQQ.js.map +0 -1
- package/dist/chunk-JUON5NXA.js.map +0 -1
- package/dist/chunk-OIYMTGYN.cjs.map +0 -1
- package/dist/chunk-OSX2GVZ2.cjs.map +0 -1
- package/dist/chunk-PQ7OKM3E.cjs.map +0 -1
- package/dist/chunk-RQH6IKDC.js.map +0 -1
- package/dist/chunk-VADPSFLS.cjs.map +0 -1
- package/dist/chunk-VHSMOBU7.cjs.map +0 -1
- package/dist/chunk-WK7C7TP6.js.map +0 -1
- package/dist/chunk-ZWPDWFN2.js.map +0 -1
|
@@ -241,13 +241,16 @@ var MediaResource = class {
|
|
|
241
241
|
* `height`, `frames`, `coverage` (the fraction of pixels selected, rounded
|
|
242
242
|
* to 4 decimals) and `kept` (any region left on any frame: false only when
|
|
243
243
|
* the size / zone filters left an empty mask — read it rather than
|
|
244
|
-
* `coverage`, which a few pixels round to 0)
|
|
244
|
+
* `coverage`, which a few pixels round to 0), and `recipe` (the version of
|
|
245
|
+
* the masking algorithm that made it, `VIDEO_MASK_RECIPE`: it changes
|
|
246
|
+
* whenever the same clip and settings would give a different mask). Feed the mask to
|
|
245
247
|
* {@link MediaResource.maskComposite}.
|
|
246
248
|
*
|
|
247
249
|
* `holdLastMask: true` keeps the last mask on frames where the subject is
|
|
248
250
|
* lost (a mask covering under 0.002 % of the frame, e.g. a face behind a
|
|
249
251
|
* hand): each such frame after the first detection repeats the most recent
|
|
250
|
-
* non-empty one
|
|
252
|
+
* non-empty one, for at most 1 s of frames (`VIDEO_MASK_LIMITS.HOLD_MAX_SEC`;
|
|
253
|
+
* longer gaps stay empty after that); the output then also carries `heldFrames`.
|
|
251
254
|
* `minSubjectHeightPct` (0–100, default 0 = off) drops every connected
|
|
252
255
|
* region whose bounding box is shorter than that % of the frame height
|
|
253
256
|
* (extras' small faces behind the main subject); it runs before
|
|
@@ -263,7 +266,15 @@ var MediaResource = class {
|
|
|
263
266
|
* detected again after every cut, `holdLastMask` never crosses a cut, and
|
|
264
267
|
* each shot's mask is fitted to that shot's exact frame count; on a clip
|
|
265
268
|
* with no cut it gives the same mask, fitted to the clip's exact frame
|
|
266
|
-
* count. The output carries `shots` (1 when off).
|
|
269
|
+
* count. The output carries `shots` (1 when off). `redetectEverySec` (0 =
|
|
270
|
+
* off, the default, or 1–10) also detects the subject again every that
|
|
271
|
+
* many seconds — the clip (each shot, with `perShot`) is sent to the model
|
|
272
|
+
* in pieces of at most that length, a last piece under 8 frames joining the
|
|
273
|
+
* one before — so someone who walks in or turns to the camera mid-shot is
|
|
274
|
+
* masked from the next boundary on. These boundaries are not cuts:
|
|
275
|
+
* `holdLastMask` holds across them. Each piece is one more model request
|
|
276
|
+
* (three run at a time), so it is slower; the price does not change. The
|
|
277
|
+
* output's `chunks` counts the requests.
|
|
267
278
|
*/
|
|
268
279
|
videoMask(input) {
|
|
269
280
|
return this.client.request("POST", "/v1/video-mask", { body: input });
|
|
@@ -312,5 +323,5 @@ var MediaResource = class {
|
|
|
312
323
|
};
|
|
313
324
|
|
|
314
325
|
exports.MediaResource = MediaResource;
|
|
315
|
-
//# sourceMappingURL=chunk-
|
|
316
|
-
//# sourceMappingURL=chunk-
|
|
326
|
+
//# sourceMappingURL=chunk-22P4X3N5.cjs.map
|
|
327
|
+
//# sourceMappingURL=chunk-22P4X3N5.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resources/media.ts"],"names":["readSseStream"],"mappings":"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","file":"chunk-22P4X3N5.cjs","sourcesContent":["import type { NodaroClientCore } from \"../core.js\"\nimport { readSseStream } from \"../sse.js\"\nimport type { OverlayAnchor, VideoMaskRequest, MaskCompositeRequest } from \"@nodaro/shared/media\"\nimport type { CaptionStyle, CaptionLookId, SupportedFontName } from \"@nodaro/shared/captions\"\nimport type { TranscribeLane } from \"@nodaro/shared/models\"\nimport type { VideoOverlayRequest } from \"@nodaro/shared/video\"\n\n/** One word-timed caption entry (one per WORD for the kinetic styles). */\nexport interface CaptionEntry {\n text: string\n startMs: number\n endMs: number\n timestampMs?: number | null\n confidence?: number | null\n}\n\n/** Look + motion levers — see {@link MediaResource.addCaptions}. `look` is a\n * named preset the explicit levers override; an unset look renders as `outline`\n * (the default). The STYLING levers (`look`, `fontFamily`, `fontWeight`,\n * `strokeColor`/`strokeWidth`, `uppercase`, `positionY`, `maxWordsPerLine`) now\n * apply to the static `subtitle` style too — a subtitle carrying any of them\n * renders via Remotion. Only `highlightColor` (no spoken-word cursor to colour)\n * and `animate` (no motion to freeze) stay kinetic-only and are rejected (400)\n * on `subtitle`. */\nexport interface CaptionLookInput {\n look?: CaptionLookId\n fontFamily?: SupportedFontName\n fontWeight?: number\n strokeColor?: string\n strokeWidth?: number\n highlightColor?: string\n uppercase?: boolean\n positionY?: number\n /** Cap on how many words one caption LINE (or `tiktok-words` page) may hold,\n * 1-20, on TOP of the frame-width budget, sentence ends and pauses that\n * already close a line — 1-2 gives the punchy CapCut read, unset fits the\n * width. Applies to `word-highlight` / `karaoke` / `bouncy` / `tiktok-words`\n * and to a Remotion-rendered `subtitle`; inert on `word-pop` (always one\n * word). */\n maxWordsPerLine?: number\n /** Kinetic styles only. `false` freezes the per-word MOTION — the grouping,\n * line-holding and spoken-word highlight stay, only the movement stops;\n * default `true`. Rejected (400) on the static `subtitle` style. */\n animate?: boolean\n}\n\n/** One caption SEGMENT: a time range with optional style/look overrides and its\n * own words (`text`/`captions`, else the shared transcript filtered to range).\n * A segmented render is entirely Remotion, so any `style` is valid here. */\nexport interface CaptionSegmentInput extends CaptionLookInput {\n startMs: number\n endMs: number\n style?: CaptionStyle\n position?: \"bottom\" | \"top\" | \"center\"\n fontSize?: number\n color?: string\n backgroundColor?: string\n text?: string\n captions?: CaptionEntry[]\n}\n\n/** {@link MediaResource.addCaptions} input. */\nexport interface AddCaptionsInput extends CaptionLookInput {\n videoUrl: string\n text?: string\n captions?: CaptionEntry[]\n /** Transcribe the video's audio when no text/captions are given (default true). */\n autoTranscribe?: boolean\n transcribeProvider?: TranscribeLane\n style?: CaptionStyle\n position?: \"bottom\" | \"top\" | \"center\"\n fontSize?: number\n color?: string\n backgroundColor?: string\n /** Apply different treatments to time ranges in one call (non-overlapping). */\n segments?: CaptionSegmentInput[]\n}\n\n/**\n * Media ingestion + trimming — the source-preparation steps a Voice Changer Pro\n * flow (or any pipeline) needs before it has a clip to work on: pull a social\n * video into storage, copy a remote URL into storage, trim a video/audio to a\n * range, and probe a video's metadata. Each generation-style op returns a job id\n * to poll (`jobs.get(jobId)`); `videoMetadata` is a direct read.\n */\n/**\n * Where one overlay layer should go, in {@link MediaResource.imageOverlay}'s\n * percent units — the answer from {@link MediaResource.suggestOverlayPlacement}.\n */\nexport interface OverlayPlacement {\n /** The point on the base image the layer attaches to. */\n anchor: OverlayAnchor\n /** Offset from the anchor in % of the base width (negative on a right anchor = inward). */\n x: number\n /** Offset from the anchor in % of the base height (negative on a bottom anchor = inward). */\n y: number\n /** The layer's width in % of the base width. */\n width: number\n /** One sentence, plain language, on why this spot. */\n reason: string\n}\n\nexport class MediaResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * Download a social video (YouTube / TikTok / Instagram / X / Facebook) into\n * your storage (`POST /v1/download-video`). `maxHeight` caps the resolution\n * (default \"best\"); `sectionStartSec` + `sectionEndSec` (both-or-neither) fetch\n * ONLY that time range instead of the whole video. A download that arrives\n * with no audio stream FAILS by default (it is usually a degraded source\n * response, and is retried through other routes first); pass\n * `requireAudio: false` to accept a clip that really has no sound. Returns a\n * `downloadId`; progress streams from\n * `GET /v1/download-video/progress/:downloadId` (server-sent events) and the\n * finished file lands in your library.\n */\n downloadVideo(input: {\n url: string\n maxHeight?: number\n sectionStartSec?: number\n sectionEndSec?: number\n requireAudio?: boolean\n }): Promise<{ downloadId: string }> {\n return this.client.request<{ downloadId: string }>(\"POST\", \"/v1/download-video\", { body: input })\n }\n\n /**\n * Stream the live progress of a {@link MediaResource.downloadVideo} import\n * (`GET /v1/download-video/progress/:downloadId`, server-sent events) as an\n * async iterable. Yields a {@link DownloadVideoProgress} roughly every 500ms\n * until the download reaches `completed` (its event carries the stored\n * `videoUrl`) or `failed` (its event carries `error`), then ends. The progress\n * state expires server-side shortly after the download starts existing, so\n * start iterating promptly after `downloadVideo` returns.\n *\n * No request timeout is applied (a large import legitimately takes minutes) —\n * pass an `AbortSignal` to cancel from the caller.\n */\n async *downloadVideoProgress(\n downloadId: string,\n opts: { signal?: AbortSignal } = {},\n ): AsyncGenerator<DownloadVideoProgress, void, undefined> {\n const url = `${this.client.baseUrl}/v1/download-video/progress/${encodeURIComponent(downloadId)}`\n const token = await this.client.auth.getToken()\n const res = await this.client.fetch(url, {\n headers: token ? { Authorization: `Bearer ${token}` } : {},\n signal: opts.signal,\n })\n // The shared reader owns the non-OK throw, the empty-body guard, the frame\n // parse and the cancel-on-early-exit — this route emits the same\n // `data: <json>` frames every other stream here does.\n yield* readSseStream<DownloadVideoProgress>(res, { label: \"progress stream\" })\n }\n\n /**\n * Copy an external media URL into your Nodaro storage (`POST /v1/save-to-storage`)\n * — a server-side fetch, so nothing round-trips through the client. Poll\n * `jobs.get(jobId)`.\n */\n saveToStorage(input: { mediaUrl: string; filename?: string; mediaType?: \"image\" | \"video\" | \"audio\" }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/save-to-storage\", { body: input })\n }\n\n /**\n * Composite 2–30 images into ONE large 2K/4K collage (`POST /v1/image-collage`).\n * `layout` `\"smart\"` (default — justified rows at each image's exact aspect\n * ratio, no cropping; the output height floats) or `\"grid\"` (uniform,\n * letterboxed cells). `imageSizes` is index-aligned with `imageUrls` and\n * gives per-image RELATIVE size hints for the smart layout: `0` auto\n * (\"don't care\", default), `1` big (~2× linear vs medium), `2` medium,\n * `3` small (~½ linear). All-equal hints change nothing; grid ignores them.\n * For storyboards, set `numbered` to stamp a 1-based sequence badge at each\n * image's corner (top-left by default; `badgePosition: \"top-right\"` moves it;\n * in `imageUrls` order) and pass `imageLabels`\n * (index-aligned with `imageUrls`; `null`/`\"\"`/omitted = no caption for that\n * image) to caption images after the number, e.g. `3 · Close-up`. Badges are\n * an overlay only — they never change the layout, the output size or the\n * credit cost. Poll `jobs.get(jobId)` for the finished image.\n */\n imageCollage(input: {\n imageUrls: string[]\n imageSizes?: Array<0 | 1 | 2 | 3>\n numbered?: boolean\n imageLabels?: Array<string | null>\n /** Corner the badges sit in — `\"top-left\"` (default, storyboard convention) or `\"top-right\"`. */\n badgePosition?: \"top-left\" | \"top-right\"\n layout?: \"smart\" | \"grid\"\n resolution?: \"2K\" | \"4K\"\n aspectRatio?: string\n gap?: number\n backgroundColor?: string\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/image-collage\", { body: input })\n }\n\n /**\n * Place 1–12 layers — pictures, real text, QR codes, shapes — on a base image,\n * pixel-exactly (`POST /v1/image-overlay`) — local, deterministic, no AI.\n * Every layer position/size is a PERCENT of the base image: `anchor` (nine\n * positions, default `\"center\"`), `x`/`y` (offset from the anchor in % of\n * the base width/height; negative on a right/bottom anchor moves inward),\n * `width` (% of the base width, default 25 — height follows the layer's\n * aspect unless `height` is set). Plus `opacity` (0..1), `rotation`\n * (degrees), `blend` (`\"over\"` | `\"multiply\"` | `\"screen\"`), `fit`,\n * `shadow` and `roundedCorners`. The output keeps the base's pixel size\n * unless `canvas` is set. Flat credit cost. Poll `jobs.get(jobId)` for the\n * finished image.\n */\n imageOverlay(input: {\n imageUrl: string\n layers: Array<{\n imageUrl: string\n anchor?: OverlayAnchor\n x?: number\n y?: number\n width?: number\n height?: number\n opacity?: number\n rotation?: number\n blend?: \"over\" | \"multiply\" | \"screen\"\n fit?: \"contain\" | \"cover\" | \"stretch\"\n shadow?: { blur: number; offsetX: number; offsetY: number; color: string; opacity: number }\n roundedCorners?: number\n zIndex?: number\n /** \"image\" (default, needs imageUrl) | \"text\" | \"qr\" | \"shape\" — see @nodaro/shared image-overlay-layers. */\n kind?: \"image\" | \"text\" | \"qr\" | \"shape\"\n text?: Record<string, unknown>\n qr?: Record<string, unknown>\n shape?: Record<string, unknown>\n effects?: Record<string, unknown>\n }>\n canvas?: { width: number; height: number; backgroundColor?: string }\n baseFit?: \"contain\" | \"cover\"\n outputFormat?: \"png\" | \"jpg\" | \"webp\"\n /** Extra platform renders (see OVERLAY_PLATFORMS in @nodaro/shared); returned as output `variants[]`. */\n variants?: string[]\n /** The mask the job also emits as output `maskUrl` (white = may change). Default \"around\". */\n maskMode?: \"none\" | \"layers\" | \"around\" | \"outside\"\n maskSpread?: number\n /** Fills every QR layer whose `qr.fromInput` is true — the node's QR link handle. */\n qrText?: string\n }): Promise<{ jobId: string }> {\n // The finished job's output: { imageUrl, width, height, maskUrl?, variants?: [{ id, label, width, height, url }] }.\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/image-overlay\", { body: input })\n }\n\n /**\n * Ask a vision model WHERE one overlay layer should sit on a base image\n * (`POST /v1/image-overlay/suggest-placement`) — it reads the picture and\n * keeps the element off the faces, the subject and the busiest texture.\n * The answer comes back in {@link MediaResource.imageOverlay}'s own percent\n * units — `anchor`, `x`/`y` offsets, `width` — so it drops straight onto a\n * layer (`const { reason, ...box } = placement`), plus a one-sentence\n * `reason` you can show a user. Nothing is composited here: apply the\n * placement yourself.\n *\n * `layerAspect` is the element's width / height (1 = square, the default) so\n * the proposed box stays in proportion; `intent` says what the element is\n * (\"a logo\", \"a price badge\"); `safeArea` is the always-visible region as\n * fractions of the canvas (a platform preset's safe area), which the\n * placement is kept inside. Unlike the other media calls this one answers\n * synchronously — there is nothing to poll; `jobId` is the billing record\n * (one image-to-text call).\n */\n suggestOverlayPlacement(input: {\n imageUrl: string\n layerAspect?: number\n intent?: string\n safeArea?: { x: number; y: number; w: number; h: number }\n llmModel?: string\n }): Promise<{ jobId: string; placement: OverlayPlacement }> {\n return this.client.request<{ jobId: string; placement: OverlayPlacement }>(\n \"POST\",\n \"/v1/image-overlay/suggest-placement\",\n { body: input },\n )\n }\n\n /**\n * Trim a video to a range (`POST /v1/trim-video`). Give the range in whichever\n * unit fits: `startTime`/`endTime` seconds, `trim*Frames`, `trim*Seconds`, or\n * `keepFirst`/`keepLastSeconds`. Poll `jobs.get(jobId)`.\n */\n trimVideo(input: {\n videoUrl: string\n startTime?: number\n endTime?: number\n trimStartFrames?: number\n trimEndFrames?: number\n trimStartSeconds?: number\n trimEndSeconds?: number\n keepFirstSeconds?: number\n keepLastSeconds?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/trim-video\", { body: input })\n }\n\n /**\n * Burn captions into a video (`POST /v1/add-captions`). Give the words as\n * `text`, word-timed `captions[]`, or let it transcribe (`autoTranscribe`,\n * the default when neither is set).\n *\n * `style: \"subtitle\"` renders statically (FFmpeg) UNLESS it carries a styling\n * lever, in which case it — like the KINETIC styles (`word-highlight` /\n * `karaoke` / `tiktok-words` / `word-pop` / `bouncy`) — renders via Remotion.\n * `look` picks a preset — `outline` (Montserrat 900, UPPERCASE, black outline,\n * yellow spoken word — the TikTok/Reels read) or `clean`; an UNSET look renders\n * as `outline`. The explicit STYLING levers (`fontFamily`, `fontWeight`,\n * `strokeColor`/`strokeWidth`, `uppercase`, `positionY`, `maxWordsPerLine`)\n * override individual fields of it and now apply to `subtitle` too. Only\n * `highlightColor` (the spoken-word cursor) and `animate` stay kinetic-only\n * and are REJECTED on `subtitle`.\n *\n * `animate` (default true) freezes the per-word MOTION on the kinetic styles\n * when set to false — the grouping, line-holding and spoken-word highlight\n * stay; only the movement stops.\n *\n * `maxWordsPerLine` (1-20) caps how many words a line — or a `tiktok-words`\n * page — may hold, on top of the width budget / sentence ends / pauses that\n * already close one; it is inert on `word-pop`, which is always one word.\n * Like every lever here it exists top-level AND per segment, and a segment\n * that does not name its own inherits the top-level value.\n *\n * `segments[]` applies DIFFERENT treatments to non-overlapping time ranges in\n * one call (e.g. a large top intro, then a small bottom body); a segment that\n * names its own `look` starts fresh from that preset and does not inherit the\n * top-level explicit levers. Poll `jobs.get(jobId)`.\n *\n * `word-highlight` shows ONE held line at a time and a word's\n * `startMs`/`endMs` is its SPOKEN window (what times the highlight, not how\n * long the text is on screen) — so `captions[]` can be handed over verbatim\n * from an `audio.transcribe()` job's `output_data.words`, with\n * `autoTranscribe: false`.\n */\n addCaptions(input: AddCaptionsInput): Promise<{ jobId: string }> {\n const { autoTranscribe, transcribeProvider, ...rest } = input\n // The route body is camelCase except these two flags (worker snake_case);\n // omit each when unset so an absent flag stays absent on the wire.\n const body: Record<string, unknown> = {\n ...rest,\n ...(autoTranscribe !== undefined ? { auto_transcribe: autoTranscribe } : {}),\n ...(transcribeProvider !== undefined ? { transcribe_provider: transcribeProvider } : {}),\n }\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/add-captions\", { body })\n }\n\n /**\n * Trim (and extract) audio from a video or audio source\n * (`POST /v1/trim-audio`) to `[startTime, endTime]` seconds, in `audioFormat`\n * (`mp3` default / `wav` / `aac`). Poll `jobs.get(jobId)`.\n */\n trimAudio(input: {\n videoUrl?: string\n audioUrl?: string\n audioFormat?: \"mp3\" | \"wav\" | \"aac\"\n startTime?: number\n endTime?: number\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/trim-audio\", { body: input })\n }\n\n /**\n * Cut a video and/or an audio file into consecutive parts\n * (`POST /v1/split-media`). Every part is at most `chunkDuration` seconds and\n * at least `minPartDuration` (default 4, never above `chunkDuration`) — a\n * short leftover is evened out with the parts before it. Video is cut on\n * whole frames with each part's sound matching its picture; audio parts are\n * sample-exact, in `audioFormat` (`mp3` default / `wav` / `aac`). With both\n * sources each is split on its own. At most 200 parts per input. Poll\n * `jobs.get(jobId)`; the parts are on the completed job's `output_data` —\n * see {@link SplitMediaOutput}.\n */\n splitMedia(input: SplitMediaInput): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/split-media\", { body: input })\n }\n\n /**\n * Turn one still image + one audio track into an MP4\n * (`POST /v1/still-to-video`) — locally rendered (FFmpeg), no AI model,\n * zero credits. The output duration IS the audio's duration; there is no\n * duration field. Optional `motion` animates the still (zoom / pan /\n * ken-burns) at `intensity` 1–10. `fit: \"contain\"` letterboxes with\n * `padColor` instead of cropping. Poll `jobs.get(jobId)`.\n */\n stillToVideo(input: {\n imageUrl: string\n audioUrl: string\n motion?: \"none\" | \"zoom-in\" | \"zoom-out\" | \"pan-left\" | \"pan-right\" | \"ken-burns\"\n intensity?: number\n resolution?: \"720p\" | \"1080p\" | \"4K\"\n aspectRatio?: \"16:9\" | \"9:16\" | \"1:1\" | \"4:3\"\n fps?: 24 | 30\n fit?: \"cover\" | \"contain\"\n padColor?: string\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/still-to-video\", { body: input })\n }\n\n /**\n * Turn 2–100 images + one optional audio track into an MP4 slideshow\n * (`POST /v1/slideshow`) — locally rendered (FFmpeg), zero credits. With\n * audio, the output duration IS the audio's duration (equal split unless\n * `imageDurations` pins rows — null entries = auto; mismatched pinned sums\n * scale proportionally and the factor is disclosed in the job output).\n * Without audio: N × `perImageDuration`, silent output. Transitions\n * consume the outgoing slide, so totals stay exact. For a single image use\n * `stillToVideo`. Poll `jobs.get(jobId)`.\n */\n slideshow(input: {\n imageUrls: string[]\n audioUrl?: string\n imageDurations?: Array<number | null>\n perImageDuration?: number\n transition?: string\n transitionDuration?: number\n motion?: \"none\" | \"zoom-in\" | \"zoom-out\" | \"ken-burns\" | \"alternate\"\n intensity?: number\n resolution?: \"720p\" | \"1080p\" | \"4K\"\n aspectRatio?: \"16:9\" | \"9:16\" | \"1:1\" | \"4:3\"\n fps?: 24 | 30\n fit?: \"cover\" | \"contain\"\n padColor?: string\n }): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/slideshow\", { body: input })\n }\n\n /**\n * Place 1–20 timed image layers over a video (`POST /v1/video-overlay`) —\n * rendered locally (FFmpeg) in one pass, no AI; the base audio is kept\n * untouched. Each layer has an `imageUrl`, a `start` and an optional `end`\n * in seconds (no `end` = to the end of the video) and a placement: a\n * `preset` (`\"card\"`, `\"corner-badge\"` with a `corner`, `\"full-frame\"`) or\n * an explicit box in {@link MediaResource.imageOverlay}'s percent vocabulary\n * (`anchor`, `x`, `y`, `width`, `height`, `fit`) — an explicit box field\n * overrides the preset, and a layer with neither is a corner badge\n * (bottom-right, or the `corner` it names). `outputAspect` renders onto a\n * 16:9 / 9:16 / 1:1 / 4:5 canvas\n * (`baseFit` cover by default; `backgroundColor` pads `contain`). Poll\n * `jobs.get(jobId)`: the output carries `videoUrl`, `thumbnailUrl`, `width`,\n * `height`, `durationSec` and `warnings[]` (a layer clipped or skipped at the\n * video's end, an animated image's first frame, re-encoded audio).\n */\n videoOverlay(input: VideoOverlayRequest): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/video-overlay\", { body: input })\n }\n\n /**\n * Track a subject described in plain text through a video and get a\n * black-and-white MASK VIDEO back (`POST /v1/video-mask`, SAM 3.1 by default,\n * or `provider: \"sam-3\"` at half the price) — white = the subject. Clips up\n * to 5 minutes at 30 fps (a faster clip counts\n * proportionally longer), priced by the clip's length. Poll\n * `jobs.get(jobId)`: the output carries `videoUrl` (the mask), `width`,\n * `height`, `frames`, `coverage` (the fraction of pixels selected, rounded\n * to 4 decimals) and `kept` (any region left on any frame: false only when\n * the size / zone filters left an empty mask — read it rather than\n * `coverage`, which a few pixels round to 0), and `recipe` (the version of\n * the masking algorithm that made it, `VIDEO_MASK_RECIPE`: it changes\n * whenever the same clip and settings would give a different mask). Feed the mask to\n * {@link MediaResource.maskComposite}.\n *\n * `holdLastMask: true` keeps the last mask on frames where the subject is\n * lost (a mask covering under 0.002 % of the frame, e.g. a face behind a\n * hand): each such frame after the first detection repeats the most recent\n * non-empty one, for at most 1 s of frames (`VIDEO_MASK_LIMITS.HOLD_MAX_SEC`;\n * longer gaps stay empty after that); the output then also carries `heldFrames`.\n * `minSubjectHeightPct` (0–100, default 0 = off) drops every connected\n * region whose bounding box is shorter than that % of the frame height\n * (extras' small faces behind the main subject); it runs before\n * `holdLastMask`, and the output then carries `droppedRegions`.\n * `maxSubjectHeightPct` (0–100, default 0 = off) is its mirror: it drops\n * regions TALLER than that % (keep only the small background faces). With\n * both set a region is kept when min ≤ height ≤ max; min > max is a 400.\n * `zone` (`{ leftPct, rightPct, topPct, bottomPct }`, each 0–49 % of the\n * frame, a margin left out = 0; default null = off) keeps only the regions\n * whose bounding-box centre lies inside the frame minus those margins (after\n * the size cuts, before `holdLastMask`). `perShot: true` finds the clip's\n * scene cuts first and segments each shot on its own: the subject is\n * detected again after every cut, `holdLastMask` never crosses a cut, and\n * each shot's mask is fitted to that shot's exact frame count; on a clip\n * with no cut it gives the same mask, fitted to the clip's exact frame\n * count. The output carries `shots` (1 when off). `redetectEverySec` (0 =\n * off, the default, or 1–10) also detects the subject again every that\n * many seconds — the clip (each shot, with `perShot`) is sent to the model\n * in pieces of at most that length, a last piece under 8 frames joining the\n * one before — so someone who walks in or turns to the camera mid-shot is\n * masked from the next boundary on. These boundaries are not cuts:\n * `holdLastMask` holds across them. Each piece is one more model request\n * (three run at a time), so it is slower; the price does not change. The\n * output's `chunks` counts the requests.\n */\n videoMask(input: VideoMaskRequest): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/video-mask\", { body: input })\n }\n\n /**\n * Place a foreground video over a background video through a mask video\n * (`POST /v1/mask-composite`; white = foreground) — local FFmpeg, no AI.\n * The result follows the foreground's size, frame rate and length; the\n * background is fitted (`backgroundFit`, default cover) and loops when\n * shorter; the mask holds its last frame when shorter. Optional\n * `invertMask`, `feather` (0–20 px), `grow` (−10–10 px), `opacity` (0–1,\n * default 1: how strongly the foreground covers the background inside the\n * mask) and `audioSource` (foreground / background / mix / none). Priced per started 5 seconds of\n * the result (the video it follows up to 300 s). Poll `jobs.get(jobId)` for `videoUrl`.\n *\n * `fill` picks what fills the masked area: `video` (default — the\n * foreground, required then), `pixelate` (the background itself in\n * blocks of `pixelSizePct` % of the frame width, 0.2–10, default 1.25) or\n * `blur` (the background blurred, sigma `blurStrengthPct` % of the frame\n * width, 0.1–5, default 2). Pixelate and blur take no\n * `foregroundUrl` (one sent is ignored): the result follows the background\n * and is priced on its length, and it always carries the background's sound\n * (`audioSource` is ignored).\n */\n maskComposite(input: MaskCompositeRequest): Promise<{ jobId: string }> {\n return this.client.request<{ jobId: string }>(\"POST\", \"/v1/mask-composite\", { body: input })\n }\n\n /**\n * Probe a social video's metadata (`POST /v1/video-metadata`) — duration,\n * dimensions, title, live status — WITHOUT downloading it. A direct read, not a\n * job. Use it to decide whether to trim before importing.\n */\n videoMetadata(input: { url: string }): Promise<VideoMetadata> {\n return this.client.request<VideoMetadata>(\"POST\", \"/v1/video-metadata\", { body: input })\n }\n\n /**\n * Cut or crop a stored file (`POST /v1/media/process`) — synchronous and\n * free, the source-preparation sibling of the priced `trimVideo` node.\n * `trim` is `[startTime, endTime]` in seconds; `deleteSource: true` removes\n * the source object afterwards when it is yours and nothing else references\n * it (\"the cut replaces the original\"). Answers the stored file.\n */\n process(input: MediaProcessInput): Promise<{ data: MediaProcessResult }> {\n return this.client.request<{ data: MediaProcessResult }>(\"POST\", \"/v1/media/process\", { body: input })\n }\n}\n\n/**\n * One event from {@link MediaResource.downloadVideoProgress}. The stream ends\n * after a `completed` event (which carries the stored `videoUrl` + an optional\n * `thumbnailUrl`) or a `failed` event (which carries `error`).\n */\nexport interface DownloadVideoProgress {\n phase: \"downloading\" | \"processing\" | \"uploading\" | \"completed\" | \"failed\"\n /** Download percent (0–100). Section fetches report jumpy percents — display, don't sum. */\n percent: number\n /** The imported video's storage URL — set on the `completed` event. */\n videoUrl?: string\n /** Thumbnail storage URL — set on the `completed` event when one was captured. */\n thumbnailUrl?: string\n /** What went wrong — set on the `failed` event. */\n error?: string\n}\n\n/** Result of {@link MediaResource.videoMetadata}. Fields are best-effort — a probe may omit some. */\nexport interface VideoMetadata {\n durationSec?: number | null\n width?: number | null\n height?: number | null\n title?: string | null\n isLive?: boolean\n [key: string]: unknown\n}\n\n/** Body of {@link MediaResource.splitMedia} (`POST /v1/split-media`). At least one of `videoUrl` / `audioUrl`. */\nexport interface SplitMediaInput {\n /** Video to split. */\n videoUrl?: string\n /** Audio file to split (split on its own when a video is given too). */\n audioUrl?: string\n /** The longest a part may be, in seconds (at least 1). Every part is at most this long. */\n chunkDuration: number\n /** The shortest a part may be, in seconds (0 or more, default 4). Must not exceed `chunkDuration` (400 otherwise). */\n minPartDuration?: number\n /** Format of the audio parts. Default `mp3`. */\n audioFormat?: \"mp3\" | \"wav\" | \"aac\"\n}\n\n/** A completed split-media job's `output_data`. */\nexport interface SplitMediaOutput {\n /** The video parts, in order (when a video was split). */\n videoUrls?: string[]\n /** The audio parts, in order (when an audio file was split). */\n audioUrls?: string[]\n /** The number of parts — the larger of the two lists. */\n chunkCount: number\n /** Each part's exact length in seconds: the video parts' when a video was split, else the audio parts'. */\n partDurationsSec?: number[]\n /** The audio parts' lengths, only when BOTH a video and an audio file were split. */\n audioPartDurationsSec?: number[]\n}\n\nexport interface MediaProcessInput {\n sourceUrl: string\n type: \"video\" | \"audio\"\n trim?: { startTime: number; endTime: number }\n crop?: { x: number; y: number; width: number; height: number }\n format?: \"mp4\" | \"webm\" | \"mp3\" | \"wav\" | \"m4a\" | \"aac\"\n deleteSource?: boolean\n}\n\nexport interface MediaProcessResult {\n url: string\n thumbnailUrl: string | null\n assetId: string | null\n sizeBytes: number\n mimeType: string\n metadata: Record<string, unknown>\n}\n"]}
|
|
@@ -193,8 +193,19 @@ var WorkflowsResource = class {
|
|
|
193
193
|
sharedWithMe() {
|
|
194
194
|
return this.client.request("GET", "/v1/workflows/shared-with-me");
|
|
195
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Your MCP conversations of one month (`GET /v1/mcp-conversations`): each is a
|
|
198
|
+
* flow an assistant built from its steps, newest first. A conversation is a
|
|
199
|
+
* workflow, so open, rename, export or delete it with the other methods here.
|
|
200
|
+
*/
|
|
201
|
+
listMcpConversations(params = {}) {
|
|
202
|
+
const query = {};
|
|
203
|
+
if (params.month) query.month = params.month;
|
|
204
|
+
if (params.sources?.length) query.sources = params.sources.join(",");
|
|
205
|
+
return this.client.request("GET", "/v1/mcp-conversations", { query });
|
|
206
|
+
}
|
|
196
207
|
};
|
|
197
208
|
|
|
198
209
|
export { WorkflowsResource };
|
|
199
|
-
//# sourceMappingURL=chunk-
|
|
200
|
-
//# sourceMappingURL=chunk-
|
|
210
|
+
//# sourceMappingURL=chunk-33HT46A3.js.map
|
|
211
|
+
//# sourceMappingURL=chunk-33HT46A3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resources/workflows.ts"],"names":[],"mappings":"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","file":"chunk-33HT46A3.js","sourcesContent":["import type { NodaroClientCore } from \"../core.js\"\nimport type {\n GenericNode,\n GenericEdge,\n} from \"@nodaro/shared/types\"\nimport type {\n WorkflowExport,\n WorkflowImportReport,\n} from \"@nodaro/shared/workflow\"\nimport type {\n WorkflowVisibility,\n CollaboratorRole,\n} from \"@nodaro/shared/organizations\"\n\n/**\n * Workflow metadata + (when fetched as a single record) full nodes/edges/settings.\n *\n * The list endpoint returns metadata only; `get`, `create`, and `update` return the\n * full record. `nodes`, `edges`, `settings`, and `sourcePrompt` are present only on\n * full records and omitted in list responses.\n */\nexport interface Workflow {\n id: string\n projectId: string | null\n userId: string\n name: string\n description?: string | null\n folderId?: string | null\n isTemplate?: boolean\n version?: number\n thumbnailUrl?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string | null\n createdAt: string\n updatedAt: string\n}\n\nexport interface ListWorkflowsParams {\n /** Required — list endpoint is `/v1/projects/:projectId/workflows`. */\n projectId: string\n}\n\nexport interface CreateWorkflowInput {\n /** Required — workflow is created under this project. */\n projectId: string\n name: string\n description?: string\n folderId?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string\n}\n\nexport interface UpdateWorkflowInput {\n name?: string\n description?: string\n folderId?: string | null\n nodes?: GenericNode[]\n edges?: GenericEdge[]\n settings?: Record<string, unknown>\n sourcePrompt?: string\n thumbnailUrl?: string | null\n /**\n * Optimistic concurrency: the `updatedAt` your copy of the workflow was\n * loaded with. When the row has since been written by another tab/device,\n * the update is rejected with HTTP 409 `workflow_conflict`\n * (`WorkflowConflictError`) carrying the current record — re-read/merge and\n * retry instead of last-writer-wins clobbering the other writer's changes.\n * Omit for the legacy unconditional replace.\n */\n expectedUpdatedAt?: string\n /**\n * Optimistic concurrency by monotonic version — preferred over\n * `expectedUpdatedAt` (integer bumped by the DB on every content change).\n */\n expectedVersion?: number\n}\n\nexport interface RunWorkflowParams {\n /**\n * Optional subset of node IDs to execute. Omit to run the full workflow.\n * With `continueMode: \"from-failed\"`, the nodes to consider (default: the\n * nodes the run's chain was first started on, plus every node left failed or\n * held back; see `continueMode`); the run executes only those the failed run\n * did not complete. Every id sent counts as named: a node of work no run of\n * the chain ever ran (each only handed on its saved results) runs and is\n * priced, while a source, trigger, parameter picker or container keeps what\n * the run handed on and a node set to skip never runs.\n */\n nodeIds?: string[]\n /**\n * Per-node field overrides for this run only, `{ [nodeId]: { field: value } }`,\n * shallow-merged over each node's saved data (the node's saved results are\n * not used). A run through the API has nobody to review a Preview render, so\n * a workflow whose Apply EDL render is set to Preview is refused\n * (`preview_review_required`) unless the run sets it to Final:\n * `{ [renderNodeId]: { quality: \"final\" } }`. A node with Review before\n * render on (Tighten Episode, Trailer) is refused the same way (turn its\n * review off). A failed execution records what held it —\n * `preview_review_required` / `preview_render_nested` (a render),\n * `…_review_on` (a review-on node) or `…_mixed` (both); match all six with\n * `isPreviewRefusalCode` from `@nodaro/shared`. A review-on Tighten Episode\n * fed by a List is refused with `use_case_review_list_driven`; a Trailer fed\n * by a List, with `use_case_list_driven`.\n */\n inputOverrides?: Record<string, Record<string, unknown>>\n /**\n * Continue from an earlier execution of this workflow — Render final after a\n * run that stopped at its preview, or (with `continueMode: \"from-failed\"`) a\n * failed run from where it stopped. The run executes `nodeIds` only\n * (required with this field, except with `continueMode`); every other node\n * hands on what that execution produced, not the workflow's saved results,\n * and an Edit Plan hands on its plan with the person's review applied. The\n * execution must be your own `completed` run of this workflow (`failed`,\n * with `continueMode`), not of a published app version. Refused with\n * `continuation_subset_required`, `continuation_not_found` (404),\n * `continuation_workflow_mismatch`, `continuation_version_mismatch` or\n * `continuation_not_completed` (409). A continuation its payer cannot cover\n * (with or without `continueMode`, priced on the nodes it runs) throws\n * `InsufficientCreditsError` before any execution exists, checked as a\n * {@link WorkflowsResource.renderFinal} is.\n */\n continueFromExecutionId?: string\n /**\n * `\"from-failed\"`: continue a FAILED run (`continueFromExecutionId`) from\n * where it stopped. It runs every node that run did not complete — of\n * `nodeIds`, which default to the nodes the run was started on (a run\n * started on named nodes is continued on those, a run of every node on every\n * node; a continuation of a continuation keeps those of the first run of its\n * chain) plus every node the run left failed or held back — and hands on\n * what it completed,\n * never run or charged again. That is per node: a node that failed runs\n * again in full, every list item included. When a later continuation of it\n * completed only part of what it left (a narrower `nodeIds`), the run builds\n * on the newest such one: it is seeded from that run and runs only what is\n * still left ({@link RunWorkflowResult.continuedFrom} names it). A node\n * `nodeIds` names that no run of the chain ever ran runs too, and is priced\n * (see `nodeIds`). A Render\n * final, or a run of named nodes, continuing the newest of those counts as\n * one of them too. Not combinable with {@link renderFinal}. Refused with\n * `continuation_not_failed` (409) for a run that did not fail,\n * `continuation_superseded` (409) when a later continuation supersedes it —\n * the message names that run: one that failed (continue that one instead),\n * or one that completed all this run left (the work is done, and its results\n * are on that run) — `continuation_preview_pending` (409, a\n * `ContinuationPreviewPendingError`) when the newest later continuation\n * stopped at a Preview review of a render or of a Tighten Episode (the error\n * names that run, what holds it, and in `next` the run that finishes it:\n * `client.workflows.run(workflowId, error.next)` first — for a single render,\n * its {@link renderFinal}), and\n * `continuation_subset_required` (409) when nothing is left to run. Send an\n * {@link RunWorkflowOptions.idempotencyKey} with each continuation.\n */\n continueMode?: \"from-failed\"\n}\n\n/** Per-call options for {@link WorkflowsResource.run}. */\nexport interface RunWorkflowOptions {\n /**\n * Sent as the `Idempotency-Key` header (at least 8 characters; the server\n * ignores a shorter one). Reuse the same value when retrying the same\n * request: the server answers the run it already started (`deduped: true`)\n * instead of starting a second one — or, once that run is under way or has\n * superseded the one it continued, refuses with `already_running` (an\n * `AlreadyRunningError` carrying its `executionId`) or\n * `continuation_superseded` naming it. Either way nothing runs twice.\n */\n idempotencyKey?: string\n}\n\nexport interface RunWorkflowResult {\n executionId: string\n status: \"pending\" | \"running\"\n /** `true` when an `idempotencyKey` retry found the run already started: `executionId` is that run. */\n deduped?: boolean\n /**\n * With `continueMode: \"from-failed\"`: the execution the run continued from —\n * its states seed the run. The run asked for (`continueFromExecutionId`), or\n * the newest later continuation of it that completed part of what it left.\n * Absent on any other run, and on a `deduped` answer.\n */\n continuedFrom?: string\n}\n\n/**\n * A Render final: the Apply EDL render at Final for this run only, and every\n * node after it (Camera Switch first, in multicam). The server works out which\n * nodes run — the same rule as the editor's Render final button.\n */\nexport interface RenderFinalParams {\n /** The Apply EDL render node to finalize. */\n renderNodeId: string\n /**\n * The execution that stopped at the preview: your own `completed` run of\n * this workflow. Every node the Render final does not run hands on what it\n * produced (an Edit Plan hands on its plan with the review applied).\n */\n continueFromExecutionId: string\n}\n\n/** What a Render final will run and charge. Nothing is created by the quote. */\nexport interface RenderFinalQuote {\n renderNodeId: string\n /** The nodes the run executes, in canvas order. */\n nodeIds: string[]\n /** The one-shot override it runs with: `{ [renderNodeId]: { quality: \"final\" } }`. */\n inputOverrides: Record<string, Record<string, unknown>>\n /** Estimated credits; `null` in an edition without credits. */\n estimatedCredits: number | null\n /**\n * Present (`true`) when `estimatedCredits` includes an LLM node's estimate:\n * an LLM call is charged its actual usage after it runs, so show `≈N`.\n * Absent for a fixed price.\n */\n estimatedCreditsIsEstimate?: true\n /**\n * What the run holds while it renders (a UGC clip at its hold), never below\n * `estimatedCredits`: the figure `sufficient` is checked against. `null` in\n * an edition without credits; absent from an older server.\n */\n holdCredits?: number | null\n /**\n * Whether the payer can cover what the run holds (`holdCredits`); `null` in\n * an edition without credits. When `false`, {@link WorkflowsResource.renderFinal}\n * is refused with `InsufficientCreditsError` (its `required` is that hold)\n * before any execution exists.\n */\n sufficient: boolean | null\n /**\n * The payer's spendable credits; `null` when it is not yours to see (a\n * deployment's operator pays) or a workspace budget pays.\n */\n available: number | null\n}\n\n/** A person granted access to a workflow. Email is never returned (privacy). */\nexport interface Collaborator {\n userId: string\n name?: string | null\n avatar?: string | null\n role: CollaboratorRole\n}\n\nexport interface AddCollaboratorInput {\n /** Provide exactly one of `userId` or `email`. */\n userId?: string\n email?: string\n role: CollaboratorRole\n}\n\n/** A workflow someone else shared with you, plus the role you hold on it. */\nexport interface SharedWorkflow extends Workflow {\n grantedRole: CollaboratorRole\n}\n\n/** A grant dropped because a move took the workflow out of the workspace the\n * access came from. */\nexport interface DroppedCollaborator {\n userId: string\n name: string | null\n}\n\n/**\n * The people a workflow is shared with — reached as `client.workflows.collaborators`.\n * All four endpoints are served by the organizations feature and exist only when\n * it is enabled server-side; against an install without it they 404\n * (→ `NotFoundError`). The active workspace travels on the request like every\n * other call, so a workspace admin's grants apply automatically.\n */\nexport class WorkflowCollaboratorsResource {\n constructor(private client: NodaroClientCore) {}\n\n /** List a workflow's collaborators (id, name, avatar, role — never email). */\n list(workflowId: string): Promise<{ data: Collaborator[] }> {\n return this.client.request(\n \"GET\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/collaborators`,\n )\n }\n\n /** Add a collaborator by `userId` OR `email` (exactly one), at the given role. */\n add(\n workflowId: string,\n input: AddCollaboratorInput,\n ): Promise<{ data: { userId: string; role: CollaboratorRole } }> {\n return this.client.request(\n \"POST\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/collaborators`,\n { body: input },\n )\n }\n\n /** Change a collaborator's role. */\n update(\n workflowId: string,\n userId: string,\n input: { role: CollaboratorRole },\n ): Promise<{ data: { userId: string; role: CollaboratorRole } }> {\n return this.client.request(\n \"PATCH\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/collaborators/${encodeURIComponent(userId)}`,\n { body: input },\n )\n }\n\n /** Remove a collaborator — or yourself. Returns `{ success: true }`. */\n remove(workflowId: string, userId: string): Promise<{ success: true }> {\n return this.client.request(\n \"DELETE\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/collaborators/${encodeURIComponent(userId)}`,\n )\n }\n}\n\n/** One MCP conversation: a flow its assistant built step by step (`GET /v1/mcp-conversations`). */\nexport interface McpConversation {\n /** The conversation's workflow id — open it with {@link WorkflowsResource.get}. */\n id: string\n projectId: string\n name: string\n /** The source: \"claude\", \"claude-code\", \"chatgpt\", or another client's name in lower case. */\n client: string\n clientLabel: string\n /** The connection's own name, as the connector was named. */\n clientName: string\n startedAt: string\n lastActivityAt: string\n /** Still receiving steps: its last step is less than 30 minutes old. */\n live: boolean\n /** The user's first request, when the assistant passed it. */\n firstRequest: string | null\n nodeCount: number\n counts: { image: number; video: number; audio: number }\n /** Credits charged for its jobs; null where credits are not billed. */\n credits: number | null\n cover: { url: string; kind: \"image\" | \"video\"; setByUser: boolean } | null\n updatedAt: string\n}\n\nexport interface ListMcpConversationsParams {\n /** \"YYYY-MM\". Default: the latest month that has conversations. */\n month?: string\n /** Keep only these sources (\"claude\", \"claude-code\", \"chatgpt\", …). */\n sources?: readonly string[]\n}\n\nexport interface McpConversationList {\n /** The month listed. */\n month: string\n /** Every month that has conversations, newest first, with its count. */\n months: Array<{ month: string; conversations: number }>\n /** The month's sources, with the connection names each one folds in. */\n sources: Array<{ client: string; label: string; conversations: number; connections: Array<{ clientName: string; conversations: number }> }>\n data: McpConversation[]\n}\n\nexport class WorkflowsResource {\n /** The people this workflow is shared with. See {@link WorkflowCollaboratorsResource}. */\n readonly collaborators: WorkflowCollaboratorsResource\n\n constructor(private client: NodaroClientCore) {\n this.collaborators = new WorkflowCollaboratorsResource(client)\n }\n\n /** List workflows for a project. Returns metadata only — `nodes`/`edges` are not included. */\n list(params: ListWorkflowsParams): Promise<{ data: Workflow[] }> {\n return this.client.request(\n \"GET\",\n `/v1/projects/${encodeURIComponent(params.projectId)}/workflows`,\n )\n }\n\n /** Get a workflow including its full nodes/edges/settings. */\n get(id: string): Promise<{ data: Workflow }> {\n return this.client.request(\"GET\", `/v1/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Get a PUBLICLY-SHARED workflow by id (`GET /v1/public/workflows/:id`) — the\n * unauthenticated share-by-link read. Returns the workflow's nodes/edges/\n * settings ONLY when it's opted into sharing server-side (`settings.studio.shared\n * === true`); otherwise the route 404s (→ `NotFoundError`). No auth required —\n * a share viewer has no session; the SDK omits the bearer when no token exists.\n */\n getPublic(id: string): Promise<{ data: Workflow }> {\n return this.client.request(\"GET\", `/v1/public/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Create a workflow under a project. Returns the full record.\n * NOTE: server route is `POST /v1/projects/:projectId/workflows`.\n */\n create(input: CreateWorkflowInput): Promise<{ data: Workflow }> {\n const { projectId, ...body } = input\n return this.client.request(\n \"POST\",\n `/v1/projects/${encodeURIComponent(projectId)}/workflows`,\n { body },\n )\n }\n\n /** Patch a workflow. Returns the full updated record. */\n update(id: string, input: UpdateWorkflowInput): Promise<{ data: Workflow }> {\n return this.client.request(\n \"PATCH\",\n `/v1/workflows/${encodeURIComponent(id)}`,\n { body: input },\n )\n }\n\n /** Delete a workflow. Returns `{ success: true }`. Throws `NotFoundError`\n * when the id doesn't exist or isn't yours (the delete is not silent). */\n delete(id: string): Promise<{ success: true }> {\n return this.client.request(\"DELETE\", `/v1/workflows/${encodeURIComponent(id)}`)\n }\n\n /**\n * Run a workflow. Returns the executionId for polling via\n * `client.executions.get(executionId)`.\n */\n run(id: string, params: RunWorkflowParams = {}, options?: RunWorkflowOptions): Promise<RunWorkflowResult> {\n return this.client.request(\n \"POST\",\n `/v1/workflows/${encodeURIComponent(id)}/run`,\n {\n body: params,\n ...(options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}),\n },\n )\n }\n\n /**\n * Render final (`renderFinal` on `POST /v1/workflows/:id/run`): run the\n * render at Final for this run only, and every node after it, continuing the\n * execution that stopped at its preview. The server derives the nodes and\n * the override — send nothing else. Quote it first with\n * {@link estimateRenderFinal}. Refused with `render_final_node_not_found`,\n * `render_final_not_a_render`, or a continuation code (see `run`); a run\n * its payer cannot cover throws `InsufficientCreditsError` (with `required`\n * and, when it is yours to see, `available`) before any execution exists.\n */\n renderFinal(id: string, params: RenderFinalParams): Promise<RunWorkflowResult> {\n return this.client.request(\"POST\", `/v1/workflows/${encodeURIComponent(id)}/run`, {\n body: {\n renderFinal: { renderNodeId: params.renderNodeId },\n continueFromExecutionId: params.continueFromExecutionId,\n },\n })\n }\n\n /**\n * Quote a Render final (`POST /v1/workflows/:id/render-final/estimate`): the\n * nodes it will run, the override it runs with and its estimated credits.\n * Nothing is created or charged.\n */\n estimateRenderFinal(id: string, params: RenderFinalParams): Promise<{ data: RenderFinalQuote }> {\n return this.client.request(\"POST\", `/v1/workflows/${encodeURIComponent(id)}/render-final/estimate`, {\n body: { renderNodeId: params.renderNodeId, continueFromExecutionId: params.continueFromExecutionId },\n })\n }\n\n /**\n * Export a workflow as a portable JSON bundle.\n * Pass `opts.assets = true` to include character/object/location entity data.\n */\n export(\n workflowId: string,\n opts?: { assets?: boolean },\n ): Promise<{ data: WorkflowExport }> {\n return this.client.request(\n \"GET\",\n `/v1/workflows/${encodeURIComponent(workflowId)}/export`,\n { query: { assets: opts?.assets ?? false } },\n )\n }\n\n /**\n * Import a `WorkflowExport` bundle into the specified project.\n * Re-creates any bundled assets (characters, objects, creatures, locations)\n * under your account, and re-points BOTH the entity nodes and every `@`-chip\n * (`ConnectedReference`) bound in the graph at the rows it created.\n * Media the bundle references on other hosts is copied onto this instance's\n * storage where reachable; a bundled entity's images are copied whoever\n * hosts them, because they are the exporter's bytes and their lifecycle is\n * not yours. `importReport` says what was copied, what could not be reached\n * and what was skipped (and why); `assetIdMap` maps each bundled entity id\n * to the row created for it (for chips you hold outside the graph), and\n * `assetsSkipped` names the entities your storage quota left uncreated —\n * the workflow itself still lands.\n */\n import(input: WorkflowExport & { projectId: string }): Promise<{ data: Workflow; importReport?: WorkflowImportReport }> {\n const { projectId, ...workflowJson } = input\n return this.client.request(\"POST\", \"/v1/workflows/import\", {\n body: { projectId, workflow_json: workflowJson },\n })\n }\n\n /**\n * Set a workflow's visibility — `\"private\"` (creator + explicit collaborators)\n * or `\"workspace\"` (everyone in its workspace). Only the creator or a workspace\n * admin may change it; anyone else gets HTTP 403. Thin wrapper over `update()`:\n * the visibility lever lives on `PATCH /v1/workflows/:id`.\n */\n setVisibility(id: string, visibility: WorkflowVisibility): Promise<{ data: Workflow }> {\n return this.client.request(\"PATCH\", `/v1/workflows/${encodeURIComponent(id)}`, {\n body: { visibility },\n })\n }\n\n /**\n * Move a workflow to another project (`POST /v1/workflows/:id/move`); its folder\n * is cleared. When the move takes the workflow out of a workspace, collaborator\n * grants that came from that workspace are dropped and returned as\n * `droppedCollaborators`.\n */\n move(\n id: string,\n params: { projectId: string },\n ): Promise<{ data: Workflow; droppedCollaborators: DroppedCollaborator[] }> {\n return this.client.request(\"POST\", `/v1/workflows/${encodeURIComponent(id)}/move`, {\n body: params,\n })\n }\n\n /**\n * Workflows other people shared with you (`GET /v1/workflows/shared-with-me`) —\n * grants on work that is NOT in a workspace you belong to (workspace work already\n * shows in that workspace's own lists). Each carries the `grantedRole` you hold.\n * Empty when the organizations feature is off server-side.\n */\n sharedWithMe(): Promise<{ data: SharedWorkflow[] }> {\n return this.client.request(\"GET\", \"/v1/workflows/shared-with-me\")\n }\n\n /**\n * Your MCP conversations of one month (`GET /v1/mcp-conversations`): each is a\n * flow an assistant built from its steps, newest first. A conversation is a\n * workflow, so open, rename, export or delete it with the other methods here.\n */\n listMcpConversations(params: ListMcpConversationsParams = {}): Promise<McpConversationList> {\n const query: Record<string, string> = {}\n if (params.month) query.month = params.month\n if (params.sources?.length) query.sources = params.sources.join(\",\")\n return this.client.request(\"GET\", \"/v1/mcp-conversations\", { query })\n }\n}\n"]}
|
|
@@ -34,7 +34,7 @@ async function readBinaryResponse(response, maxBytes) {
|
|
|
34
34
|
}
|
|
35
35
|
return bytes.buffer;
|
|
36
36
|
}
|
|
37
|
-
var SDK_VERSION = "2.
|
|
37
|
+
var SDK_VERSION = "2.45.1" ;
|
|
38
38
|
var CLIENT_HEADER = "X-Nodaro-Client";
|
|
39
39
|
var isBrowser = () => typeof window !== "undefined" && typeof window.document !== "undefined";
|
|
40
40
|
var NodaroClientCore = class {
|
|
@@ -224,5 +224,5 @@ function supabaseAuth(supabase) {
|
|
|
224
224
|
}
|
|
225
225
|
|
|
226
226
|
export { CallbackAuth, NodaroClientCore, StaticTokenAuth, supabaseAuth };
|
|
227
|
-
//# sourceMappingURL=chunk-
|
|
228
|
-
//# sourceMappingURL=chunk-
|
|
227
|
+
//# sourceMappingURL=chunk-3Y5TV37H.js.map
|
|
228
|
+
//# sourceMappingURL=chunk-3Y5TV37H.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/binary-response.ts","../src/core.ts","../src/auth.ts"],"names":[],"mappings":"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","file":"chunk-V3KMBQMK.js","sourcesContent":["/** Bound decoded response bytes even when Content-Length is absent or compressed. */\nexport async function readBinaryResponse(response: Response, maxBytes: number): Promise<ArrayBuffer> {\n const advertised = response.headers.get(\"content-length\")\n if (advertised !== null && (!/^\\d+$/.test(advertised) || Number(advertised) > maxBytes)) {\n await response.body?.cancel()\n throw new Error(\"Binary response exceeds its declared size limit\")\n }\n if (!response.body) return new ArrayBuffer(0)\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let length = 0\n try {\n while (true) {\n const next = await reader.read()\n if (next.done) break\n length += next.value.byteLength\n if (length > maxBytes) {\n await reader.cancel()\n throw new Error(\"Binary response exceeds its declared size limit\")\n }\n chunks.push(next.value)\n }\n } finally { reader.releaseLock() }\n const bytes = new Uint8Array(length)\n let offset = 0\n for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength }\n return bytes.buffer\n}\n","/**\n * The client core: configuration, auth, the request transport and the\n * account-identity calls — everything a resource needs, and none of the\n * resources themselves.\n *\n * {@link NodaroClient} (`createClient`) extends it with every resource. An app\n * that uses only a few resources can extend it with just those, so it loads\n * (and its tests depend on) only the resource modules it actually uses:\n *\n * class AppClient extends NodaroClientCore {\n * readonly jobs = new JobsResource(this)\n * readonly nodes = new NodesResource(this) // needs `jobs` on the same client\n * }\n *\n * A resource that calls another one through the client says so in its\n * constructor type (`NodesResource` takes a core that also has `jobs`), so a\n * client missing it fails to compile rather than at run time.\n */\nimport { readBinaryResponse } from \"./binary-response.js\"\nimport { throwFromResponse } from \"./errors.js\"\nimport type { Auth } from \"./auth.js\"\nimport type { DeveloperAppKind, DeveloperAppScope } from \"./resources/developer-apps.js\"\nimport { WORKSPACE_HEADER, type MeOrganizations } from \"@nodaro/shared/organizations\"\n\n/** Replaced at build time by tsup `define` from package.json. The fallback\n * keeps `tsx`/vitest runs of the source working, where no define applies. */\ndeclare const __SDK_VERSION__: string | undefined\nconst SDK_VERSION = typeof __SDK_VERSION__ === \"string\" ? __SDK_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Identifies the calling package to the backend, which records it as the job's\n * provenance (`jobs.source` / `source_detail`) so an operator can tell a CLI\n * run from an SDK integration from a browser session.\n *\n * Only `sdk/…` and `cli/…` are honoured server-side; anything else is ignored\n * rather than trusted, since this is an unauthenticated header.\n */\nexport const CLIENT_HEADER = \"X-Nodaro-Client\"\n\n/**\n * In a browser the header is redundant AND a liability, so it is not sent.\n *\n * Redundant: the browser already sends `Origin`, which names the actual product\n * (`studio.nodaro.ai`) rather than merely the library it used — and the backend\n * deliberately prefers Origin over this header, so a browser-sent value is\n * discarded on arrival.\n *\n * A liability: `X-Nodaro-Client` is not a CORS-safelisted request header, so a\n * browser sending it requires an exact match in the server's\n * `Access-Control-Allow-Headers`. Any deployment whose backend predates that\n * allowlist entry would fail the PREFLIGHT — breaking every API call from the\n * app, not just its provenance. Self-hosted and lagging deployments make that a\n * real risk, and nothing in a browser app would reveal the cause.\n *\n * An EXPLICIT `clientLabel` is always sent regardless: a caller that names\n * itself has opted in deliberately, and that intent outranks this default.\n */\nconst isBrowser = (): boolean => typeof window !== \"undefined\" && typeof window.document !== \"undefined\"\n\nexport interface ClientOptions {\n /** Backend base URL, e.g. \"https://nodaro.example.com\" or empty string for same-origin. */\n baseUrl: string\n /** Auth provider. Use StaticTokenAuth, supabaseAuth, or CallbackAuth. */\n auth: Auth\n /** Optional fetch override (for tests or custom transports). */\n fetch?: typeof fetch\n /** Default request timeout in ms. Default 60s. */\n timeoutMs?: number\n /**\n * Overrides the `X-Nodaro-Client` label. Defaults to `sdk/<version>`.\n *\n * Exists for `@nodaro/cli`, which is a wrapper AROUND this SDK: without an\n * override every CLI invocation would be recorded as an SDK call and the two\n * surfaces could never be told apart.\n */\n clientLabel?: string\n /**\n * The workspace every request acts in — sent as `X-Nodaro-Workspace`.\n *\n * It selects which workspace a LIST reads from and where a CREATE lands.\n * It never authorizes: reading, updating, deleting or running an\n * identified object is decided by that object's own workspace, so a\n * forgotten header cannot hide work and a forged one cannot reach anyone\n * else's. Omit it to work in the caller's personal space.\n *\n * Prefer {@link NodaroClient.withWorkspace} for anything short-lived — one\n * client per workspace, no shared mutable selection to get wrong.\n */\n workspaceId?: string\n}\n\ninterface RequestOptions {\n body?: unknown\n query?: Record<string, string | number | boolean | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\n/**\n * The authenticated user's canonical identity (`GET /v1/me`). A token-\n * introspection primitive: any valid bearer token (first-party Supabase JWT or\n * a developer-app OAuth token) resolves to its owner's identity. Mirrors the\n * `profiles` identity columns server-side — the route is the source of truth.\n */\nexport interface UserIdentity {\n /** Nodaro user id (= the Supabase auth user id). */\n readonly id: string\n readonly email: string\n /** Human-readable display name (from `profiles.full_name`); `null` if unset. */\n readonly displayName: string | null\n /** Avatar URL; `null` if unset. */\n readonly avatarUrl: string | null\n /** Subscription tier (e.g. \"free\", \"pro\"). */\n readonly tier: string\n /**\n * Whether the user holds an admin role. DESCRIPTIVE only — use it to decide\n * whether to render admin surfaces without capability-probing an admin\n * endpoint; every admin API stays enforced server-side regardless.\n */\n readonly isAdmin: boolean\n}\n\n/**\n * An app the user let into their account through OAuth (`GET\n * /v1/me/connected-apps`): a developer app, an AI assistant's MCP connector, or\n * a self-hosted install. One entry per grant still in force.\n */\nexport interface ConnectedApp {\n /** The grant's id — what {@link NodaroClient.revokeConnectedApp} takes. */\n readonly authorizationId: string\n /** The app's name; `null` if it has none. A `dynamic_mcp` client chose its own. */\n readonly name: string | null\n /**\n * How the app came to exist: `user` (a developer app someone registered),\n * `first_party_mcp`, `dynamic_mcp` (an MCP client that registered itself) or\n * `community_instance` (a self-hosted install).\n */\n readonly kind: DeveloperAppKind | (string & {})\n readonly homepageUrl: string | null\n /** The scopes this grant holds. */\n readonly scopes: readonly DeveloperAppScope[]\n /** When the user granted access (ISO 8601). */\n readonly connectedAt: string\n /** The latest use of any token issued under the grant; `null` if never used. */\n readonly lastUsedAt: string | null\n}\n\nexport class NodaroClientCore {\n readonly baseUrl: string\n readonly auth: Auth\n readonly timeoutMs: number\n /** Value sent as `X-Nodaro-Client`; recorded by the backend as job provenance. */\n readonly clientLabel: string\n /** Whether to actually send it — see the note on {@link CLIENT_HEADER}. */\n private readonly sendClientHeader: boolean\n private readonly fetchOverride: typeof fetch | undefined\n\n /**\n * Resolved lazily so consumers can swap `globalThis.fetch` after the\n * client has been constructed (e.g. test mocks). Always rebound to the\n * global object — native fetch throws \"Illegal invocation\" when its\n * `this` is anything else.\n */\n get fetch(): typeof fetch {\n return this.fetchOverride ?? globalThis.fetch.bind(globalThis)\n }\n\n /** The workspace this client acts in; undefined = the personal space. */\n readonly workspaceId: string | undefined\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\") // strip trailing slash\n this.auth = opts.auth\n this.fetchOverride = opts.fetch\n this.timeoutMs = opts.timeoutMs ?? 60_000\n this.clientLabel = opts.clientLabel ?? `sdk/${SDK_VERSION}`\n this.workspaceId = opts.workspaceId\n // Explicit label = deliberate opt-in, always sent. The default is suppressed\n // in browsers — see the note on CLIENT_HEADER.\n this.sendClientHeader = opts.clientLabel !== undefined || !isBrowser()\n }\n\n /**\n * The transport: build the request, send it, throw the typed error on a\n * non-2xx via {@link throwFromResponse}, then read the body via `read` — all\n * inside the abort timer, so `timeoutMs` bounds the body download too (a\n * stalled CSV stream aborts, it does not hang forever). Both the JSON path\n * ({@link request}) and the text path ({@link requestText}) go through here.\n */\n private async send<T>(\n method: string,\n path: string,\n options: RequestOptions,\n read: (res: Response) => Promise<T>,\n ): Promise<T> {\n const url = this.buildUrl(path, options.query)\n\n const token = await this.auth.getToken()\n // A `FormData` body is a multipart upload: let the runtime set\n // `Content-Type: multipart/form-data; boundary=…` itself (a manual JSON\n // content-type corrupts the boundary), and send the body as-is rather than\n // JSON-stringifying it. Every other body stays JSON, exactly as before.\n const isFormData =\n typeof FormData !== \"undefined\" && options.body instanceof FormData\n const headers: Record<string, string> = {\n ...(isFormData ? {} : { \"Content-Type\": \"application/json\" }),\n ...(this.sendClientHeader ? { [CLIENT_HEADER]: this.clientLabel } : {}),\n // Before per-request headers: a resource that needs to reach outside\n // this client's workspace says so explicitly and wins.\n ...(this.workspaceId ? { [WORKSPACE_HEADER]: this.workspaceId } : {}),\n ...(options.headers ?? {}),\n }\n if (token) headers[\"Authorization\"] = `Bearer ${token}`\n\n const ac = new AbortController()\n const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs)\n const abort = () => ac.abort(options.signal?.reason)\n options.signal?.addEventListener(\"abort\", abort, { once: true })\n if (options.signal?.aborted) abort()\n\n try {\n ac.signal.throwIfAborted()\n const res = await this.fetch(url, {\n method,\n headers,\n body:\n options.body === undefined\n ? undefined\n : isFormData\n ? (options.body as FormData)\n : JSON.stringify(options.body),\n signal: ac.signal,\n })\n\n if (!res.ok) {\n let errBody: Record<string, unknown> = {}\n try {\n errBody = await res.json() as Record<string, unknown>\n } catch {\n // Empty/non-JSON body — fall through with empty errBody\n }\n throwFromResponse(res.status, errBody)\n }\n\n return await read(res)\n } finally {\n clearTimeout(timeoutId)\n options.signal?.removeEventListener(\"abort\", abort)\n }\n }\n\n /** Authenticated, bounded binary reads through the normal timeout/error transport. */\n async requestBytes(method: string, path: string, options: RequestOptions & { maxBytes: number }): Promise<ArrayBuffer> {\n if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1) throw new Error(\"maxBytes must be a positive integer\")\n return this.send(method, path, options, (response) => readBinaryResponse(response, options.maxBytes))\n }\n\n async request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {\n return this.send(method, path, options, async (res) =>\n // 204 No Content\n res.status === 204 ? (undefined as T) : ((await res.json()) as T),\n )\n }\n\n /**\n * Like {@link request}, for endpoints that answer text (CSV exports). Errors\n * are still the JSON envelope and throw the same typed errors, and the read is\n * bounded by the same timeout as the request.\n */\n async requestText(method: string, path: string, options: RequestOptions = {}): Promise<string> {\n return this.send(method, path, options, (res) => res.text())\n }\n\n /**\n * `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).\n * Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the\n * token is missing/invalid, and the SDK's other typed errors as usual.\n *\n * On an instance with organizations it also carries what the caller belongs\n * to. THREE states, and collapsing them is wrong in a way users feel: the\n * organization fields ABSENT means this instance has no organizations at\n * all; present and empty means the account belongs to none; and\n * `organizationsUnavailable` means the lookup failed — keep whatever\n * selection you already had rather than concluding the person was removed\n * from everything.\n */\n async me(): Promise<UserIdentity & MeOrganizations> {\n const res = await this.request<{ data: UserIdentity & MeOrganizations }>(\"GET\", \"/v1/me\")\n return res.data\n }\n\n /**\n * `GET /v1/me/connected-apps` → every app with access to the signed-in\n * user's account (see {@link ConnectedApp}), newest grant first.\n *\n * **A signed-in browser session only.** The route answers a Supabase\n * session token (`supabaseAuth(…)`, `createSharedSupabaseClient`, or a\n * `StaticTokenAuth` holding one) and refuses every other credential with\n * `UnauthorizedError` (\"Sign in required\"): an app token or an API key must\n * not list the grants it lives under. Build your \"Connected apps\" settings\n * screen on this; a server-side integration cannot call it.\n */\n async connectedApps(): Promise<ConnectedApp[]> {\n const res = await this.request<{ apps: ConnectedApp[] }>(\"GET\", \"/v1/me/connected-apps\")\n return res.apps\n }\n\n /**\n * `POST /v1/me/connected-apps/:authorizationId/revoke` — ends an app's\n * access: the grant and every token issued under it, at once (the app's next\n * call is a 401). Resolves when done.\n *\n * A signed-in browser session only, like {@link connectedApps}. Throws\n * `NotFoundError` when the grant is not the user's or is already revoked.\n */\n async revokeConnectedApp(authorizationId: string): Promise<void> {\n await this.request(\"POST\", `/v1/me/connected-apps/${encodeURIComponent(authorizationId)}/revoke`)\n }\n\n private buildUrl(path: string, query?: Record<string, string | number | boolean | undefined>): string {\n const base = this.baseUrl || (typeof window !== \"undefined\" ? window.location.origin : \"http://placeholder\")\n const url = new URL(path, base)\n // If baseUrl was empty, strip the placeholder origin\n const fullUrl = this.baseUrl ? url.toString() : url.pathname + url.search\n if (query) {\n const u = new URL(this.baseUrl ? fullUrl : fullUrl, base)\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) u.searchParams.set(k, String(v))\n }\n return this.baseUrl ? u.toString() : u.pathname + u.search\n }\n return fullUrl\n }\n}\n","/**\n * Auth provides the token used for `Authorization: Bearer <token>` on each request.\n * Implementations:\n * - StaticTokenAuth — fixed string (server-side, OAuth access token, API token)\n * - CallbackAuth — caller-supplied async function (BYO logic)\n * - supabaseAuth — pulls JWT from a Supabase client live (browser frontends)\n */\nexport interface Auth {\n /** Returns the current Bearer token, or null if not authenticated. */\n getToken(): Promise<string | null>\n}\n\nexport class StaticTokenAuth implements Auth {\n constructor(private token: string) {}\n async getToken() {\n return this.token\n }\n}\n\nexport class CallbackAuth implements Auth {\n constructor(private fn: () => string | null | Promise<string | null>) {}\n async getToken() {\n return this.fn()\n }\n}\n\ninterface SupabaseLikeClient {\n auth: {\n getSession(): Promise<{ data: { session: { access_token: string } | null } }>\n }\n}\n\n/** Pulls a JWT from a Supabase v2 client. Caller supplies their own supabase. */\nexport function supabaseAuth(supabase: SupabaseLikeClient): Auth {\n return {\n async getToken() {\n const { data } = await supabase.auth.getSession()\n return data.session?.access_token ?? null\n },\n }\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/binary-response.ts","../src/core.ts","../src/auth.ts"],"names":[],"mappings":";;;;AACA,eAAsB,kBAAA,CAAmB,UAAoB,QAAA,EAAwC;AACnG,EAAA,MAAM,UAAA,GAAa,QAAA,CAAS,OAAA,CAAQ,GAAA,CAAI,gBAAgB,CAAA;AACxD,EAAA,IAAI,UAAA,KAAe,IAAA,KAAS,CAAC,OAAA,CAAQ,IAAA,CAAK,UAAU,CAAA,IAAK,MAAA,CAAO,UAAU,CAAA,GAAI,QAAA,CAAA,EAAW;AACvF,IAAA,MAAM,QAAA,CAAS,MAAM,MAAA,EAAO;AAC5B,IAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,EACnE;AACA,EAAA,IAAI,CAAC,QAAA,CAAS,IAAA,EAAM,OAAO,IAAI,YAAY,CAAC,CAAA;AAC5C,EAAA,MAAM,MAAA,GAAS,QAAA,CAAS,IAAA,CAAK,SAAA,EAAU;AACvC,EAAA,MAAM,SAAuB,EAAC;AAC9B,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,IAAI;AACF,IAAA,OAAO,IAAA,EAAM;AACX,MAAA,MAAM,IAAA,GAAO,MAAM,MAAA,CAAO,IAAA,EAAK;AAC/B,MAAA,IAAI,KAAK,IAAA,EAAM;AACf,MAAA,MAAA,IAAU,KAAK,KAAA,CAAM,UAAA;AACrB,MAAA,IAAI,SAAS,QAAA,EAAU;AACrB,QAAA,MAAM,OAAO,MAAA,EAAO;AACpB,QAAA,MAAM,IAAI,MAAM,iDAAiD,CAAA;AAAA,MACnE;AACA,MAAA,MAAA,CAAO,IAAA,CAAK,KAAK,KAAK,CAAA;AAAA,IACxB;AAAA,EACF,CAAA,SAAE;AAAU,IAAA,MAAA,CAAO,WAAA,EAAY;AAAA,EAAE;AACjC,EAAA,MAAM,KAAA,GAAQ,IAAI,UAAA,CAAW,MAAM,CAAA;AACnC,EAAA,IAAI,MAAA,GAAS,CAAA;AACb,EAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAAE,IAAA,KAAA,CAAM,GAAA,CAAI,OAAO,MAAM,CAAA;AAAG,IAAA,MAAA,IAAU,KAAA,CAAM,UAAA;AAAA,EAAW;AACnF,EAAA,OAAO,KAAA,CAAM,MAAA;AACf;ACAA,IAAM,WAAA,GAAoD,QAAA,CAAkB;AAUrE,IAAM,aAAA,GAAgB,iBAAA;AAoB7B,IAAM,YAAY,MAAe,OAAO,WAAW,WAAA,IAAe,OAAO,OAAO,QAAA,KAAa,WAAA;AA0FtF,IAAM,mBAAN,MAAuB;AAAA,EACnB,OAAA;AAAA,EACA,IAAA;AAAA,EACA,SAAA;AAAA;AAAA,EAEA,WAAA;AAAA;AAAA,EAEQ,gBAAA;AAAA,EACA,aAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQjB,IAAI,KAAA,GAAsB;AACxB,IAAA,OAAO,IAAA,CAAK,aAAA,IAAiB,UAAA,CAAW,KAAA,CAAM,KAAK,UAAU,CAAA;AAAA,EAC/D;AAAA;AAAA,EAGS,WAAA;AAAA,EAET,YAAY,IAAA,EAAqB;AAC/B,IAAA,IAAA,CAAK,OAAA,GAAU,IAAA,CAAK,OAAA,CAAQ,OAAA,CAAQ,OAAO,EAAE,CAAA;AAC7C,IAAA,IAAA,CAAK,OAAO,IAAA,CAAK,IAAA;AACjB,IAAA,IAAA,CAAK,gBAAgB,IAAA,CAAK,KAAA;AAC1B,IAAA,IAAA,CAAK,SAAA,GAAY,KAAK,SAAA,IAAa,GAAA;AACnC,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA,CAAK,WAAA,IAAe,CAAA,IAAA,EAAO,WAAW,CAAA,CAAA;AACzD,IAAA,IAAA,CAAK,cAAc,IAAA,CAAK,WAAA;AAGxB,IAAA,IAAA,CAAK,gBAAA,GAAmB,IAAA,CAAK,WAAA,KAAgB,MAAA,IAAa,CAAC,SAAA,EAAU;AAAA,EACvE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,IAAA,CACZ,MAAA,EACA,IAAA,EACA,SACA,IAAA,EACY;AACZ,IAAA,MAAM,GAAA,GAAM,IAAA,CAAK,QAAA,CAAS,IAAA,EAAM,QAAQ,KAAK,CAAA;AAE7C,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,IAAA,CAAK,QAAA,EAAS;AAKvC,IAAA,MAAM,UAAA,GACJ,OAAO,QAAA,KAAa,WAAA,IAAe,QAAQ,IAAA,YAAgB,QAAA;AAC7D,IAAA,MAAM,OAAA,GAAkC;AAAA,MACtC,GAAI,UAAA,GAAa,EAAC,GAAI,EAAE,gBAAgB,kBAAA,EAAmB;AAAA,MAC3D,GAAI,IAAA,CAAK,gBAAA,GAAmB,EAAE,CAAC,aAAa,GAAG,IAAA,CAAK,WAAA,EAAY,GAAI,EAAC;AAAA;AAAA;AAAA,MAGrE,GAAI,IAAA,CAAK,WAAA,GAAc,EAAE,CAAC,gBAAgB,GAAG,IAAA,CAAK,WAAA,EAAY,GAAI,EAAC;AAAA,MACnE,GAAI,OAAA,CAAQ,OAAA,IAAW;AAAC,KAC1B;AACA,IAAA,IAAI,KAAA,EAAO,OAAA,CAAQ,eAAe,CAAA,GAAI,UAAU,KAAK,CAAA,CAAA;AAErD,IAAA,MAAM,EAAA,GAAK,IAAI,eAAA,EAAgB;AAC/B,IAAA,MAAM,YAAY,UAAA,CAAW,MAAM,GAAG,KAAA,EAAM,EAAG,KAAK,SAAS,CAAA;AAC7D,IAAA,MAAM,QAAQ,MAAM,EAAA,CAAG,KAAA,CAAM,OAAA,CAAQ,QAAQ,MAAM,CAAA;AACnD,IAAA,OAAA,CAAQ,QAAQ,gBAAA,CAAiB,OAAA,EAAS,OAAO,EAAE,IAAA,EAAM,MAAM,CAAA;AAC/D,IAAA,IAAI,OAAA,CAAQ,MAAA,EAAQ,OAAA,EAAS,KAAA,EAAM;AAEnC,IAAA,IAAI;AACF,MAAA,EAAA,CAAG,OAAO,cAAA,EAAe;AACzB,MAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,KAAA,CAAM,GAAA,EAAK;AAAA,QAChC,MAAA;AAAA,QACA,OAAA;AAAA,QACA,IAAA,EACE,OAAA,CAAQ,IAAA,KAAS,KAAA,CAAA,GACb,KAAA,CAAA,GACA,UAAA,GACG,OAAA,CAAQ,IAAA,GACT,IAAA,CAAK,SAAA,CAAU,OAAA,CAAQ,IAAI,CAAA;AAAA,QACnC,QAAQ,EAAA,CAAG;AAAA,OACZ,CAAA;AAED,MAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,QAAA,IAAI,UAAmC,EAAC;AACxC,QAAA,IAAI;AACF,UAAA,OAAA,GAAU,MAAM,IAAI,IAAA,EAAK;AAAA,QAC3B,CAAA,CAAA,MAAQ;AAAA,QAER;AACA,QAAA,iBAAA,CAAkB,GAAA,CAAI,QAAQ,OAAO,CAAA;AAAA,MACvC;AAEA,MAAA,OAAO,MAAM,KAAK,GAAG,CAAA;AAAA,IACvB,CAAA,SAAE;AACA,MAAA,YAAA,CAAa,SAAS,CAAA;AACtB,MAAA,OAAA,CAAQ,MAAA,EAAQ,mBAAA,CAAoB,OAAA,EAAS,KAAK,CAAA;AAAA,IACpD;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,YAAA,CAAa,MAAA,EAAgB,IAAA,EAAc,OAAA,EAAsE;AACrH,IAAA,IAAI,CAAC,MAAA,CAAO,aAAA,CAAc,OAAA,CAAQ,QAAQ,CAAA,IAAK,OAAA,CAAQ,QAAA,GAAW,CAAA,EAAG,MAAM,IAAI,KAAA,CAAM,qCAAqC,CAAA;AAC1H,IAAA,OAAO,IAAA,CAAK,IAAA,CAAK,MAAA,EAAQ,IAAA,EAAM,OAAA,EAAS,CAAC,QAAA,KAAa,kBAAA,CAAmB,QAAA,EAAU,OAAA,CAAQ,QAAQ,CAAC,CAAA;AAAA,EACtG;AAAA,EAEA,MAAM,OAAA,CAAW,MAAA,EAAgB,IAAA,EAAc,OAAA,GAA0B,EAAC,EAAe;AACvF,IAAA,OAAO,IAAA,CAAK,IAAA;AAAA,MAAK,MAAA;AAAA,MAAQ,IAAA;AAAA,MAAM,OAAA;AAAA,MAAS,OAAO,GAAA;AAAA;AAAA,QAE7C,IAAI,MAAA,KAAW,GAAA,GAAO,MAAA,GAAoB,MAAM,IAAI,IAAA;AAAK;AAAA,KAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,WAAA,CAAY,MAAA,EAAgB,IAAA,EAAc,OAAA,GAA0B,EAAC,EAAoB;AAC7F,IAAA,OAAO,IAAA,CAAK,KAAK,MAAA,EAAQ,IAAA,EAAM,SAAS,CAAC,GAAA,KAAQ,GAAA,CAAI,IAAA,EAAM,CAAA;AAAA,EAC7D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAM,EAAA,GAA8C;AAClD,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,OAAA,CAAkD,OAAO,QAAQ,CAAA;AACxF,IAAA,OAAO,GAAA,CAAI,IAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAM,aAAA,GAAyC;AAC7C,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,OAAA,CAAkC,OAAO,uBAAuB,CAAA;AACvF,IAAA,OAAO,GAAA,CAAI,IAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAM,mBAAmB,eAAA,EAAwC;AAC/D,IAAA,MAAM,KAAK,OAAA,CAAQ,MAAA,EAAQ,yBAAyB,kBAAA,CAAmB,eAAe,CAAC,CAAA,OAAA,CAAS,CAAA;AAAA,EAClG;AAAA,EAEQ,QAAA,CAAS,MAAc,KAAA,EAAuE;AACpG,IAAA,MAAM,IAAA,GAAO,KAAK,OAAA,KAAY,OAAO,WAAW,WAAA,GAAc,MAAA,CAAO,SAAS,MAAA,GAAS,oBAAA,CAAA;AACvF,IAAA,MAAM,GAAA,GAAM,IAAI,GAAA,CAAI,IAAA,EAAM,IAAI,CAAA;AAE9B,IAAA,MAAM,OAAA,GAAU,KAAK,OAAA,GAAU,GAAA,CAAI,UAAS,GAAI,GAAA,CAAI,WAAW,GAAA,CAAI,MAAA;AACnE,IAAA,IAAI,KAAA,EAAO;AACT,MAAA,MAAM,IAAI,IAAI,GAAA,CAAI,KAAK,OAAA,GAAU,OAAA,GAAU,SAAS,IAAI,CAAA;AACxD,MAAA,KAAA,MAAW,CAAC,CAAA,EAAG,CAAC,KAAK,MAAA,CAAO,OAAA,CAAQ,KAAK,CAAA,EAAG;AAC1C,QAAA,IAAI,CAAA,KAAM,QAAW,CAAA,CAAE,YAAA,CAAa,IAAI,CAAA,EAAG,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,MACtD;AACA,MAAA,OAAO,KAAK,OAAA,GAAU,CAAA,CAAE,UAAS,GAAI,CAAA,CAAE,WAAW,CAAA,CAAE,MAAA;AAAA,IACtD;AACA,IAAA,OAAO,OAAA;AAAA,EACT;AACF;;;ACjUO,IAAM,kBAAN,MAAsC;AAAA,EAC3C,YAAoB,KAAA,EAAe;AAAf,IAAA,IAAA,CAAA,KAAA,GAAA,KAAA;AAAA,EAAgB;AAAA,EAAhB,KAAA;AAAA,EACpB,MAAM,QAAA,GAAW;AACf,IAAA,OAAO,IAAA,CAAK,KAAA;AAAA,EACd;AACF;AAEO,IAAM,eAAN,MAAmC;AAAA,EACxC,YAAoB,EAAA,EAAkD;AAAlD,IAAA,IAAA,CAAA,EAAA,GAAA,EAAA;AAAA,EAAmD;AAAA,EAAnD,EAAA;AAAA,EACpB,MAAM,QAAA,GAAW;AACf,IAAA,OAAO,KAAK,EAAA,EAAG;AAAA,EACjB;AACF;AASO,SAAS,aAAa,QAAA,EAAoC;AAC/D,EAAA,OAAO;AAAA,IACL,MAAM,QAAA,GAAW;AACf,MAAA,MAAM,EAAE,IAAA,EAAK,GAAI,MAAM,QAAA,CAAS,KAAK,UAAA,EAAW;AAChD,MAAA,OAAO,IAAA,CAAK,SAAS,YAAA,IAAgB,IAAA;AAAA,IACvC;AAAA,GACF;AACF","file":"chunk-3Y5TV37H.js","sourcesContent":["/** Bound decoded response bytes even when Content-Length is absent or compressed. */\nexport async function readBinaryResponse(response: Response, maxBytes: number): Promise<ArrayBuffer> {\n const advertised = response.headers.get(\"content-length\")\n if (advertised !== null && (!/^\\d+$/.test(advertised) || Number(advertised) > maxBytes)) {\n await response.body?.cancel()\n throw new Error(\"Binary response exceeds its declared size limit\")\n }\n if (!response.body) return new ArrayBuffer(0)\n const reader = response.body.getReader()\n const chunks: Uint8Array[] = []\n let length = 0\n try {\n while (true) {\n const next = await reader.read()\n if (next.done) break\n length += next.value.byteLength\n if (length > maxBytes) {\n await reader.cancel()\n throw new Error(\"Binary response exceeds its declared size limit\")\n }\n chunks.push(next.value)\n }\n } finally { reader.releaseLock() }\n const bytes = new Uint8Array(length)\n let offset = 0\n for (const chunk of chunks) { bytes.set(chunk, offset); offset += chunk.byteLength }\n return bytes.buffer\n}\n","/**\n * The client core: configuration, auth, the request transport and the\n * account-identity calls — everything a resource needs, and none of the\n * resources themselves.\n *\n * {@link NodaroClient} (`createClient`) extends it with every resource. An app\n * that uses only a few resources can extend it with just those, so it loads\n * (and its tests depend on) only the resource modules it actually uses:\n *\n * class AppClient extends NodaroClientCore {\n * readonly jobs = new JobsResource(this)\n * readonly nodes = new NodesResource(this) // needs `jobs` on the same client\n * }\n *\n * A resource that calls another one through the client says so in its\n * constructor type (`NodesResource` takes a core that also has `jobs`), so a\n * client missing it fails to compile rather than at run time.\n */\nimport { readBinaryResponse } from \"./binary-response.js\"\nimport { throwFromResponse } from \"./errors.js\"\nimport type { Auth } from \"./auth.js\"\nimport type { DeveloperAppKind, DeveloperAppScope } from \"./resources/developer-apps.js\"\nimport { WORKSPACE_HEADER, type MeOrganizations } from \"@nodaro/shared/organizations\"\n\n/** Replaced at build time by tsup `define` from package.json. The fallback\n * keeps `tsx`/vitest runs of the source working, where no define applies. */\ndeclare const __SDK_VERSION__: string | undefined\nconst SDK_VERSION = typeof __SDK_VERSION__ === \"string\" ? __SDK_VERSION__ : \"0.0.0-dev\"\n\n/**\n * Identifies the calling package to the backend, which records it as the job's\n * provenance (`jobs.source` / `source_detail`) so an operator can tell a CLI\n * run from an SDK integration from a browser session.\n *\n * Only `sdk/…` and `cli/…` are honoured server-side; anything else is ignored\n * rather than trusted, since this is an unauthenticated header.\n */\nexport const CLIENT_HEADER = \"X-Nodaro-Client\"\n\n/**\n * In a browser the header is redundant AND a liability, so it is not sent.\n *\n * Redundant: the browser already sends `Origin`, which names the actual product\n * (`studio.nodaro.ai`) rather than merely the library it used — and the backend\n * deliberately prefers Origin over this header, so a browser-sent value is\n * discarded on arrival.\n *\n * A liability: `X-Nodaro-Client` is not a CORS-safelisted request header, so a\n * browser sending it requires an exact match in the server's\n * `Access-Control-Allow-Headers`. Any deployment whose backend predates that\n * allowlist entry would fail the PREFLIGHT — breaking every API call from the\n * app, not just its provenance. Self-hosted and lagging deployments make that a\n * real risk, and nothing in a browser app would reveal the cause.\n *\n * An EXPLICIT `clientLabel` is always sent regardless: a caller that names\n * itself has opted in deliberately, and that intent outranks this default.\n */\nconst isBrowser = (): boolean => typeof window !== \"undefined\" && typeof window.document !== \"undefined\"\n\nexport interface ClientOptions {\n /** Backend base URL, e.g. \"https://nodaro.example.com\" or empty string for same-origin. */\n baseUrl: string\n /** Auth provider. Use StaticTokenAuth, supabaseAuth, or CallbackAuth. */\n auth: Auth\n /** Optional fetch override (for tests or custom transports). */\n fetch?: typeof fetch\n /** Default request timeout in ms. Default 60s. */\n timeoutMs?: number\n /**\n * Overrides the `X-Nodaro-Client` label. Defaults to `sdk/<version>`.\n *\n * Exists for `@nodaro/cli`, which is a wrapper AROUND this SDK: without an\n * override every CLI invocation would be recorded as an SDK call and the two\n * surfaces could never be told apart.\n */\n clientLabel?: string\n /**\n * The workspace every request acts in — sent as `X-Nodaro-Workspace`.\n *\n * It selects which workspace a LIST reads from and where a CREATE lands.\n * It never authorizes: reading, updating, deleting or running an\n * identified object is decided by that object's own workspace, so a\n * forgotten header cannot hide work and a forged one cannot reach anyone\n * else's. Omit it to work in the caller's personal space.\n *\n * Prefer {@link NodaroClient.withWorkspace} for anything short-lived — one\n * client per workspace, no shared mutable selection to get wrong.\n */\n workspaceId?: string\n}\n\ninterface RequestOptions {\n body?: unknown\n query?: Record<string, string | number | boolean | undefined>\n headers?: Record<string, string>\n signal?: AbortSignal\n}\n\n/**\n * The authenticated user's canonical identity (`GET /v1/me`). A token-\n * introspection primitive: any valid bearer token (first-party Supabase JWT or\n * a developer-app OAuth token) resolves to its owner's identity. Mirrors the\n * `profiles` identity columns server-side — the route is the source of truth.\n */\nexport interface UserIdentity {\n /** Nodaro user id (= the Supabase auth user id). */\n readonly id: string\n readonly email: string\n /** Human-readable display name (from `profiles.full_name`); `null` if unset. */\n readonly displayName: string | null\n /** Avatar URL; `null` if unset. */\n readonly avatarUrl: string | null\n /** Subscription tier (e.g. \"free\", \"pro\"). */\n readonly tier: string\n /**\n * Whether the user holds an admin role. DESCRIPTIVE only — use it to decide\n * whether to render admin surfaces without capability-probing an admin\n * endpoint; every admin API stays enforced server-side regardless.\n */\n readonly isAdmin: boolean\n}\n\n/**\n * An app the user let into their account through OAuth (`GET\n * /v1/me/connected-apps`): a developer app, an AI assistant's MCP connector, or\n * a self-hosted install. One entry per grant still in force.\n */\nexport interface ConnectedApp {\n /** The grant's id — what {@link NodaroClient.revokeConnectedApp} takes. */\n readonly authorizationId: string\n /** The app's name; `null` if it has none. A `dynamic_mcp` client chose its own. */\n readonly name: string | null\n /**\n * How the app came to exist: `user` (a developer app someone registered),\n * `first_party_mcp`, `dynamic_mcp` (an MCP client that registered itself) or\n * `community_instance` (a self-hosted install).\n */\n readonly kind: DeveloperAppKind | (string & {})\n readonly homepageUrl: string | null\n /** The scopes this grant holds. */\n readonly scopes: readonly DeveloperAppScope[]\n /** When the user granted access (ISO 8601). */\n readonly connectedAt: string\n /** The latest use of any token issued under the grant; `null` if never used. */\n readonly lastUsedAt: string | null\n}\n\nexport class NodaroClientCore {\n readonly baseUrl: string\n readonly auth: Auth\n readonly timeoutMs: number\n /** Value sent as `X-Nodaro-Client`; recorded by the backend as job provenance. */\n readonly clientLabel: string\n /** Whether to actually send it — see the note on {@link CLIENT_HEADER}. */\n private readonly sendClientHeader: boolean\n private readonly fetchOverride: typeof fetch | undefined\n\n /**\n * Resolved lazily so consumers can swap `globalThis.fetch` after the\n * client has been constructed (e.g. test mocks). Always rebound to the\n * global object — native fetch throws \"Illegal invocation\" when its\n * `this` is anything else.\n */\n get fetch(): typeof fetch {\n return this.fetchOverride ?? globalThis.fetch.bind(globalThis)\n }\n\n /** The workspace this client acts in; undefined = the personal space. */\n readonly workspaceId: string | undefined\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\") // strip trailing slash\n this.auth = opts.auth\n this.fetchOverride = opts.fetch\n this.timeoutMs = opts.timeoutMs ?? 60_000\n this.clientLabel = opts.clientLabel ?? `sdk/${SDK_VERSION}`\n this.workspaceId = opts.workspaceId\n // Explicit label = deliberate opt-in, always sent. The default is suppressed\n // in browsers — see the note on CLIENT_HEADER.\n this.sendClientHeader = opts.clientLabel !== undefined || !isBrowser()\n }\n\n /**\n * The transport: build the request, send it, throw the typed error on a\n * non-2xx via {@link throwFromResponse}, then read the body via `read` — all\n * inside the abort timer, so `timeoutMs` bounds the body download too (a\n * stalled CSV stream aborts, it does not hang forever). Both the JSON path\n * ({@link request}) and the text path ({@link requestText}) go through here.\n */\n private async send<T>(\n method: string,\n path: string,\n options: RequestOptions,\n read: (res: Response) => Promise<T>,\n ): Promise<T> {\n const url = this.buildUrl(path, options.query)\n\n const token = await this.auth.getToken()\n // A `FormData` body is a multipart upload: let the runtime set\n // `Content-Type: multipart/form-data; boundary=…` itself (a manual JSON\n // content-type corrupts the boundary), and send the body as-is rather than\n // JSON-stringifying it. Every other body stays JSON, exactly as before.\n const isFormData =\n typeof FormData !== \"undefined\" && options.body instanceof FormData\n const headers: Record<string, string> = {\n ...(isFormData ? {} : { \"Content-Type\": \"application/json\" }),\n ...(this.sendClientHeader ? { [CLIENT_HEADER]: this.clientLabel } : {}),\n // Before per-request headers: a resource that needs to reach outside\n // this client's workspace says so explicitly and wins.\n ...(this.workspaceId ? { [WORKSPACE_HEADER]: this.workspaceId } : {}),\n ...(options.headers ?? {}),\n }\n if (token) headers[\"Authorization\"] = `Bearer ${token}`\n\n const ac = new AbortController()\n const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs)\n const abort = () => ac.abort(options.signal?.reason)\n options.signal?.addEventListener(\"abort\", abort, { once: true })\n if (options.signal?.aborted) abort()\n\n try {\n ac.signal.throwIfAborted()\n const res = await this.fetch(url, {\n method,\n headers,\n body:\n options.body === undefined\n ? undefined\n : isFormData\n ? (options.body as FormData)\n : JSON.stringify(options.body),\n signal: ac.signal,\n })\n\n if (!res.ok) {\n let errBody: Record<string, unknown> = {}\n try {\n errBody = await res.json() as Record<string, unknown>\n } catch {\n // Empty/non-JSON body — fall through with empty errBody\n }\n throwFromResponse(res.status, errBody)\n }\n\n return await read(res)\n } finally {\n clearTimeout(timeoutId)\n options.signal?.removeEventListener(\"abort\", abort)\n }\n }\n\n /** Authenticated, bounded binary reads through the normal timeout/error transport. */\n async requestBytes(method: string, path: string, options: RequestOptions & { maxBytes: number }): Promise<ArrayBuffer> {\n if (!Number.isSafeInteger(options.maxBytes) || options.maxBytes < 1) throw new Error(\"maxBytes must be a positive integer\")\n return this.send(method, path, options, (response) => readBinaryResponse(response, options.maxBytes))\n }\n\n async request<T>(method: string, path: string, options: RequestOptions = {}): Promise<T> {\n return this.send(method, path, options, async (res) =>\n // 204 No Content\n res.status === 204 ? (undefined as T) : ((await res.json()) as T),\n )\n }\n\n /**\n * Like {@link request}, for endpoints that answer text (CSV exports). Errors\n * are still the JSON envelope and throw the same typed errors, and the read is\n * bounded by the same timeout as the request.\n */\n async requestText(method: string, path: string, options: RequestOptions = {}): Promise<string> {\n return this.send(method, path, options, (res) => res.text())\n }\n\n /**\n * `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).\n * Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the\n * token is missing/invalid, and the SDK's other typed errors as usual.\n *\n * On an instance with organizations it also carries what the caller belongs\n * to. THREE states, and collapsing them is wrong in a way users feel: the\n * organization fields ABSENT means this instance has no organizations at\n * all; present and empty means the account belongs to none; and\n * `organizationsUnavailable` means the lookup failed — keep whatever\n * selection you already had rather than concluding the person was removed\n * from everything.\n */\n async me(): Promise<UserIdentity & MeOrganizations> {\n const res = await this.request<{ data: UserIdentity & MeOrganizations }>(\"GET\", \"/v1/me\")\n return res.data\n }\n\n /**\n * `GET /v1/me/connected-apps` → every app with access to the signed-in\n * user's account (see {@link ConnectedApp}), newest grant first.\n *\n * **A signed-in browser session only.** The route answers a Supabase\n * session token (`supabaseAuth(…)`, `createSharedSupabaseClient`, or a\n * `StaticTokenAuth` holding one) and refuses every other credential with\n * `UnauthorizedError` (\"Sign in required\"): an app token or an API key must\n * not list the grants it lives under. Build your \"Connected apps\" settings\n * screen on this; a server-side integration cannot call it.\n */\n async connectedApps(): Promise<ConnectedApp[]> {\n const res = await this.request<{ apps: ConnectedApp[] }>(\"GET\", \"/v1/me/connected-apps\")\n return res.apps\n }\n\n /**\n * `POST /v1/me/connected-apps/:authorizationId/revoke` — ends an app's\n * access: the grant and every token issued under it, at once (the app's next\n * call is a 401). Resolves when done.\n *\n * A signed-in browser session only, like {@link connectedApps}. Throws\n * `NotFoundError` when the grant is not the user's or is already revoked.\n */\n async revokeConnectedApp(authorizationId: string): Promise<void> {\n await this.request(\"POST\", `/v1/me/connected-apps/${encodeURIComponent(authorizationId)}/revoke`)\n }\n\n private buildUrl(path: string, query?: Record<string, string | number | boolean | undefined>): string {\n const base = this.baseUrl || (typeof window !== \"undefined\" ? window.location.origin : \"http://placeholder\")\n const url = new URL(path, base)\n // If baseUrl was empty, strip the placeholder origin\n const fullUrl = this.baseUrl ? url.toString() : url.pathname + url.search\n if (query) {\n const u = new URL(this.baseUrl ? fullUrl : fullUrl, base)\n for (const [k, v] of Object.entries(query)) {\n if (v !== undefined) u.searchParams.set(k, String(v))\n }\n return this.baseUrl ? u.toString() : u.pathname + u.search\n }\n return fullUrl\n }\n}\n","/**\n * Auth provides the token used for `Authorization: Bearer <token>` on each request.\n * Implementations:\n * - StaticTokenAuth — fixed string (server-side, OAuth access token, API token)\n * - CallbackAuth — caller-supplied async function (BYO logic)\n * - supabaseAuth — pulls JWT from a Supabase client live (browser frontends)\n */\nexport interface Auth {\n /** Returns the current Bearer token, or null if not authenticated. */\n getToken(): Promise<string | null>\n}\n\nexport class StaticTokenAuth implements Auth {\n constructor(private token: string) {}\n async getToken() {\n return this.token\n }\n}\n\nexport class CallbackAuth implements Auth {\n constructor(private fn: () => string | null | Promise<string | null>) {}\n async getToken() {\n return this.fn()\n }\n}\n\ninterface SupabaseLikeClient {\n auth: {\n getSession(): Promise<{ data: { session: { access_token: string } | null } }>\n }\n}\n\n/** Pulls a JWT from a Supabase v2 client. Caller supplies their own supabase. */\nexport function supabaseAuth(supabase: SupabaseLikeClient): Auth {\n return {\n async getToken() {\n const { data } = await supabase.auth.getSession()\n return data.session?.access_token ?? null\n },\n }\n}\n"]}
|
|
@@ -24,9 +24,11 @@ var CastSwapResource = class {
|
|
|
24
24
|
return this.client.request("POST", "/v1/cast-swap/estimate", { body: params });
|
|
25
25
|
}
|
|
26
26
|
/**
|
|
27
|
-
* Quote finishing a
|
|
27
|
+
* Quote finishing a run at Full Quality (`POST
|
|
28
28
|
* /v1/cast-swap/:jobId/full-quality/estimate`); spends nothing. `jobId` is a
|
|
29
|
-
* finished DRAFT run
|
|
29
|
+
* finished Seedance 2.5 DRAFT run, or a finished Kling 4 run at 720p (a Cast
|
|
30
|
+
* Swap or trend run); the quote's `kind` says whether it is the same take
|
|
31
|
+
* re-rendered or a new take. The quote
|
|
30
32
|
* does not look for a Full Quality already running: when one is,
|
|
31
33
|
* {@link fullQuality} answers it (`existing: true`) and charges nothing.
|
|
32
34
|
* Refusals: {@link CastSwapFullQualityRefusalCode}.
|
|
@@ -35,12 +37,13 @@ var CastSwapResource = class {
|
|
|
35
37
|
return this.client.request("POST", `/v1/cast-swap/${encodeURIComponent(jobId)}/full-quality/estimate`, { body: {} });
|
|
36
38
|
}
|
|
37
39
|
/**
|
|
38
|
-
* Finish a
|
|
39
|
-
*
|
|
40
|
-
*
|
|
40
|
+
* Finish a run at Full Quality (`POST /v1/cast-swap/:jobId/full-quality`):
|
|
41
|
+
* at 1080p, with the clip's audio, as a new job linked to the run — a
|
|
42
|
+
* Seedance 2.5 draft's own take re-rendered, or a new Kling 4 take from a
|
|
43
|
+
* 720p run's inputs (the quote's `kind`). SPENDS CREDITS: quote it first with
|
|
41
44
|
* {@link fullQualityEstimate}. Answers 202 `{ jobId }`, or 200
|
|
42
|
-
* `{ jobId, existing: true }` when this
|
|
43
|
-
* running or done (one per
|
|
45
|
+
* `{ jobId, existing: true }` when this run's Full Quality is already
|
|
46
|
+
* running or done (one per run; a failed one may be asked again). Follow
|
|
44
47
|
* the job with `client.jobs.get(jobId)`. No `Idempotency-Key` is taken: the
|
|
45
48
|
* server keys the job by the draft. The new job keeps the draft's origin.
|
|
46
49
|
* Refusals: {@link CastSwapFullQualityRefusalCode}.
|
|
@@ -52,5 +55,5 @@ var CastSwapResource = class {
|
|
|
52
55
|
};
|
|
53
56
|
|
|
54
57
|
exports.CastSwapResource = CastSwapResource;
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
56
|
-
//# sourceMappingURL=chunk-
|
|
58
|
+
//# sourceMappingURL=chunk-55ZX6TE3.cjs.map
|
|
59
|
+
//# sourceMappingURL=chunk-55ZX6TE3.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/resources/cast-swap.ts"],"names":[],"mappings":";;;AAwdO,IAAM,mBAAN,MAAuB;AAAA,EAC5B,YAAoB,MAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA2B;AAAA,EAA3B,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASpB,GAAA,CAAI,QAAwB,OAAA,EAA6E;AACvG,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,eAAA,EAAiB;AAAA,MAClD,IAAA,EAAM,MAAA;AAAA,MACN,GAAI,OAAA,EAAS,cAAA,GAAiB,EAAE,OAAA,EAAS,EAAE,iBAAA,EAAmB,OAAA,CAAQ,cAAA,EAAe,EAAE,GAAI;AAAC,KAC7F,CAAA;AAAA,EACH;AAAA;AAAA,EAGA,SAAS,MAAA,EAAmD;AAC1D,IAAA,OAAO,IAAA,CAAK,OAAO,OAAA,CAAQ,MAAA,EAAQ,0BAA0B,EAAE,IAAA,EAAM,QAAQ,CAAA;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,oBAAoB,KAAA,EAAqD;AACvE,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,CAAA,cAAA,EAAiB,kBAAA,CAAmB,KAAK,CAAC,CAAA,sBAAA,CAAA,EAA0B,EAAE,IAAA,EAAM,IAAI,CAAA;AAAA,EACrH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,WAAA,CAAY,KAAA,EAAe,MAAA,GAAoC,EAAC,EAAuC;AACrG,IAAA,MAAM,IAAA,GAAO,OAAO,SAAA,KAAc,MAAA,GAAY,EAAE,SAAA,EAAW,MAAA,CAAO,SAAA,EAAU,GAAI,EAAC;AACjF,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAQ,MAAA,EAAQ,CAAA,cAAA,EAAiB,kBAAA,CAAmB,KAAK,CAAC,CAAA,aAAA,CAAA,EAAiB,EAAE,IAAA,EAAM,CAAA;AAAA,EACxG;AACF","file":"chunk-55ZX6TE3.cjs","sourcesContent":["import type { NodaroClientCore } from \"../core.js\"\n\n/**\n * Cast Swap — replace the people, pets, products and/or the location in a\n * short video with your own references, keeping its motion, framing, timing\n * and sound (the clip's audio is kept, and the result is the clip's length).\n *\n * Before the video, each person, pet and product role's photos are turned\n * into one clean reference picture by an image model. This costs extra\n * credits (`breakdown.references` in the estimate), charged when it runs, even\n * if the video then fails; it adds about a minute, falls back to your own\n * photos if it fails, and is turned off with `prepareReferences: false`.\n * Locations are not prepared.\n *\n * Engines: a run is made on Seedance 2.5 (`engine` left out, or\n * `\"seedance-2-5\"`) or Kling 4 (`\"kling-4\"`). Kling 4 renders 720p (its\n * default) or 1080p and has no draft; it makes a new take guided by the clip\n * and pictures, so its Full Quality is a new take at 1080p rather than the\n * same take re-rendered (`CastSwapFullQualityEstimate.kind`).\n *\n * Cloud edition only: `/v1/cast-swap` is served by the cloud plugin and 404s\n * on self-hosted installs. `run()` answers 202 with a job id; follow it with\n * `client.jobs.get(jobId)` / `client.jobs.getStatus(jobId)` like any other job.\n *\n * Repeats: pass `{ idempotencyKey }` to `run()` and it is sent as the\n * `Idempotency-Key` header. A repeat with the same key within the credit\n * guard's dedup window (about 10 s) answers 200 `{ jobId, deduped: true }` with\n * an `X-Dedup-Hit` header; a later one answers 202 with the first run's\n * `jobId`. Nothing new is started or charged.\n *\n * Refusals (thrown as a `NodaroError` subclass; body `{ error: { code, message } }`), before\n * anything is charged: 400 `validation_error` (the body: no role with images,\n * no reference and no `prePrompt` or `postPrompt`, more roles of a kind\n * than allowed, more than 30 images in all, `draft: true` above 480p, a\n * `prePrompt` or `postPrompt` over 4,000 characters, a `faceMasking` setting\n * out of range); on Kling 4, `draft_unsupported` (`draft: true`),\n * `resolution_unsupported` (480p), `output_format_unsupported` (a MOV edit\n * output) and `too_many_images` (more than 10 images in the run, across every\n * role and fixed reference); `raw_reference_token` (also in a reference's `line`),\n * `invalid_role_mention` (also any `{role:N}` in a reference's `line`),\n * `unsafe_url`, `video_unreadable`, `clip_out_of_range` (4–30 s),\n * `preview_out_of_range`, `no_roles_in_preview` (a preview with every role\n * `inPreview: false`, always refused even with a prompt, or with no role kept\n * in it that has images and no `prePrompt` or `postPrompt`), `prompt_too_long`\n * (the prompt would build too long: a `{role:N}` reads as the role's name when\n * it sends no images; Kling 4's cap is 8,000 characters); 401; 402 `insufficient_credits` (`run()` only); 404\n * (a `sessionId` you may not use, `run()` only); 422 `job_blocked` (`run()`\n * only); 503 `host_too_old` (both); 503 `estimate_unavailable` (`estimate()`\n * only).\n */\n\n/**\n * The video engine a Cast Swap run is made on. Left out = `\"seedance-2-5\"`.\n *\n * - `\"seedance-2-5\"`: 480p (the default, a draft unless `draft: false`), 720p\n * or 1080p; Full Quality re-renders a draft's own take at 1080p.\n * - `\"kling-4\"`: 720p (the default) or 1080p, never a draft (`draft: true`\n * answers 400 `draft_unsupported`); Full Quality makes a new take at 1080p\n * from a completed 720p run's inputs. A preview is still 4 s from\n * `previewStart`.\n */\nexport type CastSwapEngine = \"seedance-2-5\" | \"kling-4\"\n\n/**\n * Where a Kling 4 run cuts the clip to whole seconds (Kling renders whole\n * seconds; asked for more than the clip, it would stretch the motion):\n * - `\"cut-end\"` (the default): drop the fraction of a second at its end;\n * - `\"cut-start\"`: drop it at its start.\n */\nexport type CastSwapKlingLength = \"cut-end\" | \"cut-start\"\n\n/** Whether a swapped subject takes this trait from the references (`\"new\"`) or keeps the video's (`\"original\"`). */\nexport type CastSwapTraitSource = \"new\" | \"original\"\n\n/**\n * Preview-only settings, on every role kind; a full run (`preview` not `true`)\n * ignores both.\n */\nexport interface CastSwapRolePreviewSettings {\n /** The name used instead of `name` in a preview. Same rules as `name` (1–200 characters). */\n previewName?: string\n /**\n * `false` leaves this role out of the preview: its reference images are not\n * sent, not prepared and not charged, and a `{role:N}` naming it reads as its\n * `previewName`, else its `name`. Default `true`. A preview with every role\n * left out (always refused, even with a prompt), or with no role kept in it\n * that has images and no pre / post prompt, answers 400 `no_roles_in_preview`.\n */\n inPreview?: boolean\n}\n\n/** A person in the video to recast. */\nexport interface CastSwapPersonRole extends CastSwapRolePreviewSettings {\n kind: \"person\"\n /** How the person is told apart in the video, e.g. `\"the woman that attacks\"`. */\n name: string\n /** 0–3 images of ONE person. Empty or `null` keeps the original person. */\n images: string[] | null\n /** Body from the references or the video. Default `\"new\"`. */\n body?: CastSwapTraitSource\n /** Outfit from the references or the video. Default `\"new\"`. */\n outfit?: CastSwapTraitSource\n /** Hairstyle and haircut from the references or the video. Default `\"new\"`. */\n hair?: CastSwapTraitSource\n}\n\n/** The place the video happens in. */\nexport interface CastSwapLocationRole extends CastSwapRolePreviewSettings {\n kind: \"location\"\n /** How the place is named. Default `\"the setting\"`. */\n name?: string\n /** 0–3 images of ONE place. Empty or `null` keeps the original place. */\n images: string[] | null\n}\n\n/** An animal in the video to recast. */\nexport interface CastSwapPetRole extends CastSwapRolePreviewSettings {\n kind: \"pet\"\n /** How the animal is told apart in the video, e.g. `\"the dog\"`. */\n name: string\n /** 0–3 images of ONE animal. Empty or `null` keeps the original animal. */\n images: string[] | null\n /** Body (shape, size and build) from the references or the video. Default `\"new\"`. */\n body?: CastSwapTraitSource\n /** Coat (fur, colors and markings) from the references or the video. Default `\"new\"`. */\n coat?: CastSwapTraitSource\n}\n\n/** An object in the video to recast. */\nexport interface CastSwapProductRole extends CastSwapRolePreviewSettings {\n kind: \"product\"\n /** How the object is told apart in the video, e.g. `\"the can\"`. */\n name: string\n /** 0–3 images of ONE product. Empty or `null` keeps the original object. */\n images: string[] | null\n /** Shape and proportions from the references or the video. Default `\"new\"`. */\n shape?: CastSwapTraitSource\n /** Finish (colors, materials, label and markings) from the references or the video. Default `\"new\"`. */\n finish?: CastSwapTraitSource\n}\n\nexport type CastSwapRole = CastSwapPersonRole | CastSwapLocationRole | CastSwapPetRole | CastSwapProductRole\n\n/**\n * A fixed reference: 1–3 images of ONE thing that no role replaces, and the\n * line that says what it is (`\"the glass she drinks from\"`). Sent as given,\n * never prepared; its images count toward the 30.\n */\nexport interface CastSwapReference {\n /** 1–3 http(s) image URLs of one thing. */\n images: string[]\n /**\n * What it is, 1–500 characters. `{image}` in the line stands for this\n * reference's first image (`\"Use {image} for the sky\"`); a line without it\n * gets it appended. A `{role:N}` or a raw `{image:` / `{video:` / `{audio:`\n * token answers 400.\n */\n line: string\n}\n\n/**\n * Margins of the frame, each 0–49 %: a face whose centre lies in a margin is\n * not masked, so only faces inside the inner rectangle are. A margin left out\n * of a zone that is given is 0. (A `zone` left out altogether is 10 % at the\n * left and right: see `CastSwapFaceMasking.zone`.)\n */\nexport interface CastSwapFaceMaskingZone {\n /** % of the frame's width from the left edge. Default 0 in a zone that is given. */\n leftPct?: number\n /** % of the frame's width from the right edge. Default 0 in a zone that is given. */\n rightPct?: number\n /** % of the frame's height from the top edge. Default 0. */\n topPct?: number\n /** % of the frame's height from the bottom edge. Default 0. */\n bottomPct?: number\n}\n\n/**\n * Face masking: the faces in the source clip are covered before the video\n * model sees it, keeping their movement and lip timing while the rest of the\n * clip is sent as it is.\n *\n * - `\"off\"` (the same as leaving `faceMasking` out): the clip is sent as it is.\n * - `\"always\"`: the faces are masked before the first attempt. Its credits are\n * in the quote (`breakdown.masking`, counted in `totalCredits`).\n * - `\"auto\"`: the run starts unmasked; only if the video model refuses the\n * source clip is it retried once with the faces masked. Any other failure is\n * not retried. The extra credits, charged only when that retry runs, are the\n * estimate's `maskingCredits` (not in `totalCredits`).\n *\n * A completed job's output carries `faceMasking: { applied, reason }`:\n * `applied` is whether the video was made from the masked clip, and `reason`\n * is `\"always\"` (masked before the first attempt), `\"input_refused\"` (`auto`\n * retried after the source clip was refused), `\"no_faces\"` (no face was left\n * to mask after the filters: an `always` run continues unmasked; an `auto`\n * run fails with the original refusal instead) or `\"off\"`.\n */\nexport interface CastSwapFaceMasking {\n mode: \"off\" | \"always\" | \"auto\"\n /** What covers a masked face: `\"blur\"` (default) or `\"depth\"` (a depth-color view of it). */\n fill?: \"blur\" | \"depth\"\n /** Mask only faces at least this % of the frame's height, 0–100. Default 15. */\n minFaceHeightPct?: number\n /**\n * Mask only faces at most this % of the frame's height, 0–100. Default 0 (no\n * limit). When above 0 it must be at least `minFaceHeightPct` (else 400).\n */\n maxFaceHeightPct?: number\n /**\n * Leave out faces near the edges of the frame. Default (left out):\n * `{ leftPct: 10, rightPct: 10, topPct: 0, bottomPct: 0 }`, so people at the\n * left and right edges are not masked. `null`, or all four margins 0, means\n * no margins.\n */\n zone?: CastSwapFaceMaskingZone | null\n /** The blur's strength, 0.1–5 % of the frame's width (`fill: \"blur\"`). Default 2. */\n blurStrengthPct?: number\n /** How sure the face finder must be, 0.05–0.95: lower finds more faces. Default 0.5. */\n threshold?: number\n}\n\n/** The body of `POST /v1/cast-swap` and `POST /v1/cast-swap/estimate`. */\nexport interface CastSwapParams {\n /** The source clip, 4–30 seconds. */\n videoUrl: string\n /**\n * Who and what to swap: at most 6 person roles, 1 location role, 3 pet roles\n * and 3 product roles, with at most 30 images in all. A role with no images\n * is skipped (that subject or place stays as in the video). At least one role\n * with images, or a `prePrompt` or `postPrompt`, is required.\n */\n roles: CastSwapRole[]\n /**\n * Fixed references (at most 5): things the video should show that no role\n * replaces, each with the line that names it. Their images count toward the\n * 30. A reference alone is enough for a run (no role needs images).\n */\n references?: CastSwapReference[]\n /**\n * Your own words placed at the start of the prompt, before the role\n * instructions; at most 4,000 characters. Mention a role as `{role:N}`, as in\n * `postPrompt`; the same 400 refusals apply (an N outside 1..`roles.length`,\n * a raw `{image:` / `{video:` / `{audio:` token).\n */\n prePrompt?: string\n /**\n * Extra instructions in your own words, at most 4,000 characters. Mention a\n * role as `{role:N}`, where N is its 1-based position in `roles`\n * (`{role:1}` is `roles[0]`; empty roles count).\n *\n * The route answers 400, before charging, for an N outside 1..`roles.length`,\n * for a raw `{image:` / `{video:` / `{audio:` token in the pre or post prompt\n * or a role's `name`, for a clip outside 4–30 s, and for a preview past the clip's\n * end (`previewStart + 4` > its length).\n */\n postPrompt?: string\n /** `true` runs on 4 seconds of the clip, starting at `previewStart`: a cheap check of the roles and choices. */\n preview?: boolean\n /** Where the preview starts, in seconds. Default 0. */\n previewStart?: number\n /**\n * The video engine (see {@link CastSwapEngine}). Default `\"seedance-2-5\"`.\n * The job records it in `input_data.engine`, and a completed job's output\n * in its `engine`. A Cast Swap engine older than Kling 4 support ignores\n * the field and runs Seedance 2.5: a Kling 4 quote names `engine:\n * \"kling-4\"`, so check the estimate's `engine` before running.\n */\n engine?: CastSwapEngine\n /**\n * Kling 4 only (Seedance 2.5 ignores it): which end of the clip is cut to\n * whole seconds (see {@link CastSwapKlingLength}). Default `\"cut-end\"`.\n */\n klingLength?: CastSwapKlingLength\n /**\n * The output resolution. Seedance 2.5: `\"480p\"` (default), `\"720p\"` or\n * `\"1080p\"`. Kling 4: `\"720p\"` (default) or `\"1080p\"`; `\"480p\"` answers 400.\n */\n resolution?: \"480p\" | \"720p\" | \"1080p\"\n /**\n * Render as a draft (Seedance 2.5 only). Defaults to `true` at 480p and\n * `false` above it; `true` with `\"720p\"` or `\"1080p\"` answers 400 (draft is\n * 480p only). Kling 4 has no draft: `true` answers 400 `draft_unsupported`.\n */\n draft?: boolean\n /**\n * Turn each person, pet and product role's photos into one clean reference\n * picture before the video (extra credits, shown in `breakdown.references`;\n * about a minute longer; falls back to your own photos if it fails).\n * Default `true`.\n */\n prepareReferences?: boolean\n /** Mask the faces in the source clip (see {@link CastSwapFaceMasking}). Default: off. */\n faceMasking?: CastSwapFaceMasking\n /**\n * `\"draft\"` means 480p draft.\n * @deprecated use `resolution` + `draft`.\n */\n quality?: \"draft\"\n /** A generation session to file the result in. */\n sessionId?: string\n /** The client app that sent the run (stored on the job as `input_data.origin`). */\n origin?: string\n}\n\n/** What a run would cost, in credits. */\nexport interface CastSwapEstimate {\n /**\n * The engine the run would use. Sent by an engine that knows Kling 4 on a\n * Kling 4 quote (`\"kling-4\"`); absent = Seedance 2.5.\n */\n engine?: CastSwapEngine\n /**\n * What is held when the run starts: `breakdown.castSwap +\n * breakdown.seedance + breakdown.references` (on Kling 4,\n * `breakdown.kling` in place of `breakdown.seedance`), plus\n * `breakdown.masking` with face masking `\"always\"`. The run is charged what it actually costs, which\n * is expected to be `expectedCredits`.\n */\n totalCredits: number\n /**\n * The charge the run is expected to come to: `breakdown.castSwap +\n * breakdown.seedanceExpected + breakdown.references`, plus\n * `breakdown.masking` with face masking `\"always\"` (the hold, with the video\n * model's expected charge in place of its hold). Never above `totalCredits`.\n * With `\"auto\"` the masking is in neither total (see `maskingCredits`).\n * `totalCredits` is what is held when the run starts; the run is charged\n * what it actually costs.\n *\n * Absent from an older Cast Swap engine: read `totalCredits` then. On\n * Kling 4 it equals `totalCredits` (Kling renders exactly the length asked\n * for, so the hold is what it settles at).\n */\n expectedCredits?: number\n /**\n * The parts of the quote:\n * - `castSwap`: the run itself;\n * - `seedance`: what the video model it calls holds (the video's length is\n * left to the model, so it is held at the model's longest output);\n * - `seedanceExpected`: what the video model is expected to be charged (the\n * clip in and about as much out), never above `seedance`. It takes\n * `seedance`'s place in `expectedCredits`; it is not added to it. Absent\n * from an older Cast Swap engine, like `expectedCredits`;\n * - `kling`: Kling 4 only, in place of `seedance` and `seedanceExpected`:\n * what the Kling take costs (held and charged alike);\n * - `references`: preparing the reference pictures (0 when\n * `prepareReferences` is off or there is nothing to prepare);\n * - `masking`: masking the faces, present only when `faceMasking` is on.\n * With `\"always\"` it is in both totals; with `\"auto\"` it equals\n * `maskingCredits` and is in neither.\n *\n * So the parts sum to more than either total: `seedance` and\n * `seedanceExpected` are alternatives, and `\"auto\"` masking is outside both.\n */\n breakdown: { castSwap: number; seedance?: number; references: number; seedanceExpected?: number; kling?: number; masking?: number }\n /**\n * Face masking `\"auto\"` only: the extra credits charged if the source clip\n * is refused and the run is retried with the faces masked. Not in\n * `totalCredits` or `expectedCredits`; `run()` needs credits for both the\n * hold and this (else 402).\n */\n maskingCredits?: number\n /**\n * `true` when the video is made at Auto length (every Seedance 2.5 run): the\n * final charge is expected to come out lower than `totalCredits`, at about\n * `expectedCredits`. `false` on Kling 4.\n */\n settlesLower: boolean\n}\n\n/** Per-call options for {@link CastSwapResource.run}. */\nexport interface CastSwapRunOptions {\n /**\n * Sent as the `Idempotency-Key` header, unchanged (the SDK checks nothing;\n * an empty string sends no header, like no key).\n * The server takes 8–200 characters and answers 400 for any other length.\n * Reuse the same value when retrying the same run: within about 10 s the\n * repeat answers 200 `{ jobId, deduped: true }`, later 202 with the first\n * run's `jobId`. Nothing new is started or charged.\n */\n idempotencyKey?: string\n}\n\n/**\n * What a Full Quality version would be: `\"same-take\"` re-renders a Seedance\n * 2.5 draft's own take at 1080p; `\"new-take\"` makes a new Kling 4 take at\n * 1080p from a 720p run's inputs (it will not match the 720p run frame for\n * frame). The 720p run or draft stays either way.\n */\nexport type CastSwapFullQualityKind = \"same-take\" | \"new-take\"\n\n/**\n * What a Full Quality version of a run would cost\n * (`POST /v1/cast-swap/:jobId/full-quality/estimate`): a Seedance 2.5 draft,\n * or a completed Kling 4 run at 720p.\n */\nexport interface CastSwapFullQualityEstimate {\n /**\n * Which Full Quality this is ({@link CastSwapFullQualityKind}), so it can\n * be worded right. Absent from an older Cast Swap engine: `\"same-take\"`.\n */\n kind?: CastSwapFullQualityKind\n /** The quote in credits: `breakdown.castSwap` + `breakdown.seedanceHd` (same take) or `breakdown.kling` (new take). */\n totalCredits: number\n /**\n * `castSwap`: the Full Quality job's own steps; `seedanceHd` (a same take):\n * the 1080p render of the draft's take (0 when an earlier try of this draft\n * already finished that render, which is then reused); `kling` (a new take,\n * in place of `seedanceHd`): the new Kling 4 take at 1080p.\n */\n breakdown: { castSwap: number; seedanceHd?: number; kling?: number }\n /** ISO time until which the run can be finished at Full Quality (7 days after it completed). */\n availableUntil: string\n}\n\n/** Options for {@link CastSwapResource.fullQuality}. */\nexport interface CastSwapFullQualityParams {\n /** A generation session to file the Full Quality result in. */\n sessionId?: string\n}\n\n/** The Full Quality job (`POST /v1/cast-swap/:jobId/full-quality`). */\nexport interface CastSwapFullQualityResult {\n jobId: string\n /** `true` when this run's Full Quality was already running or done: that job is answered, nothing new is started or charged. */\n existing?: boolean\n}\n\n/**\n * The `code` of a Full Quality refusal (`NodaroError.code`), all before\n * anything is charged:\n * - `unauthorized` (401): no authenticated user;\n * - `not_found` (404): not your job, or not a Cast Swap run; also a\n * `sessionId` you may not use;\n * - `not_completed` (409): the run has not finished;\n * - `not_a_draft` (409): the run is neither a Seedance 2.5 draft nor a\n * Kling 4 run at 720p (a Kling 4 run at 1080p is already Full Quality);\n * - `draft_expired` (410): more than 7 days after the draft completed, or\n * the draft can no longer be finished at Full Quality;\n * - `full_quality_pending` (409, `details.retryable: true`): not final — an\n * earlier try's 1080p render of this draft is being reviewed, or has not\n * started yet; try again later (once the review ends). The only refusal\n * that ends on its own;\n * - `full_quality_unavailable` (409): final — nothing kept to re-render, no\n * readable completion time, or the 1080p finalize refuses this draft; on\n * the start only, also \"failed too many times\" (every try failed) or no\n * try can be started for this run;\n * - `insufficient_credits` (402), or in a workspace `budget_exceeded`,\n * `member_cap_exceeded` or `user_allowance_exceeded`;\n * - `job_blocked` (422): the request was blocked by the job policy;\n * - `job_read_failed` (503, retryable), `estimate_unavailable` (503, try\n * again shortly), `host_too_old` (503).\n *\n * Open-ended: a code not listed here may still arrive.\n */\nexport type CastSwapFullQualityRefusalCode =\n | \"unauthorized\"\n | \"not_found\"\n | \"not_completed\"\n | \"not_a_draft\"\n | \"draft_expired\"\n | \"full_quality_pending\"\n | \"full_quality_unavailable\"\n | \"insufficient_credits\"\n | \"budget_exceeded\"\n | \"member_cap_exceeded\"\n | \"user_allowance_exceeded\"\n | \"job_blocked\"\n | \"job_read_failed\"\n | \"estimate_unavailable\"\n | \"host_too_old\"\n | (string & {})\n\nexport class CastSwapResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * Start a Cast Swap run (`POST /v1/cast-swap`, 202 `{ jobId }`). Returns the\n * job to follow. With `options.idempotencyKey` (sent as the\n * `Idempotency-Key` header; 8–200 characters, else 400) a repeat with the\n * same key within about 10 s answers 200 `{ jobId, deduped: true }`, a later\n * one 202 with the first run's `jobId`; nothing new starts.\n */\n run(params: CastSwapParams, options?: CastSwapRunOptions): Promise<{ jobId: string; deduped?: boolean }> {\n return this.client.request(\"POST\", \"/v1/cast-swap\", {\n body: params,\n ...(options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}),\n })\n }\n\n /** Quote a run before starting it (`POST /v1/cast-swap/estimate`); spends nothing. */\n estimate(params: CastSwapParams): Promise<CastSwapEstimate> {\n return this.client.request(\"POST\", \"/v1/cast-swap/estimate\", { body: params })\n }\n\n /**\n * Quote finishing a run at Full Quality (`POST\n * /v1/cast-swap/:jobId/full-quality/estimate`); spends nothing. `jobId` is a\n * finished Seedance 2.5 DRAFT run, or a finished Kling 4 run at 720p (a Cast\n * Swap or trend run); the quote's `kind` says whether it is the same take\n * re-rendered or a new take. The quote\n * does not look for a Full Quality already running: when one is,\n * {@link fullQuality} answers it (`existing: true`) and charges nothing.\n * Refusals: {@link CastSwapFullQualityRefusalCode}.\n */\n fullQualityEstimate(jobId: string): Promise<CastSwapFullQualityEstimate> {\n return this.client.request(\"POST\", `/v1/cast-swap/${encodeURIComponent(jobId)}/full-quality/estimate`, { body: {} })\n }\n\n /**\n * Finish a run at Full Quality (`POST /v1/cast-swap/:jobId/full-quality`):\n * at 1080p, with the clip's audio, as a new job linked to the run — a\n * Seedance 2.5 draft's own take re-rendered, or a new Kling 4 take from a\n * 720p run's inputs (the quote's `kind`). SPENDS CREDITS: quote it first with\n * {@link fullQualityEstimate}. Answers 202 `{ jobId }`, or 200\n * `{ jobId, existing: true }` when this run's Full Quality is already\n * running or done (one per run; a failed one may be asked again). Follow\n * the job with `client.jobs.get(jobId)`. No `Idempotency-Key` is taken: the\n * server keys the job by the draft. The new job keeps the draft's origin.\n * Refusals: {@link CastSwapFullQualityRefusalCode}.\n */\n fullQuality(jobId: string, params: CastSwapFullQualityParams = {}): Promise<CastSwapFullQualityResult> {\n const body = params.sessionId !== undefined ? { sessionId: params.sessionId } : {}\n return this.client.request(\"POST\", `/v1/cast-swap/${encodeURIComponent(jobId)}/full-quality`, { body })\n }\n}\n"]}
|
|
@@ -212,5 +212,5 @@ var NodesResource = class {
|
|
|
212
212
|
};
|
|
213
213
|
|
|
214
214
|
exports.NodesResource = NodesResource;
|
|
215
|
-
//# sourceMappingURL=chunk-
|
|
216
|
-
//# sourceMappingURL=chunk-
|
|
215
|
+
//# sourceMappingURL=chunk-5IXZLQNT.cjs.map
|
|
216
|
+
//# sourceMappingURL=chunk-5IXZLQNT.cjs.map
|