@nodaro/sdk 2.54.0 → 2.56.0

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.
Files changed (237) hide show
  1. package/dist/{chunk-P6AM42UB.cjs → chunk-2KX5ZE6M.cjs} +2 -2
  2. package/dist/chunk-2KX5ZE6M.cjs.map +1 -0
  3. package/dist/{chunk-JOVJAKBT.cjs → chunk-2LOTDOUV.cjs} +6 -6
  4. package/dist/chunk-2LOTDOUV.cjs.map +1 -0
  5. package/dist/{chunk-AN36M6HO.js → chunk-37LL6BPM.js} +5 -5
  6. package/dist/chunk-37LL6BPM.js.map +1 -0
  7. package/dist/{chunk-22P4X3N5.cjs → chunk-3CVWTUK5.cjs} +26 -8
  8. package/dist/chunk-3CVWTUK5.cjs.map +1 -0
  9. package/dist/{chunk-7CUWAVWB.js → chunk-4FSWJTRS.js} +3 -3
  10. package/dist/{chunk-7CUWAVWB.js.map → chunk-4FSWJTRS.js.map} +1 -1
  11. package/dist/{chunk-DV6GTEX5.js → chunk-4SZUSZXK.js} +2 -2
  12. package/dist/{chunk-DV6GTEX5.js.map → chunk-4SZUSZXK.js.map} +1 -1
  13. package/dist/{chunk-L6T44A3G.js → chunk-4VP2GFIZ.js} +20 -5
  14. package/dist/chunk-4VP2GFIZ.js.map +1 -0
  15. package/dist/{chunk-NKH2UFSD.cjs → chunk-4WOT5OAY.cjs} +5 -5
  16. package/dist/{chunk-NKH2UFSD.cjs.map → chunk-4WOT5OAY.cjs.map} +1 -1
  17. package/dist/{chunk-W5RMFTPA.cjs → chunk-57GANTLV.cjs} +2 -2
  18. package/dist/chunk-57GANTLV.cjs.map +1 -0
  19. package/dist/{chunk-HK5FSTKG.cjs → chunk-A3XA6UNM.cjs} +2 -2
  20. package/dist/chunk-A3XA6UNM.cjs.map +1 -0
  21. package/dist/{chunk-AUKHMT53.js → chunk-BGOZRZHY.js} +3 -3
  22. package/dist/{chunk-AUKHMT53.js.map → chunk-BGOZRZHY.js.map} +1 -1
  23. package/dist/{chunk-PBFODIFT.js → chunk-CBDUHYTR.js} +2 -2
  24. package/dist/chunk-CBDUHYTR.js.map +1 -0
  25. package/dist/{chunk-OSMKIRKI.cjs → chunk-DFT4LD2H.cjs} +4 -4
  26. package/dist/{chunk-OSMKIRKI.cjs.map → chunk-DFT4LD2H.cjs.map} +1 -1
  27. package/dist/{chunk-RBX3QTKB.js → chunk-E6X6BGRO.js} +3 -3
  28. package/dist/{chunk-RBX3QTKB.js.map → chunk-E6X6BGRO.js.map} +1 -1
  29. package/dist/{chunk-47ULSVO2.js → chunk-FTCAT2LK.js} +3 -3
  30. package/dist/chunk-FTCAT2LK.js.map +1 -0
  31. package/dist/{chunk-QGVOAPUI.js → chunk-GIQDM2MH.js} +2 -2
  32. package/dist/chunk-GIQDM2MH.js.map +1 -0
  33. package/dist/{chunk-65VIIZEF.cjs → chunk-GJH73EGF.cjs} +2 -2
  34. package/dist/{chunk-65VIIZEF.cjs.map → chunk-GJH73EGF.cjs.map} +1 -1
  35. package/dist/{chunk-HAVIVOLO.js → chunk-HFYNZ4SO.js} +2 -2
  36. package/dist/chunk-HFYNZ4SO.js.map +1 -0
  37. package/dist/{chunk-THLTFB6I.js → chunk-HJLSOT5C.js} +2 -2
  38. package/dist/chunk-HJLSOT5C.js.map +1 -0
  39. package/dist/{chunk-D2ILCSOQ.js → chunk-HK6XP5EN.js} +3 -3
  40. package/dist/{chunk-D2ILCSOQ.js.map → chunk-HK6XP5EN.js.map} +1 -1
  41. package/dist/{chunk-HWSYNUSC.js → chunk-HUYI5G3D.js} +2 -2
  42. package/dist/chunk-HUYI5G3D.js.map +1 -0
  43. package/dist/{chunk-JVDUGVLX.cjs → chunk-MIXRL6I2.cjs} +2 -2
  44. package/dist/chunk-MIXRL6I2.cjs.map +1 -0
  45. package/dist/{chunk-SF2CMKWA.cjs → chunk-OMJ45CW4.cjs} +9 -9
  46. package/dist/{chunk-SF2CMKWA.cjs.map → chunk-OMJ45CW4.cjs.map} +1 -1
  47. package/dist/{chunk-DAONQZPI.cjs → chunk-PDX742CC.cjs} +2 -2
  48. package/dist/chunk-PDX742CC.cjs.map +1 -0
  49. package/dist/{chunk-EZXFDCII.js → chunk-PQ6G5EM7.js} +15 -15
  50. package/dist/{chunk-EZXFDCII.js.map → chunk-PQ6G5EM7.js.map} +1 -1
  51. package/dist/{chunk-FRQJNGJS.cjs → chunk-Q4MFQC2J.cjs} +5 -5
  52. package/dist/{chunk-FRQJNGJS.cjs.map → chunk-Q4MFQC2J.cjs.map} +1 -1
  53. package/dist/{chunk-VHTDOACT.cjs → chunk-Q7W4QNE5.cjs} +22 -22
  54. package/dist/chunk-Q7W4QNE5.cjs.map +1 -0
  55. package/dist/{chunk-XZRBRXGJ.cjs → chunk-RIMDKJSQ.cjs} +28 -28
  56. package/dist/{chunk-XZRBRXGJ.cjs.map → chunk-RIMDKJSQ.cjs.map} +1 -1
  57. package/dist/{chunk-BQAW4RX2.js → chunk-VEQLP5TA.js} +25 -7
  58. package/dist/chunk-VEQLP5TA.js.map +1 -0
  59. package/dist/{chunk-I3MEBVLP.js → chunk-XZPSJG2X.js} +2 -2
  60. package/dist/chunk-XZPSJG2X.js.map +1 -0
  61. package/dist/{chunk-O74MSMFH.cjs → chunk-XZQ6Z4T6.cjs} +2 -2
  62. package/dist/chunk-XZQ6Z4T6.cjs.map +1 -0
  63. package/dist/{chunk-FM2KM5TY.cjs → chunk-Z4B4SZ33.cjs} +21 -4
  64. package/dist/chunk-Z4B4SZ33.cjs.map +1 -0
  65. package/dist/{developer-apps-zSQXWmEG.d.ts → developer-apps-CMUzKpE9.d.cts} +2 -0
  66. package/dist/{developer-apps-zSQXWmEG.d.cts → developer-apps-CMUzKpE9.d.ts} +2 -0
  67. package/dist/entries/apps.d.cts +1 -1
  68. package/dist/entries/apps.d.ts +1 -1
  69. package/dist/entries/audio.d.cts +1 -1
  70. package/dist/entries/audio.d.ts +1 -1
  71. package/dist/entries/billing.d.cts +1 -1
  72. package/dist/entries/billing.d.ts +1 -1
  73. package/dist/entries/cast-swap.cjs +2 -2
  74. package/dist/entries/cast-swap.d.cts +13 -1
  75. package/dist/entries/cast-swap.d.ts +13 -1
  76. package/dist/entries/cast-swap.js +1 -1
  77. package/dist/entries/catalogs.d.cts +1 -1
  78. package/dist/entries/catalogs.d.ts +1 -1
  79. package/dist/entries/characters.cjs +10 -10
  80. package/dist/entries/characters.d.cts +11 -3
  81. package/dist/entries/characters.d.ts +11 -3
  82. package/dist/entries/characters.js +1 -1
  83. package/dist/entries/client.cjs +21 -21
  84. package/dist/entries/client.d.cts +5 -5
  85. package/dist/entries/client.d.ts +5 -5
  86. package/dist/entries/client.js +16 -16
  87. package/dist/entries/collections.d.cts +1 -1
  88. package/dist/entries/collections.d.ts +1 -1
  89. package/dist/entries/community.d.cts +1 -1
  90. package/dist/entries/community.d.ts +1 -1
  91. package/dist/entries/competitors.d.cts +1 -1
  92. package/dist/entries/competitors.d.ts +1 -1
  93. package/dist/entries/copilot.cjs +4 -4
  94. package/dist/entries/copilot.d.cts +1 -1
  95. package/dist/entries/copilot.d.ts +1 -1
  96. package/dist/entries/copilot.js +3 -3
  97. package/dist/entries/core.cjs +6 -6
  98. package/dist/entries/core.d.cts +1 -1
  99. package/dist/entries/core.d.ts +1 -1
  100. package/dist/entries/core.js +2 -2
  101. package/dist/entries/creatures.cjs +6 -6
  102. package/dist/entries/creatures.d.cts +11 -3
  103. package/dist/entries/creatures.d.ts +11 -3
  104. package/dist/entries/creatures.js +1 -1
  105. package/dist/entries/credits.d.cts +1 -1
  106. package/dist/entries/credits.d.ts +1 -1
  107. package/dist/entries/developer-apps.d.cts +1 -1
  108. package/dist/entries/developer-apps.d.ts +1 -1
  109. package/dist/entries/edit.cjs +7 -7
  110. package/dist/entries/edit.d.cts +1 -1
  111. package/dist/entries/edit.d.ts +1 -1
  112. package/dist/entries/edit.js +2 -2
  113. package/dist/entries/errors.cjs +32 -24
  114. package/dist/entries/errors.d.cts +28 -4
  115. package/dist/entries/errors.d.ts +28 -4
  116. package/dist/entries/errors.js +1 -1
  117. package/dist/entries/executions.d.cts +1 -1
  118. package/dist/entries/executions.d.ts +1 -1
  119. package/dist/entries/gallery.d.cts +1 -1
  120. package/dist/entries/gallery.d.ts +1 -1
  121. package/dist/entries/jobs.cjs +2 -2
  122. package/dist/entries/jobs.d.cts +7 -4
  123. package/dist/entries/jobs.d.ts +7 -4
  124. package/dist/entries/jobs.js +1 -1
  125. package/dist/entries/keepers.d.cts +1 -1
  126. package/dist/entries/keepers.d.ts +1 -1
  127. package/dist/entries/library.d.cts +1 -1
  128. package/dist/entries/library.d.ts +1 -1
  129. package/dist/entries/llm.d.cts +1 -1
  130. package/dist/entries/llm.d.ts +1 -1
  131. package/dist/entries/locations.cjs +5 -5
  132. package/dist/entries/locations.d.cts +11 -3
  133. package/dist/entries/locations.d.ts +11 -3
  134. package/dist/entries/locations.js +1 -1
  135. package/dist/entries/media.cjs +4 -4
  136. package/dist/entries/media.d.cts +34 -5
  137. package/dist/entries/media.d.ts +34 -5
  138. package/dist/entries/media.js +3 -3
  139. package/dist/entries/models.d.cts +1 -1
  140. package/dist/entries/models.d.ts +1 -1
  141. package/dist/entries/moodboards.d.cts +1 -1
  142. package/dist/entries/moodboards.d.ts +1 -1
  143. package/dist/entries/node-presets.d.cts +1 -1
  144. package/dist/entries/node-presets.d.ts +1 -1
  145. package/dist/entries/nodes.cjs +3 -3
  146. package/dist/entries/nodes.d.cts +3 -3
  147. package/dist/entries/nodes.d.ts +3 -3
  148. package/dist/entries/nodes.js +2 -2
  149. package/dist/entries/oauth.d.cts +1 -1
  150. package/dist/entries/oauth.d.ts +1 -1
  151. package/dist/entries/objects.cjs +6 -6
  152. package/dist/entries/objects.d.cts +11 -3
  153. package/dist/entries/objects.d.ts +11 -3
  154. package/dist/entries/objects.js +1 -1
  155. package/dist/entries/organizations.d.cts +1 -1
  156. package/dist/entries/organizations.d.ts +1 -1
  157. package/dist/entries/palettes.d.cts +1 -1
  158. package/dist/entries/palettes.d.ts +1 -1
  159. package/dist/entries/picker-catalogs.d.cts +1 -1
  160. package/dist/entries/picker-catalogs.d.ts +1 -1
  161. package/dist/entries/pipelines.d.cts +1 -1
  162. package/dist/entries/pipelines.d.ts +1 -1
  163. package/dist/entries/projects.d.cts +1 -1
  164. package/dist/entries/projects.d.ts +1 -1
  165. package/dist/entries/prompt-helper.d.cts +1 -1
  166. package/dist/entries/prompt-helper.d.ts +1 -1
  167. package/dist/entries/recast.d.cts +1 -1
  168. package/dist/entries/recast.d.ts +1 -1
  169. package/dist/entries/reduce.d.cts +1 -1
  170. package/dist/entries/reduce.d.ts +1 -1
  171. package/dist/entries/saved-posts.d.cts +1 -1
  172. package/dist/entries/saved-posts.d.ts +1 -1
  173. package/dist/entries/scene3d.d.cts +4 -4
  174. package/dist/entries/scene3d.d.ts +4 -4
  175. package/dist/entries/sessions.d.cts +1 -1
  176. package/dist/entries/sessions.d.ts +1 -1
  177. package/dist/entries/shots.d.cts +1 -1
  178. package/dist/entries/shots.d.ts +1 -1
  179. package/dist/entries/studio.cjs +5 -5
  180. package/dist/entries/studio.d.cts +3 -3
  181. package/dist/entries/studio.d.ts +3 -3
  182. package/dist/entries/studio.js +2 -2
  183. package/dist/entries/templates.d.cts +1 -1
  184. package/dist/entries/templates.d.ts +1 -1
  185. package/dist/entries/trash.d.cts +1 -1
  186. package/dist/entries/trash.d.ts +1 -1
  187. package/dist/entries/trends.cjs +2 -2
  188. package/dist/entries/trends.d.cts +37 -17
  189. package/dist/entries/trends.d.ts +37 -17
  190. package/dist/entries/trends.js +1 -1
  191. package/dist/entries/tutorials.d.cts +1 -1
  192. package/dist/entries/tutorials.d.ts +1 -1
  193. package/dist/entries/uploads.d.cts +1 -1
  194. package/dist/entries/uploads.d.ts +1 -1
  195. package/dist/entries/user-settings.d.cts +1 -1
  196. package/dist/entries/user-settings.d.ts +1 -1
  197. package/dist/entries/video-pro.d.cts +1 -1
  198. package/dist/entries/video-pro.d.ts +1 -1
  199. package/dist/entries/voices.d.cts +1 -1
  200. package/dist/entries/voices.d.ts +1 -1
  201. package/dist/entries/webhooks.d.cts +3 -3
  202. package/dist/entries/webhooks.d.ts +3 -3
  203. package/dist/entries/workflows.d.cts +1 -1
  204. package/dist/entries/workflows.d.ts +1 -1
  205. package/dist/entries/workspaces.d.cts +1 -1
  206. package/dist/entries/workspaces.d.ts +1 -1
  207. package/dist/index.cjs +90 -82
  208. package/dist/index.d.cts +6 -6
  209. package/dist/index.d.ts +6 -6
  210. package/dist/index.js +16 -16
  211. package/dist/{nodes-DuXF6XVv.d.ts → nodes-1u7MPrD5.d.ts} +11 -1
  212. package/dist/{nodes-bY51Z1Os.d.cts → nodes-BZavad1T.d.cts} +11 -1
  213. package/dist/{studio-productions-r2-VZsKj.d.cts → studio-productions-CMiS827q.d.cts} +1 -1
  214. package/dist/{studio-productions-DtTE8mdl.d.ts → studio-productions-DkI51Z9M.d.ts} +1 -1
  215. package/dist/{webhooks-BJawEIz8.d.ts → webhooks-C2DZTK4n.d.ts} +1 -1
  216. package/dist/{webhooks-Dn_DtNTD.d.cts → webhooks-tg3VN39V.d.cts} +1 -1
  217. package/package.json +3 -3
  218. package/dist/chunk-22P4X3N5.cjs.map +0 -1
  219. package/dist/chunk-47ULSVO2.js.map +0 -1
  220. package/dist/chunk-AN36M6HO.js.map +0 -1
  221. package/dist/chunk-BQAW4RX2.js.map +0 -1
  222. package/dist/chunk-DAONQZPI.cjs.map +0 -1
  223. package/dist/chunk-FM2KM5TY.cjs.map +0 -1
  224. package/dist/chunk-HAVIVOLO.js.map +0 -1
  225. package/dist/chunk-HK5FSTKG.cjs.map +0 -1
  226. package/dist/chunk-HWSYNUSC.js.map +0 -1
  227. package/dist/chunk-I3MEBVLP.js.map +0 -1
  228. package/dist/chunk-JOVJAKBT.cjs.map +0 -1
  229. package/dist/chunk-JVDUGVLX.cjs.map +0 -1
  230. package/dist/chunk-L6T44A3G.js.map +0 -1
  231. package/dist/chunk-O74MSMFH.cjs.map +0 -1
  232. package/dist/chunk-P6AM42UB.cjs.map +0 -1
  233. package/dist/chunk-PBFODIFT.js.map +0 -1
  234. package/dist/chunk-QGVOAPUI.js.map +0 -1
  235. package/dist/chunk-THLTFB6I.js.map +0 -1
  236. package/dist/chunk-VHTDOACT.cjs.map +0 -1
  237. package/dist/chunk-W5RMFTPA.cjs.map +0 -1
@@ -118,5 +118,5 @@ var JobsResource = class {
118
118
  };
119
119
 
120
120
  exports.JobsResource = JobsResource;
121
- //# sourceMappingURL=chunk-P6AM42UB.cjs.map
122
- //# sourceMappingURL=chunk-P6AM42UB.cjs.map
121
+ //# sourceMappingURL=chunk-2KX5ZE6M.cjs.map
122
+ //# sourceMappingURL=chunk-2KX5ZE6M.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resources/jobs.ts"],"names":[],"mappings":"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","file":"chunk-2KX5ZE6M.cjs","sourcesContent":["import type { NodaroClientCore } from \"../core.js\"\n\n/**\n * A job's lifecycle status. Hand-copied mirror of `JOB_STATUSES` in the\n * platform's `backend/src/lib/job-status.ts` — nothing links the two at build\n * time, so a new member has to be added here in the same change.\n *\n * `pending_review` is IN-FLIGHT, NOT TERMINAL: a job policy registered by the\n * deployment held the output for a human reviewer. Treat it like `processing`\n * in a `switch` — keep waiting, do not re-run — and it resolves to `completed`\n * (approved), `failed` (rejected, carrying an `error_hint` with\n * `kind: \"policy-block\"`) or `cancelled` (a held job is cancellable like any\n * in-flight job). `credit_status` reads `\"reserved\"` for the whole hold. Only\n * appears on deployments that register a job policy; `runAndWait` throws\n * {@link JobHeldError} on the first held tick rather than waiting it out.\n */\nexport type JobStatus =\n | \"pending\"\n | \"queued\"\n | \"processing\"\n | \"pending_review\"\n | \"completed\"\n | \"failed\"\n | \"cancelled\"\n\n/**\n * A PROVIDER's verdict: the model's own safety filter refused the output.\n * `class` distinguishes a deterministic block (copyright/likeness — retrying\n * the same request never helps) from `safety`, whose filter is known to be\n * non-deterministic for some models: `retried` says whether the worker\n * already spent its one extra attempt, and `suggestedProvider` — present\n * only when the catalog declares a fallback for the model that failed — is a\n * real model id the SAME prompt/references can be retried on.\n */\nexport type SafetyBlockHint = {\n kind: \"safety-block\"\n class: \"copyright\" | \"likeness\" | \"safety\"\n retried: boolean\n suggestedProvider?: string\n /**\n * Present when the provider named the input it refused — not the prompt or\n * the output. `\"input_video\"`: the input video itself; sending a different\n * clip is the fix. `\"input_image\"`: one of the input images, refused as\n * possibly copyrighted (with `class: \"copyright\"`); sending a different\n * picture is the fix. Treat an unknown value as absent.\n */\n subject?: \"input_video\" | \"input_image\"\n}\n\n/**\n * A NODARO-side verdict, not a provider's: a job policy registered by the\n * deployment rejected the request (`hookPoint: \"request\"` — nothing ran, and\n * the call itself failed with {@link JobBlockedError}) or the finished output\n * (`hookPoint: \"result\"` — the result was blocked and never published).\n *\n * `reason` is USER-SAFE BY CONTRACT — the platform guarantees the policy's\n * machine text (scores, labels) never reaches it — so show it to the person\n * who made the request as-is. `policyId` identifies the deployment's policy,\n * not a Nodaro product feature. Only occurs on deployments that register a\n * job policy.\n */\nexport type PolicyBlockHint = {\n kind: \"policy-block\"\n policyId: string\n reason: string\n hookPoint: \"request\" | \"result\"\n}\n\n/**\n * The job ran out of credits partway: a step that reserves its own credits\n * (for example, the video a Cast Swap or trend run dispatches) was refused —\n * the balance could not cover it, or a plan or workspace cap was reached. The\n * job failed and its credits were refunded; ask the person to top up (or raise\n * the cap), then run it again.\n *\n * `code` is the refusal's own code, a lowercase snake_case token — for\n * example `insufficient_credits`, `free_video_limit`, `instance_cap_reached`,\n * `budget_exceeded`, `member_cap_exceeded` or `user_allowance_exceeded`. Treat\n * an unknown code like `insufficient_credits`.\n */\nexport type CreditsHint = {\n kind: \"credits\"\n code: string\n}\n\n/**\n * The provider refused the job's input video without naming a content-policy\n * class (its message is about the file, not a policy). Treat it like any\n * other failure — no fallback model is offered — except that the input to\n * change is known: send a different clip.\n */\nexport type InputRefusedHint = {\n kind: \"input-refused\"\n subject: \"input_video\"\n}\n\n/**\n * A job's `jobs.error_hint` (migration 376) — a user-safe, machine-readable\n * failure verdict, so a caller can act on it without parsing `error_message`\n * prose. One column, several verdict sources; `kind` is the discriminant.\n *\n * HAND-COPIED MIRROR of `ErrorHint` in the platform's\n * `backend/src/lib/safety-block.ts` (which names this file in its own\n * comment). Nothing links the two at build time: a new arm there has to be\n * pasted here verbatim in the same change, with a changeset.\n * `packages/client/dist/index.d.ts` is REBUILT, never hand-edited.\n */\nexport type JobErrorHint = SafetyBlockHint | PolicyBlockHint | CreditsHint | InputRefusedHint\n\n/**\n * The billing lifecycle of a job's credit reservation — `usage_logs.status`\n * for the job's `usage_log_id`. `null` when the job has no usage log (e.g. a\n * free run) or hasn't reserved credits yet.\n */\nexport type CreditStatus = \"reserved\" | \"committed\" | \"refunded\"\n\n/** A job owner's rating of their job — set with {@link JobsResource.rate}. */\nexport type JobRating = \"good\" | \"bad\"\n\n/**\n * The `rating` filter of {@link JobsResource.list}: one rating, any rating\n * (`\"rated\"`) or none (`\"unrated\"`).\n */\nexport type JobRatingFilter = JobRating | \"rated\" | \"unrated\"\n\n/**\n * Job record returned to non-admin SDK consumers. Field names are snake_case\n * to match the wire format (per the OpenAPI fix in Phase 1).\n *\n * Sensitive fields stripped server-side for non-admin callers:\n * `provider`, `provider_cost`, `display_cost`, `credits_actual`. USD\n * pricing is admin-only across api/sdk/mcp — non-admin consumers see\n * only the `credits` abstraction. Server-only nested job data (including\n * Recast's private remux base) is stripped for every caller, including admins.\n */\nexport interface Job {\n id: string\n status: JobStatus\n progress: number\n user_id: string\n input_data: unknown\n output_data: unknown\n error_message: string | null\n /** Present on a failed job the worker classified as a provider\n * content-policy block (`kind: \"safety-block\"`) or that a deployment's job\n * policy rejected (`kind: \"policy-block\"`); absent/null otherwise.\n * User-safe by construction — narrow on `kind` before reading the rest. */\n error_hint?: JobErrorHint | null\n /** PR9: the job's credit reservation lifecycle, derived server-side from\n * `usage_logs.status`. `null` when there is no usage log to report. Never\n * changes the meaning of `credits` (the reserved amount) below. */\n credit_status?: CreditStatus | null\n /** The credits the job itself HELD when it was accepted. Never changes — a\n * job can settle below it; read {@link Job.credits_charged} for what it cost. */\n credits: number | null\n /**\n * What the job actually cost once its hold settled, INCLUDING the steps it\n * ran: a job that runs other jobs for you (a Nodaro Motion run's reference\n * preparation, mask and video) is charged for each, and they are not listed\n * on their own. A step adds its charge once it settles. Can be lower than\n * {@link Job.credits_held} (the unused part goes back to the balance). `0`\n * when the run's own hold was refunded, plus any settled steps (a refunded\n * Nodaro Motion run whose reference and mask steps committed reads\n * `credit_status: \"refunded\"` with `credits_charged: 165`). `null` while the\n * job's own hold is still open (even when some steps have settled), when\n * nothing in the run was reserved, or when its steps could not be read for a\n * moment (unknown — never a lower figure). Sum this — not `credits` — to\n * total spend. Returned by `get()` and `list()`.\n */\n credits_charged?: number | null\n /** The run's hold: the job's own `credits` plus its steps' holds (equals `credits` for a job with no steps). */\n credits_held?: number | null\n /** What went back from `credits_held` once every hold in the run settled; `null` until then. */\n credits_released?: number | null\n job_type: string | null\n created_at: string\n started_at: string | null\n completed_at: string | null\n /**\n * Provenance: which kind of caller created the job — one of\n * `\"internal\" | \"mcp\" | \"app\" | \"cli\" | \"sdk\" | \"extension\" | \"web\" | \"api\"`.\n * `source_detail` narrows it (origin host, `extension/<name>` label,\n * `sdk/<version>`, MCP client name, developer-app id). Descriptive only;\n * lets a library view label or filter media by origin.\n */\n source?: string | null\n source_detail?: string | null\n /** The generation session this job was born in (the `sessionId` sent with\n * the generation request), or null. See `client.sessions`. */\n session_id?: string | null\n /**\n * With `list({ withSessions: true })`: every session that holds the job now,\n * each linkable — empty when it is in no session (General). Absent otherwise.\n */\n sessions?: JobSessionRef[]\n /**\n * When the job was deleted, on a job read from the Trash\n * (`client.trash.list()`). A deleted job is left out of every other read, so\n * this is otherwise absent or `null`.\n */\n deleted_at?: string | null\n /**\n * Whether the job's owner favorited it (`client.gallery.toggleFavorite`).\n * Returned by `get()` and `list()`. Absent from an older server, and when\n * the server could not read it for a moment — treat absent as unknown,\n * never as `false`.\n */\n favorited?: boolean\n /**\n * The owner's rating of the job ({@link JobsResource.rate}), or `null` when\n * unrated. Returned by `get()` and `list()`; absent like `favorited`.\n */\n rating?: JobRating | null\n}\n\nexport interface CancelJobResult {\n success: true\n cancelled: number\n /**\n * `true` when the job could not be stopped because its provider call is\n * already running — a metered LLM job's call, an external provider task. It\n * finishes and is charged only what it used.\n */\n inFlight?: boolean\n}\n\n/**\n * The kind of caller that created a job (`Job.source`). Hand-copied mirror of\n * `JOB_SOURCES` in the platform's `backend/src/lib/job-source.ts`.\n */\nexport type JobSource = \"internal\" | \"mcp\" | \"app\" | \"cli\" | \"sdk\" | \"extension\" | \"web\" | \"api\"\n\nexport interface DeleteJobResult {\n success: true\n}\n\n/** A session that holds a job now (`Job.sessions`). */\nexport interface JobSessionRef {\n id: string\n title: string | null\n /** The session's project, or `null` when it has none. */\n projectId: string | null\n}\n\nexport interface ListJobsParams {\n /** One status or several (a job matching any of them). An unknown status is a 400. */\n status?: JobStatus | readonly JobStatus[]\n /** Exact match on `input_data.type` — the route that created the job\n * (`\"llm-structured\"`, `\"video-analysis\"`, …). One value or several (up to 20). */\n type?: string | readonly string[]\n /** Exact match on `input_data.origin` — the client app that sent it. */\n origin?: string\n /** The surface that created the job (`Job.source`): `\"mcp\"` lists what your\n * MCP clients (Claude, ChatGPT, …) made. One value or several. */\n source?: JobSource | readonly JobSource[]\n /** `true`: only jobs you favorited; `false`: only jobs you did not. */\n favorited?: boolean\n /** Your rating: `\"good\"`, `\"bad\"`, any (`\"rated\"`) or none (`\"unrated\"`). */\n rating?: JobRatingFilter\n /** The jobs of one generate-image batch (`count` / `models`) — its `batchId`. */\n batchId?: string\n /**\n * The jobs a session of this project holds now — or, `\"none\"`, a session\n * with no project. Each job once, however many sessions hold it. Not with\n * `inSession: false`.\n */\n projectId?: string | \"none\"\n /**\n * `false`: the jobs no session holds now (General), whatever session they\n * were born in. `true`: the jobs at least one session holds.\n */\n inSession?: boolean\n /** `true`: each job carries `sessions`, the sessions that hold it now. */\n withSessions?: boolean\n /** Page size, 1–100 (server default 50). */\n limit?: number\n /** The previous page's `next`. */\n cursor?: string\n}\n\n/** What {@link JobsResource.rate} answers: the job's rating now. */\nexport interface RateJobResult {\n data: { jobId: string; rating: JobRating | null }\n}\n\n/**\n * {@link JobsResource.fromUpload}'s body: an image edited in the browser,\n * already stored with `client.uploads.upload`, and what it was made from.\n */\nexport interface JobFromUploadParams {\n /** The edited image's `assetId`, as `client.uploads.upload` returned it. */\n assetId: string\n /** The job whose image was edited. */\n sourceJobId?: string\n /** Which of `sourceJobId`'s images was edited, when it made several. Default: its `imageUrl`. */\n sourceImageUrl?: string\n /** The upload that was edited, when the source is not a job. Not with `sourceJobId`. */\n sourceAssetId?: string\n /** The editor's design state, stored with `client.uploads.uploadJson`, so the edit can be reopened. */\n editStateUrl?: string\n /** The generation session to file the job in. */\n sessionId?: string\n}\n\n/** A list filter value as the query string carries it: several are comma-separated. */\nfunction listParam(value: string | readonly string[] | undefined): string | undefined {\n if (value === undefined) return undefined\n return typeof value === \"string\" ? value : value.join(\",\")\n}\n\nexport interface ListJobsPage {\n /**\n * A page may hold fewer than `limit` rows — even none — and still carry a\n * `next`. Page on `next`, never on `data.length`.\n */\n data: Job[]\n /** Pass back as `cursor` for the next page; `null` on the last one. */\n next: string | null\n}\n\n/**\n * Lean job status returned by `GET /v1/jobs/:id/status`. Skips the\n * `input_data` JSONB and cost columns. Its `output_data` still goes\n * through the server-only nested-field redaction. Intended for poll loops that\n * only need progress/output/error, and when the job finished.\n */\nexport interface JobStatusResult {\n id: string\n status: JobStatus\n progress?: number\n output_data?: unknown\n error_message?: string | null\n /** PR9: see {@link Job.error_hint}. */\n error_hint?: JobErrorHint | null\n /** PR9: see {@link Job.credit_status}. */\n credit_status?: CreditStatus | null\n /** When the job finished, on the server's clock; null until it has. */\n completed_at?: string | null\n}\n\nexport class JobsResource {\n constructor(private client: NodaroClientCore) {}\n\n /** Get a single job by ID. */\n get(id: string): Promise<{ data: Job }> {\n return this.client.request(\"GET\", `/v1/jobs/${encodeURIComponent(id)}`)\n }\n\n /**\n * Your jobs, newest first (`GET /v1/jobs`), cursor-paginated. `type` and\n * `origin` filter on the job's own `input_data`, which every job carries —\n * a client app lists its runs with `{ type: \"llm-structured\", origin: \"studio\" }`.\n * `status` and `type` take one value or a list; `favorited` and `rating`\n * filter on your own marks, `source` on the surface that created the job.\n * `projectId` / `inSession` filter on where the job is now (a project's\n * sessions, or General), and `withSessions` says which sessions each job is\n * in — for \"everything in this project\". Every filter combines with AND,\n * and each job carries `favorited` and `rating`. A deleted job is never\n * listed: read the Trash with `client.trash.list()`.\n */\n list(params: ListJobsParams = {}): Promise<ListJobsPage> {\n const { status, type, source, inSession, withSessions, ...rest } = params\n return this.client.request<ListJobsPage>(\"GET\", \"/v1/jobs\", {\n query: {\n ...rest,\n status: listParam(status),\n type: listParam(type),\n source: listParam(source),\n inSession: inSession === undefined ? undefined : String(inSession),\n withSessions: withSessions ? \"true\" : undefined,\n },\n })\n }\n\n /**\n * Record an image edited in the browser as a job (`POST /v1/jobs/from-upload`):\n * a completed, private, free `image-edit` job whose `output_data.imageUrl` is\n * the upload, linked to its source in `input_data` (`sourceJobId` /\n * `sourceAssetId`, `sourceImageUrl`). It then lists, rates, favorites and\n * files into a session like any image job. One upload is one job: the same\n * `assetId` again returns the same job. OAuth apps need the `assets:write`\n * scope. A deployment's job policy can refuse the image ({@link JobBlockedError}).\n */\n fromUpload(params: JobFromUploadParams): Promise<{ data: Job }> {\n return this.client.request<{ data: Job }>(\"POST\", \"/v1/jobs/from-upload\", { body: params })\n }\n\n /**\n * Rate one of your jobs `\"good\"` or `\"bad\"`, or clear the rating with\n * `null` (`PUT /v1/jobs/:id/rating`). Only the job's owner rates it — anyone\n * else's job is a `NotFoundError`. The rating reads back as `Job.rating`\n * and filters {@link list}. OAuth apps need the `assets:write` scope.\n */\n rate(id: string, rating: JobRating | null): Promise<RateJobResult> {\n return this.client.request<RateJobResult>(\"PUT\", `/v1/jobs/${encodeURIComponent(id)}/rating`, {\n body: { rating },\n })\n }\n\n /**\n * Get the lean status of a single job (poll-loop friendly).\n * Hits `GET /v1/jobs/:id/status` — returns only id/status/progress/\n * output_data/error_message, with far less wire/CPU cost than `get()`.\n * Same auth + ownership semantics as {@link get}.\n */\n getStatus(id: string): Promise<{ data: JobStatusResult }> {\n return this.client.request(\n \"GET\",\n `/v1/jobs/${encodeURIComponent(id)}/status`,\n )\n }\n\n /**\n * Cancel a job. Server route is `POST /v1/jobs/:jobId/cancel`.\n * Refunds any reserved credit holds. A running LLM job priced per model\n * (metered) whose call has already started answers\n * `{ success: true, cancelled: 0, inFlight: true }`: it finishes and is\n * charged only what it used. A job stopped before its call starts is\n * refunded, except for what an earlier attempt of the same job already used\n * (a retry after a billed attempt): that is charged.\n */\n cancel(id: string): Promise<CancelJobResult> {\n return this.client.request(\n \"POST\",\n `/v1/jobs/${encodeURIComponent(id)}/cancel`,\n )\n }\n\n /**\n * Delete a job: it goes to the Trash (`client.trash.list()`). It is left out\n * of every read from then on, stays restorable for 30 days ({@link restore},\n * back into the sessions it was in), and is then deleted for good, as a\n * delete always was (a copy saved to the Library stays). `{ forever: true }` deletes a job already in the Trash for good now;\n * on a live job it fails with a 409 `NodaroError` whose `code` is\n * `\"not_in_trash\"`. Only the job's owner (or an admin) may delete it; a job\n * that is still running keeps running — cancel it first when its worker\n * should stop ({@link cancel}). Nothing is refunded. OAuth apps need the\n * `assets:write` scope.\n */\n delete(id: string, options: { forever?: boolean } = {}): Promise<DeleteJobResult> {\n return this.client.request(\n \"DELETE\",\n `/v1/jobs/${encodeURIComponent(id)}`,\n options.forever ? { query: { forever: \"true\" } } : {},\n )\n }\n\n /**\n * Bring a deleted job back from the Trash: it reappears in every read and in\n * every session it was in. A job that is not in the Trash succeeds too.\n * OAuth apps need the `assets:write` scope.\n */\n restore(id: string): Promise<{ success: true }> {\n return this.client.request(\"POST\", `/v1/jobs/${encodeURIComponent(id)}/restore`)\n }\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkFM2KM5TY_cjs = require('./chunk-FM2KM5TY.cjs');
3
+ var chunkZ4B4SZ33_cjs = require('./chunk-Z4B4SZ33.cjs');
4
4
  var organizations = require('@nodaro/shared/organizations');
5
5
 
6
6
  // src/binary-response.ts
@@ -36,7 +36,7 @@ async function readBinaryResponse(response, maxBytes) {
36
36
  }
37
37
  return bytes.buffer;
38
38
  }
39
- var SDK_VERSION = "2.54.0" ;
39
+ var SDK_VERSION = "2.56.0" ;
40
40
  var CLIENT_HEADER = "X-Nodaro-Client";
41
41
  var isBrowser = () => typeof window !== "undefined" && typeof window.document !== "undefined";
42
42
  var NodaroClientCore = class {
@@ -89,7 +89,7 @@ var NodaroClientCore = class {
89
89
  };
90
90
  if (token) headers["Authorization"] = `Bearer ${token}`;
91
91
  const ac = new AbortController();
92
- const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs);
92
+ const timeoutId = setTimeout(() => ac.abort(), options.timeoutMs ?? this.timeoutMs);
93
93
  const abort = () => ac.abort(options.signal?.reason);
94
94
  options.signal?.addEventListener("abort", abort, { once: true });
95
95
  if (options.signal?.aborted) abort();
@@ -107,7 +107,7 @@ var NodaroClientCore = class {
107
107
  errBody = await res.json();
108
108
  } catch {
109
109
  }
110
- chunkFM2KM5TY_cjs.throwFromResponse(res.status, errBody);
110
+ chunkZ4B4SZ33_cjs.throwFromResponse(res.status, errBody);
111
111
  }
112
112
  return await read(res);
113
113
  } finally {
@@ -229,5 +229,5 @@ exports.CallbackAuth = CallbackAuth;
229
229
  exports.NodaroClientCore = NodaroClientCore;
230
230
  exports.StaticTokenAuth = StaticTokenAuth;
231
231
  exports.supabaseAuth = supabaseAuth;
232
- //# sourceMappingURL=chunk-JOVJAKBT.cjs.map
233
- //# sourceMappingURL=chunk-JOVJAKBT.cjs.map
232
+ //# sourceMappingURL=chunk-2LOTDOUV.cjs.map
233
+ //# sourceMappingURL=chunk-2LOTDOUV.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/binary-response.ts","../src/core.ts","../src/auth.ts"],"names":["WORKSPACE_HEADER","throwFromResponse"],"mappings":"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","file":"chunk-2LOTDOUV.cjs","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 /** This request's timeout in ms, in place of the client's `timeoutMs`. */\n timeoutMs?: number\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(), options.timeoutMs ?? 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,4 +1,4 @@
1
- import { throwFromResponse } from './chunk-L6T44A3G.js';
1
+ import { throwFromResponse } from './chunk-4VP2GFIZ.js';
2
2
  import { WORKSPACE_HEADER } from '@nodaro/shared/organizations';
3
3
 
4
4
  // src/binary-response.ts
@@ -34,7 +34,7 @@ async function readBinaryResponse(response, maxBytes) {
34
34
  }
35
35
  return bytes.buffer;
36
36
  }
37
- var SDK_VERSION = "2.54.0" ;
37
+ var SDK_VERSION = "2.56.0" ;
38
38
  var CLIENT_HEADER = "X-Nodaro-Client";
39
39
  var isBrowser = () => typeof window !== "undefined" && typeof window.document !== "undefined";
40
40
  var NodaroClientCore = class {
@@ -87,7 +87,7 @@ var NodaroClientCore = class {
87
87
  };
88
88
  if (token) headers["Authorization"] = `Bearer ${token}`;
89
89
  const ac = new AbortController();
90
- const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs);
90
+ const timeoutId = setTimeout(() => ac.abort(), options.timeoutMs ?? this.timeoutMs);
91
91
  const abort = () => ac.abort(options.signal?.reason);
92
92
  options.signal?.addEventListener("abort", abort, { once: true });
93
93
  if (options.signal?.aborted) abort();
@@ -224,5 +224,5 @@ function supabaseAuth(supabase) {
224
224
  }
225
225
 
226
226
  export { CallbackAuth, NodaroClientCore, StaticTokenAuth, supabaseAuth };
227
- //# sourceMappingURL=chunk-AN36M6HO.js.map
228
- //# sourceMappingURL=chunk-AN36M6HO.js.map
227
+ //# sourceMappingURL=chunk-37LL6BPM.js.map
228
+ //# sourceMappingURL=chunk-37LL6BPM.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/binary-response.ts","../src/core.ts","../src/auth.ts"],"names":[],"mappings":"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","file":"chunk-37LL6BPM.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 /** This request's timeout in ms, in place of the client's `timeoutMs`. */\n timeoutMs?: number\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(), options.timeoutMs ?? 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,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunkFRQJNGJS_cjs = require('./chunk-FRQJNGJS.cjs');
3
+ var chunkQ4MFQC2J_cjs = require('./chunk-Q4MFQC2J.cjs');
4
4
 
5
5
  // src/resources/media.ts
6
6
  var MediaResource = class {
@@ -18,7 +18,9 @@ var MediaResource = class {
18
18
  * `requireAudio: false` to accept a clip that really has no sound. Returns a
19
19
  * `downloadId`; progress streams from
20
20
  * `GET /v1/download-video/progress/:downloadId` (server-sent events) and the
21
- * finished file lands in your library.
21
+ * finished file lands in your library. A section is fetched with ±3 s of
22
+ * padding for the caller to trim; `exactSection: true` cuts it exactly at
23
+ * `sectionStartSec`–`sectionEndSec` instead.
22
24
  */
23
25
  downloadVideo(input) {
24
26
  return this.client.request("POST", "/v1/download-video", { body: input });
@@ -42,7 +44,7 @@ var MediaResource = class {
42
44
  headers: token ? { Authorization: `Bearer ${token}` } : {},
43
45
  signal: opts.signal
44
46
  });
45
- yield* chunkFRQJNGJS_cjs.readSseStream(res, { label: "progress stream" });
47
+ yield* chunkQ4MFQC2J_cjs.readSseStream(res, { label: "progress stream" });
46
48
  }
47
49
  /**
48
50
  * Copy an external media URL into your Nodaro storage (`POST /v1/save-to-storage`)
@@ -315,13 +317,29 @@ var MediaResource = class {
315
317
  * free, the source-preparation sibling of the priced `trimVideo` node.
316
318
  * `trim` is `[startTime, endTime]` in seconds; `deleteSource: true` removes
317
319
  * the source object afterwards when it is yours and nothing else references
318
- * it ("the cut replaces the original"). Answers the stored file.
320
+ * it ("the cut replaces the original"). Answers the stored file. The
321
+ * platform may take up to 300 s on a long file: pass `timeoutMs` to wait
322
+ * longer than the client's default.
319
323
  */
320
- process(input) {
321
- return this.client.request("POST", "/v1/media/process", { body: input });
324
+ process(input, options = {}) {
325
+ return this.client.request("POST", "/v1/media/process", { body: input, ...options });
326
+ }
327
+ /**
328
+ * Throw away a file you just made and no longer need (`POST /v1/media/discard`):
329
+ * a link download replaced before use, a cut superseded by a newer one, a
330
+ * picture imported then cancelled. Not a general delete. The file goes only
331
+ * when it is your own, was made by a link download (`downloadVideo`), a cut
332
+ * (`process`) or a URL import, is under 24 hours old, and no job of yours
333
+ * uses it (as an output or as an input such as a run's `videoUrl`);
334
+ * otherwise it is left untouched and the call fails with 409
335
+ * `not_discardable`. `discarded: false` means there was nothing of yours at
336
+ * that address (already gone, or not yours).
337
+ */
338
+ discard(input) {
339
+ return this.client.request("POST", "/v1/media/discard", { body: input });
322
340
  }
323
341
  };
324
342
 
325
343
  exports.MediaResource = MediaResource;
326
- //# sourceMappingURL=chunk-22P4X3N5.cjs.map
327
- //# sourceMappingURL=chunk-22P4X3N5.cjs.map
344
+ //# sourceMappingURL=chunk-3CVWTUK5.cjs.map
345
+ //# sourceMappingURL=chunk-3CVWTUK5.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/resources/media.ts"],"names":["readSseStream"],"mappings":"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","file":"chunk-3CVWTUK5.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. A section is fetched with ±3 s of\n * padding for the caller to trim; `exactSection: true` cuts it exactly at\n * `sectionStartSec`–`sectionEndSec` instead.\n */\n downloadVideo(input: {\n url: string\n maxHeight?: number\n sectionStartSec?: number\n sectionEndSec?: number\n exactSection?: boolean\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. The\n * platform may take up to 300 s on a long file: pass `timeoutMs` to wait\n * longer than the client's default.\n */\n process(\n input: MediaProcessInput,\n options: { timeoutMs?: number; signal?: AbortSignal } = {},\n ): Promise<{ data: MediaProcessResult }> {\n return this.client.request<{ data: MediaProcessResult }>(\"POST\", \"/v1/media/process\", { body: input, ...options })\n }\n\n /**\n * Throw away a file you just made and no longer need (`POST /v1/media/discard`):\n * a link download replaced before use, a cut superseded by a newer one, a\n * picture imported then cancelled. Not a general delete. The file goes only\n * when it is your own, was made by a link download (`downloadVideo`), a cut\n * (`process`) or a URL import, is under 24 hours old, and no job of yours\n * uses it (as an output or as an input such as a run's `videoUrl`);\n * otherwise it is left untouched and the call fails with 409\n * `not_discardable`. `discarded: false` means there was nothing of yours at\n * that address (already gone, or not yours).\n */\n discard(input: { url: string }): Promise<{ data: MediaDiscardResult }> {\n return this.client.request<{ data: MediaDiscardResult }>(\"POST\", \"/v1/media/discard\", { 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\n/** Result of {@link MediaResource.discard}. */\nexport interface MediaDiscardResult {\n /** True when the file was deleted; false when there was nothing of yours at that address. */\n discarded: 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"]}
@@ -1,4 +1,4 @@
1
- import { throwFromResponse, NodaroError } from './chunk-L6T44A3G.js';
1
+ import { throwFromResponse, NodaroError } from './chunk-4VP2GFIZ.js';
2
2
 
3
3
  // src/sse.ts
4
4
  async function* readSseStream(res, opts = {}) {
@@ -42,5 +42,5 @@ async function* readSseStream(res, opts = {}) {
42
42
  }
43
43
 
44
44
  export { readSseStream };
45
- //# sourceMappingURL=chunk-7CUWAVWB.js.map
46
- //# sourceMappingURL=chunk-7CUWAVWB.js.map
45
+ //# sourceMappingURL=chunk-4FSWJTRS.js.map
46
+ //# sourceMappingURL=chunk-4FSWJTRS.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/sse.ts"],"names":[],"mappings":";;;AAyBA,gBAAuB,aAAA,CACrB,GAAA,EACA,IAAA,GAGI,EAAC,EAC+B;AACpC,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,IAAI,UAAmC,EAAC;AACxC,IAAA,IAAI;AACF,MAAA,OAAA,GAAW,MAAM,IAAI,IAAA,EAAK;AAAA,IAC5B,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,iBAAA,CAAkB,GAAA,CAAI,QAAQ,OAAO,CAAA;AAAA,EACvC;AACA,EAAA,IAAI,CAAC,IAAI,IAAA,EAAM;AACb,IAAA,MAAM,IAAI,YAAY,CAAA,EAAG,IAAA,CAAK,SAAS,cAAc,CAAA,qBAAA,CAAA,EAAyB,cAAA,EAAgB,GAAA,CAAI,MAAM,CAAA;AAAA,EAC1G;AAEA,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,SAAA,EAAU;AAClC,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI;AACF,IAAA,WAAS;AACP,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AACV,MAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,MAAA,IAAI,GAAA;AACJ,MAAA,OAAA,CAAQ,GAAA,GAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,MAAM,CAAA,EAAG;AAC1C,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AACjC,QAAA,MAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA;AAC7B,QAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA,EAAG;AACpC,UAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,EAAG;AAC/B,UAAA,IAAI;AACF,YAAA,MAAM,KAAK,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,CAAA,CAAE,MAAM,CAAA;AAAA,UACvC,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAA,SAAE;AAEA,IAAA,MAAA,CAAO,WAAA,EAAY;AACnB,IAAA,MAAM,GAAA,CAAI,IAAA,CAAK,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EACxC;AACF","file":"chunk-7CUWAVWB.js","sourcesContent":["import { NodaroError, throwFromResponse } from \"./errors.js\"\n\n/**\n * The ONE server-sent-event reader in this package.\n *\n * Every streaming route here writes the same minimal shape — `data: <json>\\n\\n`\n * frames, with the event kind INSIDE the JSON rather than on an `event:` line —\n * so one reader serves them all, and a second hand-rolled parse loop is the\n * thing this file exists to prevent. Deliberately minimal, matching the\n * server's writer rather than the whole SSE grammar: split on blank lines,\n * JSON-parse each `data:` line on its own, skip what does not parse (the next\n * frame re-reports state), and ignore every other line — keepalive comments\n * (`: keepalive`) included.\n *\n * Frames are separated by a bare `\\n\\n` and each `data:` line is parsed on its\n * own, exactly as the server writes them. Both halves are literal: a stream\n * whose frames arrive CRLF-terminated, or whose JSON was split across two\n * `data:` lines, yields NOTHING rather than erroring — so an intermediary that\n * normalises line endings would show up as an empty stream, not a parse\n * failure. The server's writer produces neither shape.\n *\n * No timeout is applied. The caller owns the lifetime: pass the `AbortSignal`\n * to the `fetch` that produced `res`, or just stop iterating — the `finally`\n * cancels the body, which ends the HTTP request.\n */\nexport async function* readSseStream<T>(\n res: Response,\n opts: {\n /** Names the stream in the \"no response body\" error. */\n label?: string\n } = {},\n): AsyncGenerator<T, void, undefined> {\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 if (!res.body) {\n throw new NodaroError(`${opts.label ?? \"event stream\"} has no response body`, \"empty_stream\", res.status)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let sep: number\n while ((sep = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n for (const line of frame.split(\"\\n\")) {\n if (!line.startsWith(\"data:\")) continue\n try {\n yield JSON.parse(line.slice(5).trim()) as T\n } catch {\n // Skip malformed frames — the next tick re-reports full state.\n }\n }\n }\n }\n } finally {\n // Ends the HTTP request when the consumer breaks out of the loop early.\n reader.releaseLock()\n await res.body.cancel().catch(() => {})\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/sse.ts"],"names":[],"mappings":";;;AAyBA,gBAAuB,aAAA,CACrB,GAAA,EACA,IAAA,GAGI,EAAC,EAC+B;AACpC,EAAA,IAAI,CAAC,IAAI,EAAA,EAAI;AACX,IAAA,IAAI,UAAmC,EAAC;AACxC,IAAA,IAAI;AACF,MAAA,OAAA,GAAW,MAAM,IAAI,IAAA,EAAK;AAAA,IAC5B,CAAA,CAAA,MAAQ;AAAA,IAER;AACA,IAAA,iBAAA,CAAkB,GAAA,CAAI,QAAQ,OAAO,CAAA;AAAA,EACvC;AACA,EAAA,IAAI,CAAC,IAAI,IAAA,EAAM;AACb,IAAA,MAAM,IAAI,YAAY,CAAA,EAAG,IAAA,CAAK,SAAS,cAAc,CAAA,qBAAA,CAAA,EAAyB,cAAA,EAAgB,GAAA,CAAI,MAAM,CAAA;AAAA,EAC1G;AAEA,EAAA,MAAM,MAAA,GAAS,GAAA,CAAI,IAAA,CAAK,SAAA,EAAU;AAClC,EAAA,MAAM,OAAA,GAAU,IAAI,WAAA,EAAY;AAChC,EAAA,IAAI,MAAA,GAAS,EAAA;AACb,EAAA,IAAI;AACF,IAAA,WAAS;AACP,MAAA,MAAM,EAAE,IAAA,EAAM,KAAA,EAAM,GAAI,MAAM,OAAO,IAAA,EAAK;AAC1C,MAAA,IAAI,IAAA,EAAM;AACV,MAAA,MAAA,IAAU,QAAQ,MAAA,CAAO,KAAA,EAAO,EAAE,MAAA,EAAQ,MAAM,CAAA;AAChD,MAAA,IAAI,GAAA;AACJ,MAAA,OAAA,CAAQ,GAAA,GAAM,MAAA,CAAO,OAAA,CAAQ,MAAM,MAAM,CAAA,EAAG;AAC1C,QAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA;AACjC,QAAA,MAAA,GAAS,MAAA,CAAO,KAAA,CAAM,GAAA,GAAM,CAAC,CAAA;AAC7B,QAAA,KAAA,MAAW,IAAA,IAAQ,KAAA,CAAM,KAAA,CAAM,IAAI,CAAA,EAAG;AACpC,UAAA,IAAI,CAAC,IAAA,CAAK,UAAA,CAAW,OAAO,CAAA,EAAG;AAC/B,UAAA,IAAI;AACF,YAAA,MAAM,KAAK,KAAA,CAAM,IAAA,CAAK,MAAM,CAAC,CAAA,CAAE,MAAM,CAAA;AAAA,UACvC,CAAA,CAAA,MAAQ;AAAA,UAER;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAA,SAAE;AAEA,IAAA,MAAA,CAAO,WAAA,EAAY;AACnB,IAAA,MAAM,GAAA,CAAI,IAAA,CAAK,MAAA,EAAO,CAAE,MAAM,MAAM;AAAA,IAAC,CAAC,CAAA;AAAA,EACxC;AACF","file":"chunk-4FSWJTRS.js","sourcesContent":["import { NodaroError, throwFromResponse } from \"./errors.js\"\n\n/**\n * The ONE server-sent-event reader in this package.\n *\n * Every streaming route here writes the same minimal shape — `data: <json>\\n\\n`\n * frames, with the event kind INSIDE the JSON rather than on an `event:` line —\n * so one reader serves them all, and a second hand-rolled parse loop is the\n * thing this file exists to prevent. Deliberately minimal, matching the\n * server's writer rather than the whole SSE grammar: split on blank lines,\n * JSON-parse each `data:` line on its own, skip what does not parse (the next\n * frame re-reports state), and ignore every other line — keepalive comments\n * (`: keepalive`) included.\n *\n * Frames are separated by a bare `\\n\\n` and each `data:` line is parsed on its\n * own, exactly as the server writes them. Both halves are literal: a stream\n * whose frames arrive CRLF-terminated, or whose JSON was split across two\n * `data:` lines, yields NOTHING rather than erroring — so an intermediary that\n * normalises line endings would show up as an empty stream, not a parse\n * failure. The server's writer produces neither shape.\n *\n * No timeout is applied. The caller owns the lifetime: pass the `AbortSignal`\n * to the `fetch` that produced `res`, or just stop iterating — the `finally`\n * cancels the body, which ends the HTTP request.\n */\nexport async function* readSseStream<T>(\n res: Response,\n opts: {\n /** Names the stream in the \"no response body\" error. */\n label?: string\n } = {},\n): AsyncGenerator<T, void, undefined> {\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 if (!res.body) {\n throw new NodaroError(`${opts.label ?? \"event stream\"} has no response body`, \"empty_stream\", res.status)\n }\n\n const reader = res.body.getReader()\n const decoder = new TextDecoder()\n let buffer = \"\"\n try {\n for (;;) {\n const { done, value } = await reader.read()\n if (done) break\n buffer += decoder.decode(value, { stream: true })\n let sep: number\n while ((sep = buffer.indexOf(\"\\n\\n\")) >= 0) {\n const frame = buffer.slice(0, sep)\n buffer = buffer.slice(sep + 2)\n for (const line of frame.split(\"\\n\")) {\n if (!line.startsWith(\"data:\")) continue\n try {\n yield JSON.parse(line.slice(5).trim()) as T\n } catch {\n // Skip malformed frames — the next tick re-reports full state.\n }\n }\n }\n }\n } finally {\n // Ends the HTTP request when the consumer breaks out of the loop early.\n reader.releaseLock()\n await res.body.cancel().catch(() => {})\n }\n}\n"]}
@@ -57,5 +57,5 @@ var CastSwapResource = class {
57
57
  };
58
58
 
59
59
  export { CastSwapResource };
60
- //# sourceMappingURL=chunk-DV6GTEX5.js.map
61
- //# sourceMappingURL=chunk-DV6GTEX5.js.map
60
+ //# sourceMappingURL=chunk-4SZUSZXK.js.map
61
+ //# sourceMappingURL=chunk-4SZUSZXK.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/resources/cast-swap.ts"],"names":[],"mappings":";;;AAoeO,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,MAAM,SAAS,MAAA,EAAmD;AAChE,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAA0B,QAAQ,wBAAA,EAA0B,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AAE5G,IAAA,MAAM,MAAA,GAAS,sBAAA,CAAuB,KAAA,EAAO,MAAM,CAAA;AACnD,IAAA,OAAO,MAAA,IAAU,WAAW,KAAA,CAAM,MAAA,GAAS,EAAE,GAAG,KAAA,EAAO,QAAO,GAAI,KAAA;AAAA,EACpE;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-DV6GTEX5.js","sourcesContent":["import { nodaroMotionEngineFrom } from \"@nodaro/shared/models\"\nimport type { NodaroClientCore } from \"../core.js\"\n\n/**\n * Nodaro Motion (the `castSwap` resource; routes `/v1/cast-swap`) — replace\n * the people, pets, products and/or the location in a short video with your\n * own references, keeping its motion, framing, timing and sound (the clip's\n * 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 Nodaro Motion Prism (`engine` left out, or\n * `\"motion-prism\"`) or Nodaro Motion Pulse (`\"motion-pulse\"`). Pulse renders 720p\n * (its default) or 1080p and has no draft; it makes a new take guided by the\n * clip and pictures, so its Full Quality is a new take at 1080p rather than\n * the same take re-rendered (`CastSwapFullQualityEstimate.kind`).\n *\n * How the clip is prepared for the video model (for one, whether faces are\n * hidden first, and whether a refused clip is retried) is set by Nodaro for\n * each engine; a run cannot change it. `faceMasking` is no longer read\n * (deprecated: accepted and ignored).\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); on Nodaro Motion Pulse,\n * `draft_unsupported` (`draft: true`), `resolution_unsupported` (480p),\n * `output_format_unsupported` (a MOV edit output) and `too_many_images` (more\n * than 10 images in the run, across every role and fixed reference);\n * `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; Nodaro Motion Pulse'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 engine a Nodaro Motion run is made on, by its public id. Left out =\n * `\"motion-prism\"`.\n *\n * - `\"motion-prism\"` (Nodaro Motion Prism): 480p (the default, a draft unless\n * `draft: false`), 720p or 1080p; Full Quality re-renders a draft's own take\n * at 1080p.\n * - `\"motion-pulse\"` (Nodaro Motion Pulse): 720p (the default) or 1080p, never a\n * draft (`draft: true` answers 400 `draft_unsupported`); Full Quality makes a\n * new take at 1080p from a completed 720p run's inputs. A preview is still\n * 4 s from `previewStart`.\n */\nexport type CastSwapEngine = \"motion-prism\" | \"motion-pulse\"\n\n/**\n * The engine ids an older client sent: the earlier public ids (`\"motion-pro\"`\n * = `\"motion-prism\"`, `\"motion-lite\"` = `\"motion-pulse\"`) and the internal\n * ones (`\"seedance-2-5\"`, `\"kling-4\"`). Still accepted in a request; never\n * returned.\n * @deprecated use {@link CastSwapEngine}.\n */\nexport type CastSwapEngineLegacyId = \"motion-pro\" | \"motion-lite\" | \"seedance-2-5\" | \"kling-4\"\n\n/**\n * How a run follows its clip. Left out = `\"precise\"`.\n *\n * - `\"precise\"`: follows the clip exactly.\n * - `\"motion\"`: follows the clip's movement and takes the looks from your\n * photos, more freely than `\"precise\"`. Its price may differ from\n * `\"precise\"`'s: the estimate shows the exact price.\n */\nexport type CastSwapStyle = \"precise\" | \"motion\"\n\n/**\n * Where a Nodaro Motion Pulse run cuts the clip to whole seconds (Pulse renders\n * whole 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 CastSwapMotionLength = \"cut-end\" | \"cut-start\"\n\n/** @deprecated use {@link CastSwapMotionLength}. */\nexport type CastSwapKlingLength = CastSwapMotionLength\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 * @deprecated Nodaro sets this per engine; a run's `faceMasking` is ignored.\n */\nexport interface CastSwapFaceMaskingZone {\n leftPct?: number\n rightPct?: number\n topPct?: number\n bottomPct?: number\n}\n\n/**\n * @deprecated Nodaro sets how the clip is prepared for the video model, per\n * engine; a run cannot change it. A `faceMasking` sent is accepted and\n * ignored (no error), so older code keeps working unchanged.\n */\nexport interface CastSwapFaceMasking {\n mode: \"off\" | \"always\" | \"auto\"\n fill?: \"blur\" | \"depth\"\n minFaceHeightPct?: number\n maxFaceHeightPct?: number\n zone?: CastSwapFaceMaskingZone | null\n blurStrengthPct?: number\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 engine (see {@link CastSwapEngine}). Default `\"motion-prism\"`. The job\n * records it in `input_data.engine`, and a completed job's output in its\n * `engine`. The ids an older client sent ({@link CastSwapEngineLegacyId})\n * are still accepted.\n */\n engine?: CastSwapEngine | CastSwapEngineLegacyId\n /**\n * How the run follows the clip (see {@link CastSwapStyle}). Default\n * `\"precise\"`; the job records it in `input_data.style`.\n */\n style?: CastSwapStyle\n /**\n * Nodaro Motion Pulse only (Prism ignores it): which end of the clip is cut to\n * whole seconds (see {@link CastSwapMotionLength}). Default `\"cut-end\"`.\n */\n motionLength?: CastSwapMotionLength\n /** @deprecated use {@link motionLength} (still accepted). */\n klingLength?: CastSwapKlingLength\n /**\n * The output resolution. Nodaro Motion Prism: `\"480p\"` (default), `\"720p\"` or\n * `\"1080p\"`. Nodaro Motion Pulse: `\"720p\"` (default) or `\"1080p\"`; `\"480p\"`\n * answers 400.\n */\n resolution?: \"480p\" | \"720p\" | \"1080p\"\n /**\n * Render as a draft (Nodaro Motion Prism only). Defaults to `true` at 480p and\n * `false` above it; `true` with `\"720p\"` or `\"1080p\"` answers 400 (draft is\n * 480p only). Nodaro Motion Pulse has no draft: `true` answers 400\n * `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 /**\n * Ignored: Nodaro sets how the clip is prepared, per engine. Accepted (no\n * error) so older code keeps working.\n * @deprecated leave it out.\n */\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, by its public id (absent from an older\n * server = `\"motion-prism\"`). An id an older server answers is read as the\n * public id it names.\n */\n engine?: CastSwapEngine\n /**\n * What is held when the run starts. The run is charged what it actually\n * costs, which is expected to be `expectedCredits`.\n */\n totalCredits: number\n /**\n * The charge the run is expected to come to. Never above `totalCredits`\n * (on Nodaro Motion Pulse it equals it: Pulse renders exactly the length\n * asked for, so the hold is what it settles at). Absent from an older\n * server: read `totalCredits` then.\n */\n expectedCredits?: number\n /**\n * The parts of the quote, by name, for display only:\n * - `base`: the run itself (its base fee). A server before Cast Swap 0.396.0\n * names it `castSwap` instead: read `base`, else `castSwap`;\n * - `references`: preparing the reference pictures (0 when\n * `prepareReferences` is off or there is nothing to prepare);\n * - `motion`: what the video holds (on Nodaro Motion Pulse, what it costs);\n * - `motionExpected`: what the video is expected to be charged, never above\n * `motion` (Nodaro Motion Prism);\n * - `privacy`: preparing the clip, when the engine's setting does. In both\n * totals when it always runs; outside both when it is the retry\n * (`privacyCredits`).\n *\n * `motion` and `motionExpected` are alternatives, so the parts sum to more\n * than either total: read `totalCredits` and `expectedCredits`, never the\n * sum. Other parts may appear.\n */\n breakdown: {\n base?: number\n /** @deprecated read {@link base} (an older server answered this name for the same part). */\n castSwap?: number\n references: number\n motion?: number\n motionExpected?: number\n privacy?: number\n } & Record<string, number>\n /**\n * The extra credits charged only if the video model refuses the clip and\n * the run is retried (Nodaro's setting for the engine decides whether it\n * does). Not in `totalCredits` or `expectedCredits`; `run()` needs credits\n * for both the hold and this (else 402).\n */\n privacyCredits?: number\n /** @deprecated read {@link privacyCredits} (an older server answered this name). */\n maskingCredits?: number\n /**\n * `true` when the video is made at Auto length (every Nodaro Motion Prism\n * run): the final charge is expected to come out lower than `totalCredits`,\n * at about `expectedCredits`. `false` on Nodaro Motion Pulse.\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 Nodaro\n * Motion Prism draft's own take at 1080p; `\"new-take\"` makes a new Nodaro Motion Pulse 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 Nodaro Motion Prism\n * draft, or a completed Nodaro Motion Pulse 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 server: `\"same-take\"`.\n */\n kind?: CastSwapFullQualityKind\n /** The quote in credits: `breakdown.base` plus the 1080p render (same take) or the new take. */\n totalCredits: number\n /**\n * The parts of the quote, by name, for display only: `base` (the Full\n * Quality job's own steps; a server before Cast Swap 0.396.0 names it\n * `castSwap` instead: read `base`, else `castSwap`), and `motionHd` (a same\n * take: the 1080p render, 0 when an earlier try of this draft already\n * finished that render, which is then reused) or `motion` (a new take).\n */\n breakdown: {\n base?: number\n /** @deprecated read {@link base} (an older server answered this name for the same part). */\n castSwap?: number\n motionHd?: number\n motion?: number\n } & Record<string, 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 Nodaro Motion 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 Nodaro Motion Prism draft nor a\n * Nodaro Motion Pulse run at 720p (a Pulse 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 Nodaro Motion 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 async estimate(params: CastSwapParams): Promise<CastSwapEstimate> {\n const quote = await this.client.request<CastSwapEstimate>(\"POST\", \"/v1/cast-swap/estimate\", { body: params })\n // An older server names the engine by an earlier id: answered as the public id it names.\n const engine = nodaroMotionEngineFrom(quote?.engine)\n return engine && engine !== quote.engine ? { ...quote, engine } : quote\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 Nodaro Motion Prism DRAFT run, or a finished Nodaro Motion Pulse\n * run at 720p (a Nodaro Motion 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 * Nodaro Motion Prism draft's own take re-rendered, or a new Nodaro Motion Pulse 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"]}
1
+ {"version":3,"sources":["../src/resources/cast-swap.ts"],"names":[],"mappings":";;;AA8eO,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,MAAM,SAAS,MAAA,EAAmD;AAChE,IAAA,MAAM,KAAA,GAAQ,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA,CAA0B,QAAQ,wBAAA,EAA0B,EAAE,IAAA,EAAM,MAAA,EAAQ,CAAA;AAE5G,IAAA,MAAM,MAAA,GAAS,sBAAA,CAAuB,KAAA,EAAO,MAAM,CAAA;AACnD,IAAA,OAAO,MAAA,IAAU,WAAW,KAAA,CAAM,MAAA,GAAS,EAAE,GAAG,KAAA,EAAO,QAAO,GAAI,KAAA;AAAA,EACpE;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-4SZUSZXK.js","sourcesContent":["import { nodaroMotionEngineFrom } from \"@nodaro/shared/models\"\nimport type { NodaroClientCore } from \"../core.js\"\n\n/**\n * Nodaro Motion (the `castSwap` resource; routes `/v1/cast-swap`) — replace\n * the people, pets, products and/or the location in a short video with your\n * own references, keeping its motion, framing, timing and sound (the clip's\n * 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 Nodaro Motion Prism (`engine` left out, or\n * `\"motion-prism\"`) or Nodaro Motion Pulse (`\"motion-pulse\"`). Pulse renders 720p\n * (its default) or 1080p and has no draft; it makes a new take guided by the\n * clip and pictures, so its Full Quality is a new take at 1080p rather than\n * the same take re-rendered (`CastSwapFullQualityEstimate.kind`).\n *\n * How the clip is prepared for the video model (for one, whether faces are\n * hidden first, and whether a refused clip is retried) is set by Nodaro for\n * each engine; a run cannot change it. `faceMasking` is no longer read\n * (deprecated: accepted and ignored).\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); on Nodaro Motion Pulse,\n * `draft_unsupported` (`draft: true`), `resolution_unsupported` (480p),\n * `output_format_unsupported` (a MOV edit output) and `too_many_images` (more\n * than 10 images in the run, across every role and fixed reference);\n * `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; Nodaro Motion Pulse'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 engine a Nodaro Motion run is made on, by its public id. Left out =\n * `\"motion-prism\"`.\n *\n * - `\"motion-prism\"` (Nodaro Motion Prism): 480p (the default, a draft unless\n * `draft: false`), 720p or 1080p; Full Quality re-renders a draft's own take\n * at 1080p.\n * - `\"motion-pulse\"` (Nodaro Motion Pulse): 720p (the default) or 1080p, never a\n * draft (`draft: true` answers 400 `draft_unsupported`); Full Quality makes a\n * new take at 1080p from a completed 720p run's inputs. A preview is still\n * 4 s from `previewStart`.\n */\nexport type CastSwapEngine = \"motion-prism\" | \"motion-pulse\"\n\n/**\n * The engine ids an older client sent: the earlier public ids (`\"motion-pro\"`\n * = `\"motion-prism\"`, `\"motion-lite\"` = `\"motion-pulse\"`) and the internal\n * ones (`\"seedance-2-5\"`, `\"kling-4\"`). Still accepted in a request; never\n * returned.\n * @deprecated use {@link CastSwapEngine}.\n */\nexport type CastSwapEngineLegacyId = \"motion-pro\" | \"motion-lite\" | \"seedance-2-5\" | \"kling-4\"\n\n/**\n * How a run follows its clip. Left out = `\"precise\"`.\n *\n * - `\"precise\"`: follows the clip exactly.\n * - `\"motion\"`: follows the clip's movement and takes the looks from your\n * photos, more freely than `\"precise\"`. Its price may differ from\n * `\"precise\"`'s: the estimate shows the exact price.\n */\nexport type CastSwapStyle = \"precise\" | \"motion\"\n\n/**\n * Where a Nodaro Motion Pulse run cuts the clip to whole seconds (Pulse renders\n * whole 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 CastSwapMotionLength = \"cut-end\" | \"cut-start\"\n\n/** @deprecated use {@link CastSwapMotionLength}. */\nexport type CastSwapKlingLength = CastSwapMotionLength\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 * @deprecated Nodaro sets this per engine; a run's `faceMasking` is ignored.\n */\nexport interface CastSwapFaceMaskingZone {\n leftPct?: number\n rightPct?: number\n topPct?: number\n bottomPct?: number\n}\n\n/**\n * @deprecated Nodaro sets how the clip is prepared for the video model, per\n * engine; a run cannot change it. A `faceMasking` sent is accepted and\n * ignored (no error), so older code keeps working unchanged.\n */\nexport interface CastSwapFaceMasking {\n mode: \"off\" | \"always\" | \"auto\"\n fill?: \"blur\" | \"depth\"\n minFaceHeightPct?: number\n maxFaceHeightPct?: number\n zone?: CastSwapFaceMaskingZone | null\n blurStrengthPct?: number\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 engine (see {@link CastSwapEngine}). Default `\"motion-prism\"`. The job\n * records it in `input_data.engine`, and a completed job's output in its\n * `engine`. The ids an older client sent ({@link CastSwapEngineLegacyId})\n * are still accepted.\n */\n engine?: CastSwapEngine | CastSwapEngineLegacyId\n /**\n * How the run follows the clip (see {@link CastSwapStyle}). Default\n * `\"precise\"`; the job records it in `input_data.style`.\n */\n style?: CastSwapStyle\n /**\n * Flex only (`style: \"motion\"`, a direct run): an art style that restyles\n * the whole video, as a `direction.style` id of the platform's `style`\n * picker catalog (the same ids an image's `direction.style` takes); the\n * platform words it. Refused with 400 `unknown_art_style` (not in the\n * catalog), `art_style_not_supported` (a saved moodboard, `moodboard:…`)\n * or `art_style_needs_flex` (Precise, a trend run). It never changes the\n * price. The job records it in `input_data.direction`.\n */\n direction?: { style: string }\n /**\n * Nodaro Motion Pulse only (Prism ignores it): which end of the clip is cut to\n * whole seconds (see {@link CastSwapMotionLength}). Default `\"cut-end\"`.\n */\n motionLength?: CastSwapMotionLength\n /** @deprecated use {@link motionLength} (still accepted). */\n klingLength?: CastSwapKlingLength\n /**\n * The output resolution. Nodaro Motion Prism: `\"480p\"` (default), `\"720p\"` or\n * `\"1080p\"`. Nodaro Motion Pulse: `\"720p\"` (default) or `\"1080p\"`; `\"480p\"`\n * answers 400.\n */\n resolution?: \"480p\" | \"720p\" | \"1080p\"\n /**\n * Render as a draft (Nodaro Motion Prism only). Defaults to `true` at 480p and\n * `false` above it; `true` with `\"720p\"` or `\"1080p\"` answers 400 (draft is\n * 480p only). Nodaro Motion Pulse has no draft: `true` answers 400\n * `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 /**\n * Ignored: Nodaro sets how the clip is prepared, per engine. Accepted (no\n * error) so older code keeps working.\n * @deprecated leave it out.\n */\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, by its public id (absent from an older\n * server = `\"motion-prism\"`). An id an older server answers is read as the\n * public id it names.\n */\n engine?: CastSwapEngine\n /**\n * What is held when the run starts. The run is charged what it actually\n * costs, which is expected to be `expectedCredits`.\n */\n totalCredits: number\n /**\n * The charge the run is expected to come to. Never above `totalCredits`\n * (on Nodaro Motion Pulse it equals it: Pulse renders exactly the length\n * asked for, so the hold is what it settles at). Absent from an older\n * server: read `totalCredits` then.\n */\n expectedCredits?: number\n /**\n * The parts of the quote, by name, for display only:\n * - `base`: the run itself (its base fee). A server before Cast Swap 0.396.0\n * names it `castSwap` instead: read `base`, else `castSwap`;\n * - `references`: preparing the reference pictures (0 when\n * `prepareReferences` is off or there is nothing to prepare);\n * - `motion`: what the video holds (on Nodaro Motion Pulse, what it costs);\n * - `motionExpected`: what the video is expected to be charged, never above\n * `motion` (Nodaro Motion Prism);\n * - `privacy`: preparing the clip, when the engine's setting does. In both\n * totals when it always runs; outside both when it is the retry\n * (`privacyCredits`).\n *\n * `motion` and `motionExpected` are alternatives, so the parts sum to more\n * than either total: read `totalCredits` and `expectedCredits`, never the\n * sum. Other parts may appear.\n */\n breakdown: {\n base?: number\n /** @deprecated read {@link base} (an older server answered this name for the same part). */\n castSwap?: number\n references: number\n motion?: number\n motionExpected?: number\n privacy?: number\n } & Record<string, number>\n /**\n * The extra credits charged only if the video model refuses the clip and\n * the run is retried (Nodaro's setting for the engine decides whether it\n * does). Not in `totalCredits` or `expectedCredits`; `run()` needs credits\n * for both the hold and this (else 402).\n */\n privacyCredits?: number\n /** @deprecated read {@link privacyCredits} (an older server answered this name). */\n maskingCredits?: number\n /**\n * `true` when the video is made at Auto length (every Nodaro Motion Prism\n * run): the final charge is expected to come out lower than `totalCredits`,\n * at about `expectedCredits`. `false` on Nodaro Motion Pulse.\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 Nodaro\n * Motion Prism draft's own take at 1080p; `\"new-take\"` makes a new Nodaro Motion Pulse 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 Nodaro Motion Prism\n * draft, or a completed Nodaro Motion Pulse 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 server: `\"same-take\"`.\n */\n kind?: CastSwapFullQualityKind\n /** The quote in credits: `breakdown.base` plus the 1080p render (same take) or the new take. */\n totalCredits: number\n /**\n * The parts of the quote, by name, for display only: `base` (the Full\n * Quality job's own steps; a server before Cast Swap 0.396.0 names it\n * `castSwap` instead: read `base`, else `castSwap`), and `motionHd` (a same\n * take: the 1080p render, 0 when an earlier try of this draft already\n * finished that render, which is then reused) or `motion` (a new take).\n */\n breakdown: {\n base?: number\n /** @deprecated read {@link base} (an older server answered this name for the same part). */\n castSwap?: number\n motionHd?: number\n motion?: number\n } & Record<string, 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 Nodaro Motion 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 Nodaro Motion Prism draft nor a\n * Nodaro Motion Pulse run at 720p (a Pulse 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 Nodaro Motion 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 async estimate(params: CastSwapParams): Promise<CastSwapEstimate> {\n const quote = await this.client.request<CastSwapEstimate>(\"POST\", \"/v1/cast-swap/estimate\", { body: params })\n // An older server names the engine by an earlier id: answered as the public id it names.\n const engine = nodaroMotionEngineFrom(quote?.engine)\n return engine && engine !== quote.engine ? { ...quote, engine } : quote\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 Nodaro Motion Prism DRAFT run, or a finished Nodaro Motion Pulse\n * run at 720p (a Nodaro Motion 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 * Nodaro Motion Prism draft's own take re-rendered, or a new Nodaro Motion Pulse 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"]}