@nodaro/sdk 2.31.0 → 2.33.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 (71) hide show
  1. package/dist/{chunk-XQAIEPAZ.js → chunk-4CXB7RA6.js} +2 -2
  2. package/dist/chunk-4CXB7RA6.js.map +1 -0
  3. package/dist/{chunk-REUFPWM7.cjs → chunk-56NJIP7P.cjs} +3 -3
  4. package/dist/{chunk-REUFPWM7.cjs.map → chunk-56NJIP7P.cjs.map} +1 -1
  5. package/dist/{chunk-I4A5VJRL.js → chunk-5A7J24LN.js} +68 -20
  6. package/dist/chunk-5A7J24LN.js.map +1 -0
  7. package/dist/{chunk-QITJGSFA.js → chunk-6ECW23GA.js} +6 -2
  8. package/dist/chunk-6ECW23GA.js.map +1 -0
  9. package/dist/{chunk-RAZN6C5R.cjs → chunk-6T54F65Q.cjs} +2 -2
  10. package/dist/{chunk-PHKYIWE4.cjs.map → chunk-6T54F65Q.cjs.map} +1 -1
  11. package/dist/{chunk-PHKYIWE4.cjs → chunk-7CK6I4CT.cjs} +2 -2
  12. package/dist/{chunk-RAZN6C5R.cjs.map → chunk-7CK6I4CT.cjs.map} +1 -1
  13. package/dist/{chunk-BFMKPELU.cjs → chunk-B7RG6AUQ.cjs} +6 -2
  14. package/dist/chunk-B7RG6AUQ.cjs.map +1 -0
  15. package/dist/{chunk-7ISSKQ6S.cjs → chunk-GB724HW2.cjs} +2 -2
  16. package/dist/chunk-GB724HW2.cjs.map +1 -0
  17. package/dist/{chunk-UI2NXUML.cjs → chunk-GKQSA24K.cjs} +10 -10
  18. package/dist/{chunk-UI2NXUML.cjs.map → chunk-GKQSA24K.cjs.map} +1 -1
  19. package/dist/{chunk-5WAI7VGO.js → chunk-JSCC2N7N.js} +6 -6
  20. package/dist/{chunk-5WAI7VGO.js.map → chunk-JSCC2N7N.js.map} +1 -1
  21. package/dist/{chunk-WUIEZ6Z4.cjs → chunk-RHJRLXKY.cjs} +68 -20
  22. package/dist/chunk-RHJRLXKY.cjs.map +1 -0
  23. package/dist/chunk-UKB64254.js +3 -0
  24. package/dist/{chunk-W7YI5UT3.js.map → chunk-UKB64254.js.map} +1 -1
  25. package/dist/{chunk-LHP2CWHD.js → chunk-Y5CYFNNW.js} +3 -3
  26. package/dist/{chunk-LHP2CWHD.js.map → chunk-Y5CYFNNW.js.map} +1 -1
  27. package/dist/chunk-ZYPH2FPT.js +3 -0
  28. package/dist/{chunk-ZH4F2P2G.js.map → chunk-ZYPH2FPT.js.map} +1 -1
  29. package/dist/entries/characters.d.cts +1 -1
  30. package/dist/entries/characters.d.ts +1 -1
  31. package/dist/entries/client.cjs +10 -10
  32. package/dist/entries/client.d.cts +1 -1
  33. package/dist/entries/client.d.ts +1 -1
  34. package/dist/entries/client.js +5 -5
  35. package/dist/entries/core.cjs +5 -5
  36. package/dist/entries/core.js +1 -1
  37. package/dist/entries/creatures.d.cts +1 -1
  38. package/dist/entries/creatures.d.ts +1 -1
  39. package/dist/entries/credits.cjs +2 -2
  40. package/dist/entries/credits.d.cts +8 -0
  41. package/dist/entries/credits.d.ts +8 -0
  42. package/dist/entries/credits.js +1 -1
  43. package/dist/entries/edit.cjs +8 -8
  44. package/dist/entries/edit.d.cts +134 -24
  45. package/dist/entries/edit.d.ts +134 -24
  46. package/dist/entries/edit.js +2 -2
  47. package/dist/entries/locations.d.cts +1 -1
  48. package/dist/entries/locations.d.ts +1 -1
  49. package/dist/entries/nodes.cjs +5 -5
  50. package/dist/entries/nodes.d.cts +2 -2
  51. package/dist/entries/nodes.d.ts +2 -2
  52. package/dist/entries/nodes.js +2 -2
  53. package/dist/entries/objects.d.cts +1 -1
  54. package/dist/entries/objects.d.ts +1 -1
  55. package/dist/entries/scene3d.d.cts +2 -2
  56. package/dist/entries/scene3d.d.ts +2 -2
  57. package/dist/index.cjs +23 -23
  58. package/dist/index.d.cts +3 -3
  59. package/dist/index.d.ts +3 -3
  60. package/dist/index.js +7 -7
  61. package/dist/{nodes-C2zu3uqn.d.ts → nodes-C-wrO6Sg.d.ts} +106 -6
  62. package/dist/{nodes-BPvrAoUs.d.cts → nodes-J8ByUN8z.d.cts} +106 -6
  63. package/package.json +2 -2
  64. package/dist/chunk-7ISSKQ6S.cjs.map +0 -1
  65. package/dist/chunk-BFMKPELU.cjs.map +0 -1
  66. package/dist/chunk-I4A5VJRL.js.map +0 -1
  67. package/dist/chunk-QITJGSFA.js.map +0 -1
  68. package/dist/chunk-W7YI5UT3.js +0 -3
  69. package/dist/chunk-WUIEZ6Z4.cjs.map +0 -1
  70. package/dist/chunk-XQAIEPAZ.js.map +0 -1
  71. package/dist/chunk-ZH4F2P2G.js +0 -3
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/resources/edit-use-case-inputs.ts","../src/resources/edit.ts"],"names":[],"mappings":"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","file":"chunk-I4A5VJRL.js","sourcesContent":["/**\n * The inputs of the podcast use-case runs on the `edit` resource (Tighten\n * Episode, Chapters, Trailer, Clip Pack) and the request body each one sends.\n * Split from `edit.ts`, which holds the methods, the type list and the\n * continue routes; every name here is re-exported from there.\n */\nimport type { EditPlanTier, EdlTargetAspect, SpeakerEmphasisStyle, SpeakerSwitchId } from \"@nodaro/shared/edl\"\nimport type { CaptionStyle } from \"@nodaro/shared/captions\"\n\n/**\n * Input for {@link EditResource.tightenEpisode} and\n * {@link EditResource.estimateTightenEpisode}: one recording in, the tightened\n * episode out. Only `mediaUrl` is required; every other field left out takes\n * the node's default.\n */\nexport interface TightenEpisodeInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl: string\n /** Optional music bed, ducked under the voice and ending with the episode. */\n musicUrl?: string\n /** `\"video\"` or `\"audio\"`; an audio recording always makes an audio episode. */\n output?: \"video\" | \"audio\"\n /**\n * Frame a video episode with Speaker View: an output aspect and a layout,\n * over automatic boxes on the speakers on screen. A layout the aspect does\n * not draw is snapped to one it does. `null` or absent: no framing.\n */\n framing?: {\n aspect: EdlTargetAspect\n layout: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n } | null\n /** Captions on a video episode (on by default). */\n captions?: boolean\n /** The captions' look. */\n captionStyle?: CaptionStyle\n /**\n * Stop at a quick preview for a person to review; render the full-quality\n * episode afterwards with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier. */\n planTier?: EditPlanTier\n /** Editing instructions for the planner (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n /** How far the music dips under the voice, 0–100 (default 75). */\n musicDuckAmount?: number\n}\n\n/**\n * Input for {@link EditResource.episodeChapters} and\n * {@link EditResource.estimateEpisodeChapters}: one recording in, its chapters\n * out. Only `mediaUrl` is required; every other field left out takes the\n * node's default.\n */\nexport interface EpisodeChaptersInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl: string\n /** The planner's tier (default `\"economy\"`). */\n planTier?: EditPlanTier\n /** Instructions for the chapters, e.g. \"5 to 8 chapters\" (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the chapter titles (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The formats a Trailer is delivered in, in delivery order (16:9 is a 1920×1080 landscape). */\nexport const TRAILER_FORMATS = [\"9:16\", \"1:1\", \"4:5\", \"16:9\"] as const\n/** One Trailer format (see {@link TRAILER_FORMATS}). */\nexport type TrailerFormat = (typeof TRAILER_FORMATS)[number]\n\n/**\n * Input for {@link EditResource.episodeTrailer} and\n * {@link EditResource.estimateEpisodeTrailer}: one video recording in, a\n * 20–40 s trailer out in each chosen format. Only `mediaUrl` is required.\n */\nexport interface EpisodeTrailerInput {\n /** The recording — a video URL, up to 3 hours (an audio-only recording is refused). */\n mediaUrl: string\n /** An optional intro card: a short video (15 s at most) played before the trailer. */\n introCardUrl?: string\n /** The formats to deliver, padded, never cropped (default `[\"9:16\", \"1:1\", \"4:5\"]`; an empty list is refused). */\n formats?: TrailerFormat[]\n /** Stop at a preview of the trailer; render every format with {@link EditResource.continueUseCase} (`mode: \"render-final\"`). */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for the trailer (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The shortest and longest clip a Clip Pack asks the plan for, in seconds. */\nexport const CLIP_PACK_MIN_TARGET_SEC = 5\nexport const CLIP_PACK_MAX_TARGET_SEC = 180\n\n/**\n * A Clip Pack emphasis: `\"none\"`, or one or more of `\"scale\"`, `\"border\"` and\n * `\"dim\"` joined with `\"+\"` (each once — the route refuses a repeat).\n */\nexport type ClipPackEmphasis = SpeakerEmphasisStyle | `${Exclude<SpeakerEmphasisStyle, \"none\">}+${string}`\n\n/**\n * Input for {@link EditResource.clipPack} and\n * {@link EditResource.estimateClipPack}: one recording in, a pack of short\n * clips out. Only `mediaUrl` is required; every other field left out takes\n * the node's default.\n */\nexport interface ClipPackInput {\n /** The recording — a video or audio URL, up to 3 hours. An audio recording makes audio clips (no framing, no captions). */\n mediaUrl: string\n /** How many clips to find, 1–50 (default 8). The plan may find fewer. */\n count?: number\n /** Each clip's target length in seconds, {@link CLIP_PACK_MIN_TARGET_SEC}–{@link CLIP_PACK_MAX_TARGET_SEC} (default 60). */\n targetDurationSec?: number\n /**\n * Frame each video clip with Speaker View on automatic boxes over the\n * episode's speakers: the shape, and optionally the layout (default\n * `\"stacked\"`; one the shape does not draw is snapped), the switch at each\n * speaker change (default `\"pan\"`) and the emphasis (default `\"scale\"`;\n * `\"none\"`, or `\"scale\"`, `\"border\"` and `\"dim\"` joined with `\"+\"`). Left\n * out: 9:16, stacked, pan, scale. `null`: no framing (the recording's shape).\n */\n framing?: {\n aspect: EdlTargetAspect\n layout?: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n switchType?: SpeakerSwitchId\n emphasisStyle?: ClipPackEmphasis\n } | null\n /** Caption each video clip (on by default). */\n captions?: boolean\n /** The captions' look (default `\"karaoke\"`). */\n captionStyle?: CaptionStyle\n /**\n * Stop at quick previews of the clips for a person to review; render the\n * kept clips at full quality with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`, an `editedEdl` that keeps or drops clips).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for picking the clips (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\nexport function tightenBody(input: TightenEpisodeInput): Record<string, unknown> {\n const { mediaUrl, ...rest } = input\n return { mediaUrls: [mediaUrl], ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)) }\n}\n\nexport function chaptersBody(input: EpisodeChaptersInput): Record<string, unknown> {\n return {\n mediaUrls: [input.mediaUrl],\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n }\n}\n\nexport function trailerBody(input: EpisodeTrailerInput): Record<string, unknown> {\n return {\n mediaUrls: [input.mediaUrl],\n ...(input.introCardUrl !== undefined ? { introCardUrl: input.introCardUrl } : {}),\n // An empty list is sent as is: the route refuses it (at least one format).\n ...(input.formats !== undefined ? { formats: input.formats } : {}),\n ...(input.reviewBeforeRender !== undefined ? { reviewBeforeRender: input.reviewBeforeRender } : {}),\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n }\n}\n\n/** Clip Pack's body: what the caller set, `framing: null` included (no framing). */\nexport function clipPackBody(input: ClipPackInput): Record<string, unknown> {\n const { mediaUrl, ...rest } = input\n return { mediaUrls: [mediaUrl], ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)) }\n}\n","import type { NodaroClientCore } from \"../core.js\"\nimport { ForbiddenError, NodaroError } from \"../errors.js\"\nimport {\n remapTranscriptThroughEdl,\n unwrapEditPlanOutput,\n resolveEditPlanSources,\n describeAudioSyncOffsetIssue,\n transcriptSpeakerLabels,\n cameraSwitchSpeakerMap, speakerMapResultOf, type SpeakerMapResult,\n type Edl,\n type Transcript,\n type EditPlanMode,\n type EditPlanTier,\n type EdlClipSet,\n type ChapterSet,\n type EdlTargetAspect\n} from \"@nodaro/shared/edl\"\nimport { USE_CASE_NODE_TYPES, isUseCaseNodeType, type UseCaseNodeType } from \"@nodaro/shared/nodes\"\nimport type { SpeakerMapInput } from \"./speaker-map-inputs.js\"\nimport {\n chaptersBody,\n clipPackBody,\n tightenBody,\n trailerBody,\n type ClipPackInput,\n type EpisodeChaptersInput,\n type EpisodeTrailerInput,\n type TightenEpisodeInput,\n} from \"./edit-use-case-inputs.js\"\n\nexport { CLIP_PACK_MAX_TARGET_SEC, CLIP_PACK_MIN_TARGET_SEC, TRAILER_FORMATS } from \"./edit-use-case-inputs.js\"\nexport type { ClipPackEmphasis, ClipPackInput, EpisodeChaptersInput, EpisodeTrailerInput, TightenEpisodeInput, TrailerFormat } from \"./edit-use-case-inputs.js\"\n\n// Re-export the canonical EDL / transcript vocabulary from `@nodaro/shared`\n// (single source of truth — the shapes live at `packages/shared/src/edl.ts`).\n// `@nodaro/shared` is already a hard dep of this package, so there is no\n// bundle-size cost to importing from it, and an integration builds requests and\n// reads results with one dependency. `EdlClipSet` / `ChapterSet` are the raw\n// `output_data` shapes of the `clips` / `chapters` modes, and\n// `unwrapEditPlanOutput` is the sanctioned way to normalize any edit-plan job's\n// `output_data` (it also strips the relay's `viaNodaroCloud` marker).\nexport type { Edl, Transcript, EditPlanMode, EditPlanTier, EdlClipSet, ChapterSet }\nexport { unwrapEditPlanOutput }\n\n/**\n * A silence-detect job's `output_data.json` — the value to pass as\n * {@link EditPlanInput.silence}. It is NOT what {@link EditResource.silenceDetect}\n * returns (that is `{ jobId }`); fetch the finished job and read its\n * `output_data.json`. The planner reads the `ranges` array — an input without\n * one is silently ignored, so pass this whole object.\n */\nexport interface SilenceRanges {\n /** Wire version of the payload. */\n version?: number\n /** Silence spans on the source clock, in ms. */\n ranges: ReadonlyArray<{ startMs: number; endMs: number }>\n /** Total source duration, in ms. */\n durationMs?: number\n}\n\n/** Every editorial route answers with the queued job to poll. */\nexport interface EditJobResult {\n /** The queued job id — follow it via `client.jobs.getStatus(jobId)`. */\n jobId: string\n}\n\nexport interface SilenceDetectInput {\n /**\n * An audio OR video source. A single ffmpeg `silencedetect` pass runs over the\n * source's audio proxy either way — no provider key required.\n */\n audioUrl: string\n /** dBFS threshold, always ≤ 0. Default -35 (a good podcast default). */\n thresholdDb?: number\n /** Minimum silence length to report, in ms. Default 700. */\n minSilenceMs?: number\n /** Padding kept around speech, in ms — shrinks each reported range inward. Default 120. */\n padMs?: number\n /**\n * Optionally associate this run with a workflow execution (display only — it\n * ties the standalone job back to a workflow for the execution history).\n */\n workflowId?: string\n}\n\n/** One recording for {@link EditResource.audioSync}. */\nexport interface AudioSyncSource {\n /**\n * Your id for this recording (1–200 chars, unique within the request) —\n * echoed back as its offset's `sourceId`. Use the id your EDL gives the same\n * recording (`EdlSource.id`) and the result folds straight into it.\n */\n id: string\n /** An audio OR video URL — the audio track is read either way. */\n url: string\n}\n\nexport interface AudioSyncInput {\n /** The 2–6 recordings of one conversation to line up (camera files and/or a master mic). */\n sources: AudioSyncSource[]\n /**\n * The id of the source every offset is measured against (its own offset is\n * 0). Must be one of `sources`' ids; default: the first source.\n */\n reference?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** One recording's measured clock offset in an {@link AudioSyncResult}. */\nexport interface AudioSyncOffset {\n /** The recording's `id` from the request. */\n sourceId: string\n /**\n * Where this recording sits on the reference's clock, in integer ms:\n * `referenceMs = sourceMs + offsetMs` (the EDL's D19 sign — the value\n * `EdlSource.offsetMs` takes when the reference is the master).\n */\n offsetMs: number\n /** 0–1. Below 0.5 a `notes` line asks for a check by ear. */\n confidence: number\n /**\n * Measured clock drift against the reference, in ms gained per hour; `null`\n * when the shared stretch was too short to measure it. Drift is reported,\n * never corrected — the offset is taken at the middle of the overlap.\n */\n driftMsPerHour: number | null\n}\n\n/**\n * An audio-sync job's `output_data.json`. It is NOT what\n * {@link EditResource.audioSync} returns (that is `{ jobId }`); fetch the\n * finished job and read its `output_data.json`.\n */\nexport interface AudioSyncResult {\n /** Wire version of the payload. */\n version: number\n /** The source every offset is measured against (its own offset is 0). */\n reference: string\n offsets: AudioSyncOffset[]\n /** Human-readable warnings: low confidence, measured drift, no shared sound. */\n notes: string[]\n}\n\nexport interface ApplyEdlInput {\n /** The edit decision list to render. Media resolves from each `edl.sources[i].url`. */\n edl: Edl\n /**\n * Optional media-URL overrides for `edl.sources[i].url`, POSITIONAL in this\n * array's order. Each URL is SSRF-guarded server-side like any media the\n * platform fetches.\n */\n sources?: string[]\n /**\n * Optional transcript to remap through the cut. The rendered job returns the\n * remapped transcript on its `json` output. A word-level multi-hour transcript\n * is several MB — if you only need the re-timed transcript (not a render), use\n * {@link EditResource.remapTranscript} locally instead of sending it here.\n */\n transcript?: Transcript\n /** Render a full video (default) or an audio-only cut. */\n output?: \"video\" | \"audio\"\n /** Fast proxy draft or the final render. Default \"final\". */\n quality?: \"proxy\" | \"final\"\n /**\n * Default crossfade (ms) on boundaries with no explicit transition; 0 = hard\n * cuts. Per-boundary clamped to the ffmpeg-xfade limit server-side. Default 0.\n */\n crossfadeMs?: number\n /**\n * The plan clip this render cuts — `edlSpanKey(clip)` from `@nodaro/shared`\n * (`\"<first inMs>-<last outMs>\"`). The job's result carries it back as\n * `output_data.clipKey`, so renders of a clip set can be matched to their\n * clips. Omit for a render of no plan clip.\n */\n clipKey?: string\n /**\n * The plan value this render cuts — `renderReadBasis(value)` from\n * `@nodaro/shared` (16 lowercase hex digits), where `value` is the Edit\n * Plan's Tighten EDL with any review applied, or the clip this render cuts.\n * The job's result carries it back as `output_data.planBasis`, so a take can\n * be told apart from one cut from an earlier plan. Send it only when the EDL\n * you send IS that value (not one an intermediate step re-cut from an older\n * plan); omit it otherwise. The result's `output_data.renderBasis` (the\n * render's own settings and sources) is stamped by the server.\n */\n planBasis?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * One media source for an edit-plan run. Mirrors the shape the workflow engine\n * sends: a stable `id` (adopted verbatim as the plan's `EdlSource` id), the\n * media `url`, and the `kind`, plus optional per-source annotations the planner\n * honors.\n */\nexport interface EditPlanSource {\n /** Stable id for this source — becomes the `EdlSource` id in the returned plan. */\n id: string\n /** The source's media URL (SSRF-guarded server-side). */\n url: string\n /** This source's medium. */\n kind: \"video\" | \"audio\"\n /** Role annotation. `\"master-audio\"` marks the clock the plan is reserved on. */\n role?: \"master-audio\" | \"camera\" | \"wide\" | \"screen\"\n /** Speaker labels present in this source. */\n speakers?: string[]\n /**\n * This source's origin on the master clock (masterMs = sourceMs + offsetMs).\n * Set by hand, it wins over a measured offset from `offsets`.\n */\n offsetMs?: number\n}\n\nexport interface EditPlanInput {\n /**\n * Planning mode. The finished job's `output_data` carries the plan — read it\n * with {@link unwrapEditPlanOutput}, which also strips the relay's\n * `viaNodaroCloud` marker:\n * - `\"tighten\"` → an `Edl`.\n * - `\"clips\"` → an {@link EdlClipSet} (`{ version, clips: Edl[] }`);\n * `unwrapEditPlanOutput` returns the bare `Edl[]`.\n * - `\"chapters\"` → a {@link ChapterSet} (`{ version, chapters: [...] }`).\n * - `\"trailer\"` → an `Edl` (one short teaser cut from the strongest moments).\n */\n mode: EditPlanMode\n /** Reasoning tier — affects plan quality AND the credit bucket. */\n planTier: EditPlanTier\n /** The timed transcript driving the plan. */\n transcript: Transcript\n /**\n * Optional detected silence ranges. Pass the silence-detect JOB'S RESULT —\n * `getStatus(jobId).data.output_data.json` (a {@link SilenceRanges} object),\n * NOT the `{ jobId }` that {@link EditResource.silenceDetect} returns. The\n * planner reads the `ranges` array; an input without one is silently ignored.\n */\n silence?: SilenceRanges\n /** The recording's media sources (1–6). */\n sources: EditPlanSource[]\n /**\n * Multicam: an {@link AudioSyncResult} (the audio-sync job's\n * `output_data.json`) measured over these same source ids. Each source's\n * measured offset is written onto its `offsetMs` BEFORE the request, on the\n * master's clock (the `master-audio` source, else the first) — a source's\n * own `offsetMs` wins. `editPlan` rejects with a {@link NodaroError}\n * (`code: \"edit_plan_sources\"`), before any request or charge, when a\n * source was not measured or matched weakly (confidence < 0.5 — set its\n * `offsetMs` by hand), or the master was not measured.\n */\n offsets?: AudioSyncResult | string\n /**\n * The id of the source the transcript was made from, when you know it. The\n * plan follows the master's clock, so a transcript made from a source that\n * is off it is rejected the same way.\n */\n transcriptSourceId?: string\n /** Free-text editing steer. */\n instructions?: string\n /** Style-guide text applied to the plan. */\n styleGuide?: string\n /** `\"clips\"` mode only: how many clips to cut. */\n count?: number\n /** `\"clips\"` mode only: target duration per clip, in seconds. */\n targetDurationSec?: number\n /** Target aspect for cut clips (`\"clips\"`) or the teaser (`\"trailer\"`). */\n targetAspect?: \"16:9\" | \"9:16\" | \"1:1\" | \"4:5\"\n /** Target platform hint (e.g. a social platform name). */\n platform?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** Input for {@link EditResource.cameraSwitch} (podcast B5). */\nexport interface CameraSwitchInput {\n /** One EDL — an `editPlan` result (`unwrapEditPlanOutput`), not a clip set. */\n edl: Edl\n /** The word transcript WITH speaker labels (transcribe with speaker detection). */\n transcript: Transcript\n /** Speaker label → the EDL source id of that speaker's camera. A speaker\n * left out shows the source whose `speakers` list names them, else the\n * `wide` camera, else any camera that filmed them. */\n speakerMap?: Record<string, string>\n /** A {@link EditResource.speakerMap} job's `output_data.json`: a label in `speakerMap`\n * wins, then this result, then the n-th speaker heard gets the n-th camera. */\n speakerMapResult?: SpeakerMapResult | string\n /** Speaker label → display name (on the segments and the returned transcript). */\n speakerNames?: Record<string, string>\n /** The shortest shot in ms (default 2 500): a shorter turn holds the shot. */\n minShotMs?: number\n /** Cut this long before the new speaker starts, ms (default 200). */\n leadMs?: number\n /** With a `wide` source: break to it after this long on one camera, ms (default 20 000). */\n maxShotMs?: number\n /** With a `wide` source: every N-th cut goes to the wide (default 0 = off). */\n wideEvery?: number\n /** Overlapping speech → a layout hint (default false; Apply EDL renders cut-only edits). */\n layoutHints?: boolean\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * The podcast use-case node types, each one a route (`POST /v1/<type>`, and\n * `POST /v1/<type>/continue` to continue one of its runs).\n */\nexport { USE_CASE_NODE_TYPES, type UseCaseNodeType }\n\n/** The route that continues a run of `type`. */\nconst continuePathOf = (type: UseCaseNodeType): `/v1/${UseCaseNodeType}/continue` => `/v1/${type}/continue`\n\n/** What a use-case run (`tightenEpisode`, `episodeChapters`, `episodeTrailer`, `clipPack`) and `continueUseCase` answer: the run's job to poll. */\nexport interface UseCaseJobResult extends EditJobResult {\n /** The run's execution (absent on a deduplicated retry). */\n executionId?: string\n /** `true` when an `Idempotency-Key` retry found the run already started: `jobId` is that run. */\n deduped?: boolean\n}\n\n/** What `tightenEpisode` answers (a {@link UseCaseJobResult}). */\nexport type TightenEpisodeJobResult = UseCaseJobResult\n\n/**\n * What a use-case run is estimated at (`data`). Every credit amount is 0\n * on an edition without credits; `durationSec` is `null` when the recording's\n * length could not be read.\n */\nexport interface UseCaseEstimate {\n /** The whole run: its steps plus the fee. */\n totalCredits: number\n /** The steps at their own rates. */\n stepsCredits: number\n /** The per-run fee. */\n feeCredits: number\n /** The recording's length, in seconds. */\n durationSec: number | null\n}\n\n/** What a Tighten Episode run is estimated at (a {@link UseCaseEstimate}). */\nexport type TightenEpisodeEstimate = UseCaseEstimate\n\n/** Input for {@link EditResource.continueUseCase}. */\nexport interface UseCaseContinueInput {\n /** The run to continue: the job `tightenEpisode`, `episodeChapters`, `episodeTrailer` or `clipPack` (or an earlier continuation) answered. */\n jobId: string\n /**\n * `\"from-failed\"` re-runs what did not finish of a failed run;\n * `\"render-final\"` renders the full-quality result after a review-on preview\n * (a Chapters run has no preview: it is refused `render_final_not_preview`;\n * a Clip Pack edit that drops every clip is refused `use_case_nothing_kept`).\n */\n mode: \"from-failed\" | \"render-final\"\n /** Render final only: the reviewer's edit of the plan (an EDL). */\n editedEdl?: unknown\n /**\n * The run's node type: its continuation goes to `POST /v1/<nodeType>/continue`.\n * Optional: when absent the job is read first (`GET /v1/jobs/:id`, which\n * needs the `jobs:read` scope on an app token) for the type the run records.\n * Required when that read cannot answer: a key without `jobs:read`, or a job\n * that records no use-case type (the call rejects with `use_case_type_unknown`).\n */\n nodeType?: UseCaseNodeType\n}\n\n/** Per-call options for the use-case requests (Tighten Episode, Chapters, Trailer, Clip Pack, and their continuations). */\nexport interface UseCaseRequestOptions {\n /**\n * Sent as the `Idempotency-Key` header. Reuse the same value when retrying\n * the same request and the server answers the run it already started\n * (`deduped: true`) instead of starting a second one.\n */\n idempotencyKey?: string\n}\n\nfunction idempotencyHeaders(options?: UseCaseRequestOptions): { headers?: Record<string, string> } {\n return options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}\n}\n\n/**\n * Editorial primitives for podcast / long-form video editing, single-camera\n * and multicam: measure the cameras' offsets ({@link audioSync}), plan the cut\n * ({@link editPlan}), put each cut on the speaker's camera\n * ({@link cameraSwitch}), render a Preview to review (`applyEdl` with\n * `quality: \"proxy\"`), then the final (`quality: \"final\"`).\n *\n * - {@link silenceDetect}, {@link audioSync} and {@link applyEdl} are core nodes\n * available on every edition.\n * - {@link editPlan} and {@link cameraSwitch} run on nodaro.ai; a self-hosted\n * install relays them once it is connected (a 503\n * `code: \"nodaro_connection_required\"` otherwise).\n * - {@link tightenEpisode} runs the whole chain (transcribe, plan, render, and\n * optionally captions, music and framing) in one call. It is core, but a\n * self-hosted install needs a nodaro.ai connection for the steps that run\n * there (a 503 `code: \"nodaro_connection_required\"` otherwise).\n * {@link continueUseCase} continues one of its runs.\n * - {@link episodeChapters} lists a recording's chapters (transcribe, then the\n * plan in chapters mode) in one call; nothing renders. Like\n * {@link tightenEpisode}, a self-hosted install needs a nodaro.ai connection.\n * - {@link episodeTrailer} cuts a 20–40 s trailer of a recording and delivers\n * it in each chosen format in one call; the same connection applies.\n * - {@link clipPack} cuts a recording into a pack of short clips (framed and\n * captioned by default) in one call; the same connection applies.\n * - {@link remapTranscript} is a PURE local transform (no request) — the same\n * remap `applyEdl` performs on its `transcript`, exposed for callers that hold\n * an EDL and a transcript and only want the re-timed transcript.\n *\n * Every request method except the estimates ({@link estimateTightenEpisode},\n * {@link estimateEpisodeChapters}, {@link estimateEpisodeTrailer},\n * {@link estimateClipPack}: a quote, `{ data }`) returns\n * {@link EditJobResult} (the use-case runs a {@link UseCaseJobResult}); poll the\n * `jobId` with `client.jobs.getStatus(jobId)`.\n */\nexport class EditResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * Detect silence ranges in an audio/video source (`POST /v1/silence-detect`).\n * Keyless — one ffmpeg pass over the source's audio proxy.\n */\n silenceDetect(input: SilenceDetectInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/silence-detect\", {\n body: {\n audioUrl: input.audioUrl,\n ...(input.thresholdDb !== undefined ? { thresholdDb: input.thresholdDb } : {}),\n ...(input.minSilenceMs !== undefined ? { minSilenceMs: input.minSilenceMs } : {}),\n ...(input.padMs !== undefined ? { padMs: input.padMs } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Measure how far apart the clocks of 2–6 recordings of one conversation are\n * (`POST /v1/audio-sync`), by cross-correlating their audio — keyless, one\n * ffmpeg decode per source. The finished job's `output_data.json` is an\n * {@link AudioSyncResult}. A malformed request (fewer than 2 or more than 6\n * sources, a repeated id, a `reference` that is not one of the ids) throws a\n * typed `NodaroError` (400, `code: \"validation_error\"`) before any credits\n * are reserved.\n */\n audioSync(input: AudioSyncInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/audio-sync\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url })),\n ...(input.reference !== undefined ? { reference: input.reference } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Which camera each speaker is on, from the cameras' own microphones (`POST\n * /v1/speaker-map`), keyless. `output_data.json` is a `SpeakerMapResult`: pass\n * it to {@link cameraSwitch} as `speakerMapResult`. A malformed request throws\n * a `NodaroError` (400 `validation_error`; 422 `no_speakers` for a transcript\n * with no speaker labels) before any credits are reserved.\n */\n speakerMap(input: SpeakerMapInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/speaker-map\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url, ...(s.kind ? { kind: s.kind } : {}) })),\n transcript: input.transcript,\n ...(input.offsets !== undefined ? { offsets: input.offsets } : {}),\n ...(input.masterSourceId !== undefined ? { masterSourceId: input.masterSourceId } : {}),\n ...(input.wideSourceId !== undefined ? { wideSourceId: input.wideSourceId } : {}),\n ...(input.marginDb !== undefined ? { marginDb: input.marginDb } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Render an edit decision list into a video or audio cut (`POST /v1/apply-edl`).\n * The EDL is validated at ingress — an unresolvable source or a picture-less\n * segment on a video edit throws a typed `NodaroError` (400, `code:\n * \"invalid_edl\"`) before any credits are reserved.\n */\n applyEdl(input: ApplyEdlInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/apply-edl\", {\n body: {\n edl: input.edl,\n ...(input.sources !== undefined ? { sources: input.sources } : {}),\n ...(input.transcript !== undefined ? { transcript: input.transcript } : {}),\n ...(input.output !== undefined ? { output: input.output } : {}),\n ...(input.quality !== undefined ? { quality: input.quality } : {}),\n ...(input.crossfadeMs !== undefined ? { crossfadeMs: input.crossfadeMs } : {}),\n ...(input.clipKey !== undefined ? { clipKey: input.clipKey } : {}),\n ...(input.planBasis !== undefined ? { planBasis: input.planBasis } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Tighten Episode (`POST /v1/tighten-episode`): tighten a podcast recording\n * in one call — transcribe, cut the silences and filler, render, with\n * optional captions, music and framing. Answers the run's job to poll; its\n * `output_data` holds the episode. The run is priced as its steps at their own\n * rates plus the listed per-run fee. Quote it first with\n * {@link estimateTightenEpisode}.\n */\n tightenEpisode(input: TightenEpisodeInput, options?: UseCaseRequestOptions): Promise<TightenEpisodeJobResult> {\n return this.client.request(\"POST\", \"/v1/tighten-episode\", { body: tightenBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Tighten Episode run is estimated at (`POST /v1/tighten-episode/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The estimate leans high: the render is priced at the recording's\n * full length, while the charge follows the minutes actually rendered.\n */\n estimateTightenEpisode(input: TightenEpisodeInput): Promise<{ data: TightenEpisodeEstimate }> {\n return this.client.request(\"POST\", \"/v1/tighten-episode/estimate\", { body: tightenBody(input) })\n }\n\n /**\n * Chapters (`POST /v1/episode-chapters`): transcribe a recording and list its\n * chapters in one call. Answers the run's job to poll; the finished job's\n * `output_data.text` is the list as show notes (one `0:00 Title` line per\n * chapter; once a chapter starts at 1:00:00 or later, every line is\n * `H:MM:SS`), `output_data.json` the list itself (`{ startMs, title }[]`, the\n * same rows; the first starts at 0). A run that finds no chapters fails,\n * and its fee is refunded. Priced as its steps at their own rates plus the\n * listed per-run fee; quote it first with {@link estimateEpisodeChapters}.\n */\n episodeChapters(input: EpisodeChaptersInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return this.client.request(\"POST\", \"/v1/episode-chapters\", { body: chaptersBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Chapters run is estimated at (`POST /v1/episode-chapters/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. A recording whose length cannot be read is estimated at the\n * 180-minute ceiling.\n */\n estimateEpisodeChapters(input: EpisodeChaptersInput): Promise<{ data: UseCaseEstimate }> {\n return this.client.request(\"POST\", \"/v1/episode-chapters/estimate\", { body: chaptersBody(input) })\n }\n\n /**\n * Trailer (`POST /v1/episode-trailer`): cut a 20–40 s trailer of a recording\n * and deliver it in each chosen format (padded, never cropped), optionally\n * after an intro card, in one call. The finished job's `output_data.formats`\n * is `{ format, url }[]` in format order, the first also `output_data.videoUrl`;\n * a format that failed is named in `output_data.failedFormats` (the run keeps\n * the others). With `reviewBeforeRender` the run stops at a preview\n * (`videoUrl`, no formats yet). Priced as its steps at their own rates plus\n * the listed per-run fee; quote it first with {@link estimateEpisodeTrailer}.\n */\n episodeTrailer(input: EpisodeTrailerInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return this.client.request(\"POST\", \"/v1/episode-trailer\", { body: trailerBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Trailer run is estimated at (`POST /v1/episode-trailer/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The trailer is quoted at 40 seconds, the longest trailer the\n * plan cuts; a Render final is quoted on the trailer the run cut (with the\n * review's edits), plus the intro card.\n */\n estimateEpisodeTrailer(input: EpisodeTrailerInput): Promise<{ data: UseCaseEstimate }> {\n return this.client.request(\"POST\", \"/v1/episode-trailer/estimate\", { body: trailerBody(input) })\n }\n\n /**\n * Clip Pack (`POST /v1/clip-pack`): cut a recording into a pack of short\n * clips in one call — the plan finds the best moments (`count`, default 8,\n * of about `targetDurationSec`, default 60), each is rendered (framed 9:16\n * with the speakers stacked by default; `framing: null` keeps the\n * recording's shape) and captioned. An audio recording makes audio clips.\n * The finished job's `output_data.clips` is `{ url, title?, hook?, clipKey,\n * thumbnailUrl?, captionsFailed? }[]` in plan order, the first also\n * `output_data.videoUrl` (`audioUrl` for audio clips). Every clip is tried:\n * one that cannot be rendered is skipped, not charged and counted on\n * `failedClips` (the pack keeps the others and its fee); a clip whose\n * captions failed comes uncaptioned (`captionsFailed`, counted on\n * `failedCaptions`). When no clip renders the run fails and its fee is\n * refunded. With `reviewBeforeRender` the run stops at previews; Render\n * final ({@link continueUseCase}) renders only the clips the edit keeps.\n * Priced as its steps at their own rates plus the listed per-run fee;\n * quote it first with {@link estimateClipPack}.\n */\n clipPack(input: ClipPackInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return this.client.request(\"POST\", \"/v1/clip-pack\", { body: clipPackBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Clip Pack run is estimated at (`POST /v1/clip-pack/estimate`): its\n * steps at their own rates plus its per-run fee. Nothing is created or\n * charged. Each clip is quoted at twice the target length (the plan may cut\n * a clip longer than asked); the charge follows each clip's real length.\n */\n estimateClipPack(input: ClipPackInput): Promise<{ data: UseCaseEstimate }> {\n return this.client.request(\"POST\", \"/v1/clip-pack/estimate\", { body: clipPackBody(input) })\n }\n\n /**\n * Continue a use-case run as a new job (`POST /v1/<type>/continue`, the\n * run's own type: `input.nodeType`, else the type the job records, read with\n * `GET /v1/jobs/:id`; when that read cannot tell, this rejects with a\n * `NodaroError` coded `use_case_type_unknown` — pass `nodeType` then):\n * `\"from-failed\"` keeps the steps that finished and runs the rest (the fee\n * is held again; the failed run's was refunded);\n * `\"render-final\"` renders the full-quality result after a review-on preview,\n * with the preview's settings and no fee. A refusal that names another job\n * (`already_running`, `continuation_superseded`,\n * `render_final_already_rendered`) throws a `ContinuationRefusedError`\n * carrying that `jobId`.\n */\n async continueUseCase(input: UseCaseContinueInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n if (input.nodeType !== undefined && !isUseCaseNodeType(input.nodeType)) {\n throw new NodaroError(\n `continueUseCase: nodeType \"${String(input.nodeType)}\" is not a use-case node type (${USE_CASE_NODE_TYPES.join(\", \")})`,\n \"invalid_node_type\",\n 400,\n )\n }\n const type = input.nodeType ?? (await this.runTypeOf(input.jobId))\n return this.client.request(\"POST\", continuePathOf(type), {\n body: { jobId: input.jobId, mode: input.mode, ...(input.editedEdl !== undefined ? { editedEdl: input.editedEdl } : {}) },\n ...idempotencyHeaders(options),\n })\n }\n\n /**\n * The use-case type a run records (`input_data.type`, else its `job_type`).\n * When the key lacks the `jobs:read` scope (403 naming that scope) or the job\n * names no use-case type, the type cannot be read: this rejects with a\n * `NodaroError` (`use_case_type_unknown`, 400) asking for `nodeType`, and\n * nothing is posted (decided 2026-10-18: no route is guessed for the run).\n * Any other failure of the read, any other 403 included, is thrown as is.\n */\n private async runTypeOf(jobId: string): Promise<UseCaseNodeType> {\n const unknown = () =>\n new NodaroError(\"continueUseCase: the run's node type cannot be read — pass nodeType\", \"use_case_type_unknown\", 400)\n let row: { job_type?: unknown; input_data?: unknown } | undefined\n try {\n row = (await this.client.request<{ data?: { job_type?: unknown; input_data?: unknown } }>(\"GET\", `/v1/jobs/${encodeURIComponent(jobId)}`)).data\n } catch (err) {\n if (err instanceof ForbiddenError && err.missingScope === \"jobs:read\") throw unknown()\n throw err\n }\n const input = row?.input_data\n const recorded = input && typeof input === \"object\" && !Array.isArray(input) ? (input as { type?: unknown }).type : undefined\n const type = typeof recorded === \"string\" && recorded.length > 0 ? recorded : row?.job_type\n if (!isUseCaseNodeType(type)) throw unknown()\n return type\n }\n\n /**\n * Plan a transcript-driven cut (`POST /v1/edit-plan`). Reads a timed\n * transcript (plus optional silence ranges) and the media sources, and plans\n * the edit. The finished job's `output_data` holds the plan: an `Edl`\n * (`\"tighten\"` or `\"trailer\"`), an {@link EdlClipSet} (`\"clips\"`), or a\n * {@link ChapterSet} (`\"chapters\"`) — normalize it with {@link unwrapEditPlanOutput}.\n *\n * On a self-hosted install the request relays to nodaro.ai and needs the\n * install connected (a 503 `code: \"nodaro_connection_required\"` otherwise);\n * on nodaro.ai it runs directly.\n */\n editPlan(input: EditPlanInput): Promise<EditJobResult> {\n // The same pre-dispatch check the canvas and MCP run (B4): the server never\n // sees `offsets` — they are written onto the sources here.\n const planned = resolveEditPlanSources(input.sources, {\n offsets: input.offsets,\n transcriptSourceId: input.transcriptSourceId,\n })\n if (!planned.ok) {\n const message = planned.issues.map((issue) => describeAudioSyncOffsetIssue(issue)).join(\"; \")\n return Promise.reject(new NodaroError(`editPlan: ${message}`, \"edit_plan_sources\", 400))\n }\n const transcript = planned.transcriptSourceId\n ? { ...input.transcript, sourceId: planned.transcriptSourceId }\n : input.transcript\n return this.client.request(\"POST\", \"/v1/edit-plan\", {\n body: {\n mode: input.mode,\n planTier: input.planTier,\n transcript,\n sources: planned.sources,\n ...(input.silence !== undefined ? { silence: input.silence } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n ...(input.count !== undefined ? { count: input.count } : {}),\n ...(input.targetDurationSec !== undefined ? { targetDurationSec: input.targetDurationSec } : {}),\n ...(input.targetAspect !== undefined ? { targetAspect: input.targetAspect } : {}),\n ...(input.platform !== undefined ? { platform: input.platform } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Camera Switch (podcast B5): choose which camera shows each cut of `edl`\n * by who is speaking — the sound never changes. Cloud edition; a flat\n * price. The finished job's `output_data.json` is the switched EDL (feed it\n * to {@link applyEdl}) and `output_data.transcript` the transcript with\n * `speakerNames` applied. A transcript with no speaker labels rejects with\n * a {@link NodaroError} (`code: \"no_speakers\"`) before any request or charge.\n */\n cameraSwitch(input: CameraSwitchInput): Promise<EditJobResult> {\n if (transcriptSpeakerLabels(input.transcript).length === 0) {\n return Promise.reject(new NodaroError(\n \"cameraSwitch: the transcript has no speaker labels — transcribe with speaker detection on\",\n \"no_speakers\",\n 422,\n ))\n }\n const result = input.speakerMapResult\n if (result !== undefined && !speakerMapResultOf(result)) return Promise.reject(new NodaroError(\"cameraSwitch: speakerMapResult is not a Speaker Map result — pass that job's output_data.json\", \"not_a_speaker_map\", 400))\n const speakerMap = result !== undefined ? cameraSwitchSpeakerMap({ speakerMap: input.speakerMap }, input.edl, input.transcript, { speakerMap: result }) : input.speakerMap\n return this.client.request(\"POST\", \"/v1/camera-switch\", {\n body: {\n edl: input.edl,\n transcript: input.transcript,\n ...(speakerMap !== undefined ? { speakerMap } : {}),\n ...(input.speakerNames !== undefined ? { speakerNames: input.speakerNames } : {}),\n ...(input.minShotMs !== undefined ? { minShotMs: input.minShotMs } : {}),\n ...(input.leadMs !== undefined ? { leadMs: input.leadMs } : {}),\n ...(input.maxShotMs !== undefined ? { maxShotMs: input.maxShotMs } : {}),\n ...(input.wideEvery !== undefined ? { wideEvery: input.wideEvery } : {}),\n ...(input.layoutHints !== undefined ? { layoutHints: input.layoutHints } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Remap a transcript through an EDL — a PURE client-side transform (NO\n * request). Returns a new transcript whose word (and segment) timings are on\n * the EDL's rendered output clock, dropping words that fall in cut regions and\n * clipping straddlers. Runs `@nodaro/shared`'s canonical\n * `remapTranscriptThroughEdl`, the same remap `applyEdl` performs server-side.\n */\n remapTranscript(edl: Edl, transcript: Transcript): Transcript {\n return remapTranscriptThroughEdl(edl, transcript)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/resources/nodes.ts"],"names":[],"mappings":"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","file":"chunk-QITJGSFA.js","sourcesContent":["import type { GenerateScene3DParams, EditScene3DParams, RenderScene3DParams, Scene3DJobOutput, Pro3DRenderRunParams, Pro3DRenderJobOutput } from \"./scene3d-types.js\"\nimport type { ImageToImageParams, EditImageParams, SplitImageParams, SplitImageResult, GenerateMaskParams, GenerateMaskJobOutput, ImageJobStarted } from \"./nodes-image-params.js\"\nimport type { NodaroClientCore } from \"../core.js\"\nimport type { JobStatusResult, JobsResource } from \"./jobs.js\"\nimport { JobAbortedError, JobFailedError, JobHeldError, JobTimeoutError, NodaroError, NotFoundError } from \"../errors.js\"\nimport { NODE_RUN_ROUTES, NODE_TYPES_WITHOUT_RUN_ROUTE, nodeRunRoute } from \"@nodaro/shared/nodes\"\nimport {\n SOCIAL_POST_NODE_PLATFORM,\n type SocialPost,\n type SocialPlatform,\n type SocialSearchMode,\n type SocialSearchParams\n} from \"@nodaro/shared/social\"\nimport type { ConnectedReference, DescribedReference } from \"@nodaro/shared/types\"\nimport type { VideoCharacterReference } from \"@nodaro/shared/video\"\nimport type { ModelInputAdjustment } from \"@nodaro/shared/model-catalog\"\nimport type { VideoDepthProvider, VideoDepthModelSize, VideoDepthColormap, VideoDepthResolution } from \"@nodaro/shared/media\"\nimport type { DirectionFields, SubjectFields } from \"@nodaro/prompts/prompt-builder\"\n\nexport type NodeCategory =\n | \"input\"\n | \"parameter\"\n | \"ai-image\"\n | \"ai-video\"\n | \"ai-audio\"\n | \"ai-text\"\n | \"processing\"\n | \"composition\"\n | \"trigger\"\n | \"output\"\n | \"control\"\n | \"entity\"\n | \"utility\"\n\nexport type OutputType = \"text\" | \"image\" | \"video\" | \"audio\" | \"data\" | \"none\"\n\n/**\n * Field shape inside a node's `inputSchema.fields[]`. Mirrors\n * `backend/src/lib/node-registry.ts`.\n */\nexport interface NodeInputField {\n key: string\n type: string\n required?: boolean\n options?: string[]\n}\n\nexport interface NodeInputSchema {\n fields: NodeInputField[]\n}\n\n/**\n * A per-unit price on a node descriptor. A run of N units is charged\n * `ceil(N × creditsPerUnit)`, rounded up once — so `GET /v1/credits/model-cost`\n * for `identifier` (one unit, rounded up: the least a run pays) times N can come\n * to more. `<identifier>:<N>u` is the exact price of N units; for a whole run\n * use `POST /v1/credits/estimate-workflow`.\n */\nexport interface NodeCreditRate {\n /** The price id the rate is read from. */\n identifier: string\n /** What one unit is, as a token (`\"1000-frames\"`: one started block of 1,000 sampled frames). */\n unit: string\n /** The credits one unit adds to a run's total — exact, not rounded. */\n creditsPerUnit: number\n /** The fewest units a run is charged for. */\n minUnits: number\n /** `\"ceil-run-total\"`: the run's total is rounded up to a whole credit once, never per unit. */\n rounding: \"ceil-run-total\"\n /** The rule in words. */\n note: string\n}\n\n/**\n * Node descriptor returned by `GET /v1/nodes` and `GET /v1/nodes/:type`.\n * Mirrors `backend/src/lib/node-registry.ts#NodeDescriptor`.\n */\nexport interface NodeDescriptor {\n type: string\n label: string\n category: NodeCategory\n description: string\n outputType: OutputType\n /** The credits a run is charged, the figure on the node's Run button.\n * Number when fixed, string range like \"10-80\" when model-dependent.\n * Undefined when the node is free — or on editions\n * without a credit system (community/business omit the field entirely;\n * it is meaningful on Cloud only). */\n creditCost?: number | string\n /** A node charged per unit of its own measure (its `creditCost` is a word\n * such as `\"per-1000-frames\"`): the charged price of one unit and how a run\n * rounds. Cloud only, like `creditCost`. */\n creditRate?: NodeCreditRate\n /** Input fields the node exposes for user override (subset of full config). */\n inputSchema?: NodeInputSchema\n /** For AI nodes: list of provider IDs supported. */\n providers?: string[]\n /** Capability flags such as \"supports-reference-image\" or \"supports-end-frame\". */\n capabilities?: string[]\n /** Hard duration ceiling in seconds (e.g. Generate Video Pro's env-configured cap). */\n maxDurationSec?: number\n /** Providers whose segment-duration menu is sparse (off-menu values snap to the nearest entry). */\n sparseProviders?: string[]\n /** Per-provider resolution tiers, e.g. `{ \"minimax-h3\": [\"2K\", \"768P\"] }`. */\n providerResolutions?: Record<string, string[]>\n /** Per-provider map of display resolution → the literal wire value the API expects. */\n providerResolutionWire?: Record<string, Record<string, string>>\n /** Generate Video Pro: the deployed engine accepts the `soundtrack` input (original-audio replay). */\n soundtrack?: boolean\n}\n\n/**\n * Result of a direct node execution. Most node types return `{ jobId }` and\n * are processed asynchronously by a worker — the caller polls\n * `client.jobs.get(jobId)` until status is `completed`/`failed`.\n *\n * Some node types (`meta-ads-scrape`, for one) answer synchronously and\n * return their full result body. The shape is route-specific; consumers\n * should branch on the presence of `jobId`.\n */\n/**\n * One parameter the server corrected because the chosen model does not accept\n * the value you sent (e.g. `gpt-image-2` has no 3:2 aspect ratio). The run\n * proceeds with the corrected value rather than failing — this is how you find\n * out what it actually used.\n *\n * Re-exports `@nodaro/shared`'s `ModelInputAdjustment` — the same shape the\n * server's `normalizeModelInput` / `resolveNormalizedImageGen` populate — under\n * the SDK's naming convention, rather than declaring a second copy of the\n * fields.\n */\nexport type RunNodeAdjustment = ModelInputAdjustment\n\n/**\n * WHO is in the shot, as picker-catalog option ids — the `subject` field of\n * `generate-image` (and `text-to-video`). The platform writes the wording from\n * the ids you send; you never write prompt text for the subject. It composes\n * with `prompt`: the subject clauses are folded in right after your prompt and\n * ahead of any `direction`.\n *\n * A flat map of field → id(s):\n * - **Keys** are the platform's own picker field names — every Person field\n * (`type`, `age`, `ethnicity`, `hairBase`, `lipState`, …), every Styling\n * field (`makeup`, `jewelry`, `outerwear`, …), and the prop keys `heldProp`,\n * `material` and `animal`. They and their ids come from the picker catalogs:\n * `pickerCatalogs.get(\"person\")` / `get(\"styling\")` list each dimension's\n * `field` with its `options[].id`; `get(\"held-prop\")`, `get(\"material\")` and\n * `get(\"animal\")` write their `options[].id` to the catalog's `valueField`.\n * - **Values** are one id or a list of ids. Ids past a dimension's own cap are\n * dropped, not rejected.\n * - **One number:** `customAge` (years, rounded and clamped to 0–120), read only\n * when `age` is `\"age-custom\"`. A number on any other key does nothing.\n * - **Bounds** (without `presetId`): at most 128 keys, 64 characters per key,\n * 8 ids per key and 100 characters per id — a map over any of them is a 400\n * `validation_error`. Unknown keys and unknown ids within them are skipped,\n * and the job's `input_data.subject` records the ids that were used.\n * - **With `presetId`** your `subject` replaces the preset's own and is folded\n * into the preset's prompt, as `direction` and `structured` are: an over-long\n * key, id or list is dropped rather than rejected, and `input_data` does not\n * record the subject.\n *\n * Same type as `@nodaro/prompts`' `SubjectFields`, under the SDK's name.\n */\nexport type Subject = SubjectFields\n\n/**\n * Something you asked for that the run did not send, with why — e.g.\n * `moodboard_images_not_sent`: some of a `moodboard:<id>` direction's images\n * did not fit the model's reference limit after your own references. The run\n * goes ahead; the same warnings are kept on the job's `input_data.warnings`.\n */\nexport interface RunNodeWarning {\n code: string\n message: string\n}\n\nexport type RunNodeResult =\n | { jobId: string; usageLogId?: string; adjustments?: RunNodeAdjustment[]; warnings?: RunNodeWarning[]; [k: string]: unknown }\n | Record<string, unknown>\n\n/**\n * Structured references — the editor's wired-reference shape — shared by\n * `generate-image` and `generate-video`. The route assembles them server-side\n * into per-ref `@image_N` directives and resolves `{image:N}` prompt tokens, so\n * a direct SDK run binds inline references exactly like the canvas. Pass the\n * same `ConnectedReference[]` the editor builds; the route dedupes + caps them\n * to the provider's image-reference limit.\n */\nexport interface StructuredReferenceParams {\n /** Wired references assembled server-side (deduped + capped per provider). */\n connectedReferences?: ConnectedReference[]\n /**\n * References you can NAME and DESCRIBE but have no media for — a cast role no\n * entity is bound to yet, a character that exists only in the script. They\n * carry no url, so nothing is attached and no `@image_N` seat is used: each\n * becomes a `<Name> — <description>.` line, and a name left in the prompt\n * prose reaches the model as a real, described subject. Up to 10.\n */\n describedReferences?: DescribedReference[]\n /** Reorder the assembled reference list by stable ref ids; renumbers `@image_N`. */\n referenceOrder?: string[]\n}\n\n/**\n * Captions for a node's video / audio rail references — INDEX-ALIGNED with\n * `referenceVideoUrls` / `referenceAudioUrls`, so `referenceVideoCaptions[0]`\n * describes `referenceVideoUrls[0]`. Each renders as `@video_N: <caption>.` /\n * `@audio_N: <caption>.` in the assembled prompt and is bounded by the number of\n * rail references that actually ship, so a caption can never bind a slot the\n * payload dropped. A blank entry is a hole in the alignment, not a line.\n */\nexport interface ReferenceCaptionParams {\n referenceVideoCaptions?: string[]\n referenceAudioCaptions?: string[]\n}\n\n/**\n * Typed request body for `nodes.run(\"text-to-speech\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through.\n */\nexport interface TextToSpeechParams extends GenerationSessionParams {\n text: string\n /** A premade voice NAME (Rachel, George, …) or a library / custom voice id. */\n voice?: string\n voiceType?: \"premade\" | \"custom\" | \"library\"\n /** `elevenlabs-v4` (the default), `elevenlabs-v3`, `elevenlabs-v4-turbo`, `elevenlabs-turbo`, `elevenlabs-multilingual`. */\n provider?: string\n stability?: number\n similarityBoost?: number\n style?: number\n speed?: number\n languageCode?: string\n /**\n * Continuity across clips: the line spoken just BEFORE this one in the finished piece\n * (the previous clip's text). Context, not spoken — a model that stitches (v4, Turbo, Multilingual; not v3) carries the\n * intonation across the cut; others ignore it. Up to 1,000 characters (a 400 above, on a model that stitches).\n */\n previousText?: string\n /** The line spoken just AFTER this one. Same rule as `previousText`. */\n nextText?: string\n [k: string]: unknown\n}\n\n/**\n * Fields every job-creating request accepts beside its own params, on any\n * route: the platform stamps them on the job it creates. Typed on every\n * generation params type.\n */\nexport interface GenerationSessionParams {\n /** A session from `client.sessions.create()` — the job is born in it and\n * shows in its feed. Must be one of yours (404 otherwise). */\n sessionId?: string\n /** Your app's slug (`create-image`, …): stored on the job, and what\n * `jobs.list({ origin })` filters on. Lowercase letters, digits, dashes. */\n origin?: string\n}\n\n/**\n * Typed request body for `nodes.run(\"generate-image\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through via the index\n * signature (the route Zod-validates the full body).\n *\n * `count` above 1 or `models` makes several jobs in one call: the answer is a\n * {@link GenerateImageBatchResult}, and {@link NodesResource.runBatchAndWait}\n * (not `runAndWait`) waits for it.\n */\nexport interface GenerateImageParams extends StructuredReferenceParams, GenerationSessionParams {\n prompt?: string\n /**\n * Apply a node preset (a factory id like `\"generate-image/cinematic-still\"`, from\n * `presets.listFactory(\"generate-image\")`, or one of yours) exactly as the editor does:\n * its prompt and pre/post text, placeholder defaults, style, negative prompt and\n * levers. Fields you pass override it; your `prompt` is wrapped in its pre/post text.\n */\n presetId?: string\n /** Values for the preset's placeholders that have no default, e.g. `{ BRAND: \"Acme\" }`.\n * Without them such a preset answers 400 `preset_variables_required`, naming them. */\n presetVariables?: Record<string, string>\n /** The prompt as your user typed it, when `prompt` is a version your app\n * expanded (filled-in fields, resolved references). Stored on the job as\n * `input_data.userPrompt` to show what the user wrote; never sent to the\n * model. Defaults to `prompt`. */\n userPrompt?: string\n /** Pre & post text wrapped around `prompt` (a preset's words), joined the\n * same way every AI prompt node joins them. */\n promptPrefix?: string\n promptSuffix?: string\n provider?: string\n /** How many images to make, 1–4 — each its own job, one image each, under\n * its own Idempotency-Key (`<key>:<i>`). With `models`, per model. */\n count?: number\n /** 2–4 distinct image model ids: the same request runs on each, instead of\n * `provider`. models × count is at most 8. With `presetId`, the preset is\n * applied once per model. */\n models?: string[]\n /** With `count`, image i gets `seed + i` — one seed would draw the same image every time. */\n seed?: number\n /** Flat reference image URLs — appended after `connectedReferences`. */\n referenceImageUrls?: string[]\n negativePrompt?: string\n /**\n * Cinematic direction as catalog IDS — the route renders the wording. Two\n * keys also take one of YOUR saved resources:\n * - `colorLook: \"palette:<id>\"` (`paletteDirectionId(id)`) — the palette's\n * colours, each by an everyday name and its hex, go into the prompt;\n * - `style: \"moodboard:<id>\"` (`moodboardDirectionId(id)`) — the moodboard's\n * description goes into the prompt and its images go to the model as style\n * references, after your own references and up to the model's reference\n * limit (the answer's `warnings` says when some did not fit). Its images\n * are references, so on models priced per reference the run costs what is\n * sent.\n * A malformed one answers 400; one that is not yours (or was deleted) 404\n * `direction_not_found`. The job keeps the id, so a re-run resolves it again.\n * Not combinable with `presetId` yet (400 `validation_error`).\n */\n direction?: DirectionFields\n /**\n * WHO is in the shot — person, styling and props as picker-catalog ids\n * ({@link Subject}). The platform writes the wording and folds it in after\n * `prompt`, ahead of `direction`. With `presetId`, it replaces the preset's own\n * and is folded into the preset's prompt (over-long values dropped, not 400).\n */\n subject?: Subject\n /**\n * The image to edit. Alone, the run is image-to-image on it (a text-to-image\n * model runs as its image-to-image sibling); with `maskUrl`, an inpaint.\n */\n baseImageUrl?: string\n /**\n * Inpaint mask — white = the area to change, black = keep (what\n * `generate-mask` writes). Needs a base: `baseImageUrl`, else the first\n * `referenceImageUrls` entry. Works on EVERY image model: the result's masked\n * area is composited back over the base, so everything outside the mask is\n * the base's own pixels. On the models whose `IMAGE_MASK_MODE` is `\"prompt\"`\n * (`@nodaro/shared`) the masked area's position is also described in the\n * prompt; the rest rely on the composite alone. (A model that takes the mask\n * itself is on `image-to-image` — `I2I_MASK_SUPPORT`.)\n */\n maskUrl?: string\n /** 0–1: how far the result may move from the base (taken by the models in `I2I_STRENGTH_SUPPORT`). */\n strength?: number\n /** 0–20: prompt adherence (taken by the models in `GUIDANCE_SCALE_SUPPORT`). */\n guidanceScale?: number\n [k: string]: unknown\n}\n\n/** One model's share of a generate-image batch. */\nexport interface GenerateImageBatchGroup {\n /** The model id as requested. */\n model: string\n jobIds: string[]\n /** Credits held for this model's images; absent where nothing is charged. */\n credits?: number\n /** Parameters corrected for this model, as on a single call. */\n adjustments?: RunNodeAdjustment[]\n /** Something asked for that was not sent on this model (e.g. a moodboard past its reference limit), as on a single call. */\n warnings?: RunNodeWarning[]\n}\n\n/**\n * What `nodes.run(\"generate-image\", …)` answers when `count` is above 1 or\n * `models` is set: every job, grouped by model. The whole batch was checked\n * against the balance and created, charged and queued together — or not at\n * all (`InsufficientCreditsError`'s `required` is then the batch's total).\n */\nexport interface GenerateImageBatchResult {\n /** The first job — so code reading the single-image answer keeps working. */\n jobId: string\n /** Every job: model by model, then image by image. */\n jobIds: string[]\n /** On every job of the batch as `input_data.batchId`; `jobs.list({ batchId })` lists them. */\n batchId: string\n groups: GenerateImageBatchGroup[]\n /** The groups' credits summed; absent where nothing is charged. */\n totalCredits?: number\n /** Every job already existed and this request charged nothing: a repeat of an earlier request with the same Idempotency-Key. */\n deduped?: true\n}\n\n/**\n * One job of a batch once {@link NodesResource.runBatchAndWait} stops waiting\n * for it: its output, or the typed error that ended the wait — a\n * `JobFailedError` (failed or cancelled), `JobHeldError` (held for review) or\n * `JobTimeoutError`. One job's error never discards the others' images.\n */\nexport type BatchJobOutcome =\n | { readonly jobId: string; readonly model: string; readonly status: \"completed\"; readonly output: NodeJobOutput }\n | { readonly jobId: string; readonly model: string; readonly status: \"error\"; readonly error: NodaroError }\n\n/** What {@link NodesResource.runBatchAndWait} resolves. */\nexport interface GenerateImageBatchOutput {\n /** The submit's answer: the ids, the groups, the credits. */\n readonly batch: GenerateImageBatchResult\n /** Every job, in `batch.jobIds` order. */\n readonly results: readonly BatchJobOutcome[]\n}\n\n/**\n * Typed request body for `nodes.run(\"generate-video\", …)` / `runAndWait`.\n * Common fields are typed; any other route field passes through.\n */\nexport interface GenerateVideoParams extends StructuredReferenceParams, ReferenceCaptionParams, GenerationSessionParams {\n prompt?: string\n provider?: string\n /** Start-frame image (image-to-video). */\n imageUrl?: string\n /** Closing frame, paired with `imageUrl` on models that support one. */\n endFrameUrl?: string\n referenceImageUrls?: string[]\n referenceVideoUrls?: string[]\n referenceAudioUrls?: string[]\n /**\n * Identity inputs that keep a real person's face — a portrait + description\n * per person, up to 3. Only models with a dedicated character channel take\n * them (Gemini Omni: `gemini-omni-video`, `gemini-omni-flash`); any other\n * model answers `400`. Distinct from `referenceImageUrls`, which a multimodal\n * model treats as loose context. Cannot be combined with a start frame\n * (`imageUrl`); each character uses 1 of the model's 7 input units (2 with a\n * `bodyImageUrl`). Adds no credit charge. Give a character a `voice` to pin one\n * voice persona (a Gemini preset + optional description / example line) across\n * clips — up to 3 distinct voices a request.\n */\n characterReferences?: VideoCharacterReference[]\n /** How a start/end frame is reshaped before the model sees it. Default\n * `\"resolution\"`: resize it to the pixel size that model actually renders,\n * which is what stops the provider reshaping it mid-clip. `\"ratio\"` corrects\n * only the aspect; `\"original\"` sends it untouched. */\n frameFit?: \"original\" | \"ratio\" | \"resolution\"\n /** Whether the frame travels as a real start frame or as a reference image\n * bound in the prompt as the opening frame. Default `\"auto\"`, which picks per\n * model. Ignored by models that take no reference images. */\n frameDelivery?: \"auto\" | \"frame\" | \"reference\"\n /** Seedance Draft (`seedance-2-5` only; ignored elsewhere): render a 480p\n * DRAFT — it costs the same as a 480p run and any `resolution` is replaced\n * by 480p. Finalize the take you like with `nodes.run(\"video-upscale\",\n * { provider: \"seedance-2-5-hd\", sourceJobId })`: the same take, frame for\n * frame, at 1080p. Drafts are kept about 7 days. */\n draft?: boolean\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"text-to-video\", …)` / `runAndWait` — the\n * PROMPT-ONLY video lane (`POST /v1/text-to-video`).\n *\n * Which lane takes what: `text-to-video` renders from text (plus optional\n * references) and `prompt` is REQUIRED; frames belong to `generate-video`\n * alone, which is the image-to-video lane (`imageUrl` start frame, `endFrameUrl`\n * closing frame) and where a prompt is optional. A model with no text-to-video\n * mode answers this lane with a `400 image_required` naming itself.\n *\n * Common fields are typed; any other route field passes through.\n */\nexport interface TextToVideoParams extends StructuredReferenceParams, ReferenceCaptionParams, GenerationSessionParams {\n prompt: string\n provider?: string\n duration?: number\n /** Native audio toggle, on the models that render sound. */\n sound?: boolean\n negativePrompt?: string\n aspectRatio?: string\n resolution?: string\n seed?: number\n referenceImageUrls?: string[]\n referenceVideoUrls?: string[]\n referenceAudioUrls?: string[]\n /** Identity inputs that keep a real person's face — see {@link GenerateVideoParams.characterReferences}. */\n characterReferences?: VideoCharacterReference[]\n /** Cinematic direction as catalog IDS — the route renders the wording. */\n direction?: DirectionFields\n /** WHO is in the shot as picker-catalog ids ({@link Subject}), folded ahead of the direction clauses. */\n subject?: Subject\n /** Seedance Draft (`seedance-2-5` only; ignored elsewhere): render a 480p\n * DRAFT — it costs the same as a 480p run and any `resolution` is replaced\n * by 480p. Finalize the take you like with `nodes.run(\"video-upscale\",\n * { provider: \"seedance-2-5-hd\", sourceJobId })`: the same take, frame for\n * frame, at 1080p. Drafts are kept about 7 days. */\n draft?: boolean\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"video-upscale\", …)` / `runAndWait`.\n *\n * - `topaz` (default): upscale any `videoUrl` by `upscaleFactor`.\n * - `veo-1080p` / `veo-4k`: re-render a VEO generation by its `taskId`.\n * - `seedance-2-5-hd`: finalize a Seedance 2.5 DRAFT (a `draft: true` run) —\n * the same take re-rendered at 1080p at the draft's own length. Name the\n * draft by `sourceJobId` (its job id) or `taskId` (its providerTaskId). The\n * price comes from the draft (its length, and whether it used a reference\n * video), never from this body. A foreign, unfinished or non-draft source is\n * refused with a 4xx before anything is charged.\n *\n * Any other route field passes through.\n */\nexport interface VideoUpscaleParams extends GenerationSessionParams {\n provider?: \"topaz\" | \"veo-1080p\" | \"veo-4k\" | \"seedance-2-5-hd\"\n videoUrl?: string\n upscaleFactor?: \"1\" | \"2\" | \"4\"\n taskId?: string\n sourceJobId?: string\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"assemble-narrated-video\", …)` / `runAndWait`.\n * Assembles blocks of video with audio narration into a single composed video.\n */\nexport interface AssembleNarratedVideoParams extends GenerationSessionParams {\n blocks: { videoUrl: string; audioUrl?: string }[]\n voiceVolume?: number\n clipAudioVolume?: number\n maxSlowdown?: number\n trimStartFrames?: number\n trimEndFrames?: number\n [k: string]: unknown\n}\n\n/**\n * Typed request body for `nodes.run(\"video-depth\", …)` / `runAndWait` — turns a\n * video into a depth video (`POST /v1/video-depth`, near is bright, far is\n * dark). The completed job writes `videoUrl` (MP4) and `thumbnailUrl`.\n *\n * Two providers: `video-depth-anything` (the default, temporally consistent)\n * honours every lever below; `depth-anything-frames` (Depth Anything per\n * frame) takes `modelSize` and `colormap` only, and renders only `grayscale` /\n * `inferno`. A lever the chosen provider does not take is dropped and a\n * colormap it cannot render becomes `grayscale` — coerced, never rejected (a\n * value outside the vocabularies below is still a `400 validation_error`).\n *\n * The clip is measured before any credits are reserved. Too long answers\n * `400 video_too_long` (`video-depth-anything`: more than 2,400 frames or\n * 300 s; `depth-anything-frames`: more than 80 s); a clip that cannot be read\n * answers `400 video_unreadable`; a provider the deployment has turned off\n * answers `403 model_not_available`.\n *\n * Common fields are typed; any other route field passes through.\n */\nexport interface VideoDepthParams extends GenerationSessionParams {\n /** The source video — a public http(s) URL. */\n videoUrl: string\n /** Default `video-depth-anything`. */\n provider?: VideoDepthProvider\n /** `small` / `base` / `large` (the default) — larger is more detailed and slower. */\n modelSize?: VideoDepthModelSize\n /** Default `grayscale`. `depth-anything-frames` renders only `grayscale` / `inferno`. */\n colormap?: VideoDepthColormap\n /** `video-depth-anything` only. Default `auto`. */\n resolution?: VideoDepthResolution\n /** `video-depth-anything` only: render the original and the depth next to each other. */\n sideBySide?: boolean\n [k: string]: unknown\n}\n\n/**\n * The `output_data` shape a finalized generation job writes. Every async\n * generation node persists one (or more) of these media URLs to\n * `jobs.output_data` on completion — `generate-image` → `imageUrl`,\n * `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale` /\n * `video-depth` → `videoUrl` (+ `thumbnailUrl`), `text-to-speech` / `generate-music` →\n * `audioUrl` (a `text-to-speech` run with `withTimestamps: true` on a model\n * that returns timings, and every `text-to-dialogue` run on one, also carries\n * `transcript`). Resolved by {@link NodesResource.runAndWait}. Extra fields may be\n * present, so the index signature is open.\n */\n/**\n * The completed `social-search` job's `output_data`: EVERY post the search\n * found, in the order it asked for (Social Search, Nodaro Cloud). Picking the\n * posts a workflow passes on is an editor step; a direct caller gets them all.\n */\nexport interface SocialSearchJobOutput extends NodeJobOutput {\n readonly json: readonly SocialPost[]\n readonly platform: SocialPlatform\n readonly mode: SocialSearchMode\n readonly query: string\n /** Non-fatal notes (\"only part of the results could be loaded\"). */\n readonly warnings?: readonly string[]\n}\n\nexport interface NodeJobOutput {\n /** `text-to-speech` / `generate-music` / audio nodes write here. For\n * `audio-separation` this is the primary stem (vocals). */\n readonly audioUrl?: string\n /** `audio-separation` (Demucs) per-stem URLs. `vocalUrl`/`instrumentalUrl`\n * in vocal/instrumental mode; the rest in full-stems mode. */\n readonly vocalUrl?: string\n readonly instrumentalUrl?: string\n readonly drumsUrl?: string\n readonly bassUrl?: string\n readonly otherUrl?: string\n readonly guitarUrl?: string\n readonly pianoUrl?: string\n /** `generate-video` / `combine-videos` / `merge-video-audio` / `video-upscale` / `video-depth` write here. */\n readonly videoUrl?: string\n /** `generate-image` / `edit-image` / `extract-frame` write here. */\n readonly imageUrl?: string\n /** Every image, when the job made more than one; `imageUrl` is the first. */\n readonly imageUrls?: readonly string[]\n /**\n * The output's pixel size. On an image job: the size of `imageUrl` as it is\n * stored — what you download, a free-tier watermark included — with EXIF\n * orientation applied, so the size a browser shows. Lay a grid tile out\n * before the image loads, or show and sort by size.\n *\n * Present on image jobs completed after this release (and on the jobs that\n * always recorded it: image overlay, video overlay, video mask, mask\n * composite). Absent when the size could not be read — never half a size.\n */\n readonly width?: number\n readonly height?: number\n /**\n * Beside `imageUrls`: each image with its pixel size, index-aligned\n * (`images[i]` is `imageUrls[i]`). Present on image jobs completed after\n * this release when at least one size was read; an entry whose size could\n * not be read has its `url` and no `width` / `height`.\n */\n readonly images?: readonly JobOutputImage[]\n /** Poster frame for video outputs. */\n readonly thumbnailUrl?: string\n readonly [k: string]: unknown\n}\n\n/** One image of a job that made several — see {@link NodeJobOutput.images}. */\nexport interface JobOutputImage {\n readonly url: string\n /** Pixel size as stored, EXIF orientation applied. Absent when it could not be read. */\n readonly width?: number\n readonly height?: number\n}\n\n/** Options for {@link NodesResource.runAndWait} and {@link NodesResource.runMany}. */\nexport interface RunAndWaitOptions {\n /**\n * Abort the run/poll loop. Aborting (or passing an already-aborted signal)\n * stops polling and rejects with {@link JobAbortedError}, whose `jobId`\n * names the job once it was submitted. Aborting stops the WAITING only: the\n * job keeps running and is charged, unless {@link cancelOnAbort} is set.\n */\n readonly signal?: AbortSignal\n /**\n * Also cancel the job when `signal` aborts after it was submitted\n * (`POST /v1/jobs/:id/cancel`, which refunds its credit hold). Best effort:\n * a failed cancel still rejects with the {@link JobAbortedError}. Default\n * `false` — aborting only stops waiting.\n */\n readonly cancelOnAbort?: boolean\n /** Called with each lean status the poll loop observes (running → terminal). */\n readonly onProgress?: (status: JobStatusResult) => void\n /** Poll interval in ms. Default 2000. */\n readonly pollMs?: number\n /** Wall-clock cap before giving up, in ms. Default ~15 min (900_000). */\n readonly maxMs?: number\n /**\n * `Idempotency-Key` for the submit half. Reuse the same value when retrying a\n * call that timed out, so the run is not started or charged twice. Omitted →\n * no key, which is the historical behaviour for every node that does not\n * require one.\n */\n readonly idempotencyKey?: string\n}\n\n/** Per-call transport controls for {@link NodesResource.run}. */\nexport interface RunNodeOptions {\n /** `Idempotency-Key` header. See {@link RunAndWaitOptions.idempotencyKey}. */\n readonly idempotencyKey?: string\n}\n\n/** One settled result from {@link NodesResource.runMany}. */\nexport interface RunManyResult {\n readonly jobId: string\n readonly output: NodeJobOutput\n}\n\n// Terminal statuses (poll loop stops on these) are handled explicitly in\n// `pollJob`: `completed` resolves output_data, `failed`/`cancelled` throw a\n// JobFailedError — matching studio's `completed | failed | cancelled` set.\nconst DEFAULT_POLL_MS = 2000\n/** Safety cap so a stuck job can't poll forever (~15 min at 2s). */\nconst DEFAULT_MAX_MS = 15 * 60 * 1000\n\nconst sleep = (ms: number, signal?: AbortSignal): Promise<void> =>\n new Promise<void>((resolve, reject) => {\n if (signal?.aborted) {\n reject(new JobAbortedError())\n return\n }\n const timer = setTimeout(() => {\n signal?.removeEventListener(\"abort\", onAbort)\n resolve()\n }, ms)\n function onAbort() {\n clearTimeout(timer)\n reject(new JobAbortedError())\n }\n signal?.addEventListener(\"abort\", onAbort, { once: true })\n })\n\n/** The error for a node type that cannot be run on its own. */\nfunction notRunnable(type: string, why: string): NodaroError {\n return new NodaroError(`Node type \"${type}\" has no route of its own to run it directly: ${why}.`, \"node_not_runnable\", 0)\n}\n\n/** A 404 for the route itself (the server has no such path), not for a missing resource. */\nconst isRouteNotFound = (err: unknown): boolean => err instanceof NotFoundError && /^Route\\b.*\\bnot found\\b/i.test(err.message)\n\n/** Pull a `{ jobId }` off a run result, or throw a labelled JobFailedError. */\nfunction extractJobId(result: RunNodeResult, label: string): string {\n if (result && typeof result === \"object\" && \"jobId\" in result) {\n const jobId = (result as { jobId: unknown }).jobId\n if (typeof jobId === \"string\") return jobId\n }\n // No jobId means the node ran inline/synchronously (meta-ads-scrape, etc.) or\n // the route shape changed — runAndWait is for async jobs, so this is an error.\n throw new JobFailedError(`${label} did not return a jobId`, \"\")\n}\n\n/** Whether a generate-image request makes several jobs: `count` above 1, or `models`. */\nfunction isImageBatchRequest(type: string, params: Record<string, unknown>): boolean {\n if (type !== \"generate-image\") return false\n return (typeof params.count === \"number\" && params.count > 1) || Array.isArray(params.models)\n}\n\n/** The job ids of a batch answer (several jobs from one call), or null for a single job. */\nfunction batchJobIds(result: RunNodeResult): string[] | null {\n const ids = (result as { jobIds?: unknown } | null)?.jobIds\n return Array.isArray(ids) && ids.every((id) => typeof id === \"string\") ? (ids as string[]) : null\n}\n\n/** A wait-for-one-job method asked to wait for a batch. */\nfunction batchRefusal(detail: string): NodaroError {\n return new NodaroError(\n `${detail}: this waits for one job — use nodes.runBatchAndWait() for a batch (count above 1, or models).`,\n \"batch_requires_run_batch_and_wait\",\n 0,\n )\n}\n\n/** The batch answer of a generate-image call, checked, or null. */\nfunction batchResultOf(result: RunNodeResult): GenerateImageBatchResult | null {\n const r = result as Partial<GenerateImageBatchResult> | null\n if (!r || typeof r.batchId !== \"string\" || !batchJobIds(result) || !Array.isArray(r.groups)) return null\n return r as GenerateImageBatchResult\n}\n\nexport class NodesResource {\n /** Runs poll through `jobs`, so the client must carry that resource too. */\n constructor(private client: NodaroClientCore & { readonly jobs: JobsResource }) {}\n\n /** List all known node descriptors. Server caches publicly for 5 minutes. */\n list(): Promise<{ data: NodeDescriptor[] }> {\n return this.client.request(\"GET\", \"/v1/nodes\")\n }\n\n /** Get a single node descriptor by type slug (e.g. \"generate-image\"). */\n get(type: string): Promise<{ data: NodeDescriptor }> {\n return this.client.request(\"GET\", `/v1/nodes/${encodeURIComponent(type)}`)\n }\n\n /**\n * Run a single node directly without wrapping it in a workflow. Posts\n * `params` as the request body to the node's API route. Most use\n * `POST /v1/<type>`; the rest are listed in `NODE_RUN_ROUTES` (exported by\n * `@nodaro/shared`): scene authoring uses `/v1/3d-scene/generate|edit`,\n * `llm-chat` uses `/v1/llm-chat/generate`, the Suno nodes `/v1/suno/…`, the\n * social post nodes `/v1/social/publish` (a per-platform node fills in its\n * `platform` when you leave it out), and render-video with a planType uses\n * `/v1/render-video/plan`.\n *\n * A node type with no route of its own (`NODE_TYPES_WITHOUT_RUN_ROUTE`, e.g.\n * `modify-image`, which runs through `image-to-image`) rejects with a\n * {@link NodaroError} of code `node_not_runnable` naming what to run\n * instead, without calling the API. A type that runs only inside a workflow\n * (an input, trigger, utility or parameter node such as `upload-image`)\n * rejects the same way once the API answers that it has no such route; a\n * 404 about a missing resource stays a `NotFoundError`.\n *\n * This is the SDK equivalent of the MCP server's verb tools — and the\n * path the Nodaro CLI uses for `nodaro nodes run <type>`.\n *\n * Most node types are async: the response includes `{ jobId }` and the\n * actual generation runs on a worker. Poll `client.jobs.get(jobId)` until\n * completed. Synchronous node types (`meta-ads-scrape`, etc.) return their\n * full result without a `jobId` field.\n *\n * @param type Node type slug — must match an entry in the registry\n * returned by `list()` (e.g. \"generate-image\").\n * @param params Request body. Field names must match the node's\n * `inputSchema` (see `get(type).inputSchema`).\n */\n run(type: \"generate-3d-scene\", params: GenerateScene3DParams): Promise<RunNodeResult>\n run(type: \"edit-3d-scene\", params: EditScene3DParams): Promise<RunNodeResult>\n run(type: \"render-video\", params: RenderScene3DParams): Promise<RunNodeResult>\n run(type: \"pro-3d-render\", params: Pro3DRenderRunParams, options?: RunNodeOptions): Promise<RunNodeResult>\n run(type: \"generate-image\", params: GenerateImageParams & ({ count: 2 | 3 | 4 } | { models: string[] }), options?: RunNodeOptions): Promise<GenerateImageBatchResult>\n run(type: \"generate-image\", params?: GenerateImageParams, options?: RunNodeOptions): Promise<RunNodeResult>\n run(type: \"text-to-speech\", params: TextToSpeechParams): Promise<RunNodeResult>\n run(type: \"generate-video\", params?: GenerateVideoParams): Promise<RunNodeResult>\n run(type: \"text-to-video\", params: TextToVideoParams): Promise<RunNodeResult>\n run(type: \"video-upscale\", params: VideoUpscaleParams): Promise<RunNodeResult>\n run(type: \"assemble-narrated-video\", params?: AssembleNarratedVideoParams): Promise<RunNodeResult>\n run(type: \"social-search\", params: SocialSearchParams, options?: RunNodeOptions): Promise<RunNodeResult>\n run(type: \"video-depth\", params: VideoDepthParams, options?: RunNodeOptions): Promise<RunNodeResult>\n run(type: \"image-to-image\", params: ImageToImageParams, options?: RunNodeOptions): Promise<ImageJobStarted>\n run(type: \"edit-image\", params: EditImageParams, options?: RunNodeOptions): Promise<ImageJobStarted>\n run(type: \"generate-mask\", params: GenerateMaskParams, options?: RunNodeOptions): Promise<ImageJobStarted>\n /** Synchronous and free: answers the tiles, no job. */\n run(type: \"split-image\", params: SplitImageParams, options?: RunNodeOptions): Promise<SplitImageResult>\n run(type: string, params?: Record<string, unknown>, options?: RunNodeOptions): Promise<RunNodeResult>\n run(type: string, params: Record<string, unknown> = {}, options: RunNodeOptions = {}): Promise<RunNodeResult | GenerateImageBatchResult> {\n // The route comes from the table the platform's workflow engine uses\n // too; only render-video's plan route depends on the body.\n const route = type === \"render-video\" && typeof params.planType === \"string\" ? \"/v1/render-video/plan\" : nodeRunRoute(type)\n if (!route) return Promise.reject(notRunnable(type, NODE_TYPES_WITHOUT_RUN_ROUTE[type]!))\n // A per-platform social node publishes through the shared route, which\n // needs the platform the node type stands for.\n const platform = SOCIAL_POST_NODE_PLATFORM[type]\n const body = platform && params.platform === undefined ? { ...params, platform } : params\n // Only a type on the `/v1/<type>` convention is relabelled: a listed\n // route that 404s means a platform that predates it, not a node that\n // cannot run.\n const conventional = !Object.prototype.hasOwnProperty.call(NODE_RUN_ROUTES, type)\n return this.client\n .request<RunNodeResult>(\"POST\", route, {\n body,\n // Transport-level retry safety, opt-in per call. The platform reads the\n // standard header, so a caller that reuses a key on a retry gets the\n // same run back instead of a second charge.\n ...(options.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}),\n })\n .catch((err: unknown) => {\n if (conventional && isRouteNotFound(err)) throw notRunnable(type, \"it runs only inside a workflow (client.workflows.run)\")\n throw err\n })\n }\n\n /**\n * Run a single async node to completion: {@link run} it, extract the\n * `{ jobId }`, then client-poll `jobs.getStatus(jobId)` every `pollMs`\n * (default 2000) until a terminal status, up to `maxMs` (default ~15 min).\n *\n * Resolves the job's typed `output_data` ({@link NodeJobOutput}) on\n * `completed`. Throws (all typed, catchable by `instanceof`):\n * - {@link InsufficientCreditsError} / {@link StorageExceededError} etc. —\n * surfaced by the underlying {@link run} on 402/413/… before any poll.\n * - {@link JobFailedError} — terminal `failed`/`cancelled` (carries the\n * job's `error_message` + `jobId`).\n * - {@link JobTimeoutError} — `maxMs` deadline exceeded before terminal.\n * - {@link JobAbortedError} — `signal` fired (or was already aborted);\n * polling stops immediately.\n * - {@link JobHeldError} — the job entered `pending_review` on a deployment\n * that registers a job policy. Ends the poll on the FIRST held tick (that\n * status waits on a human, not on the job) and does NOT cancel it.\n * - {@link JobBlockedError} — a 422 from {@link run} before any poll: the\n * deployment's job policy refused the request outright.\n *\n * Polling is fully client-side (no server function blocks) — the same model\n * thin clients use, lifted out of their hand-rolled run→poll loops.\n *\n * @param type Node type slug (e.g. \"generate-video\"). See {@link run}.\n * @param params Request body — field names match the node's `inputSchema`.\n * @param opts `signal` / `onProgress` / `pollMs` / `maxMs`.\n */\n runAndWait(type: \"generate-3d-scene\", params: GenerateScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>\n runAndWait(type: \"edit-3d-scene\", params: EditScene3DParams, options?: RunAndWaitOptions): Promise<Scene3DJobOutput>\n runAndWait(type: \"render-video\", params: RenderScene3DParams, options?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"pro-3d-render\", params: Pro3DRenderRunParams, options?: RunAndWaitOptions): Promise<Pro3DRenderJobOutput>\n runAndWait(type: \"generate-image\", params?: GenerateImageParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"text-to-speech\", params: TextToSpeechParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"generate-video\", params?: GenerateVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"text-to-video\", params: TextToVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"video-upscale\", params: VideoUpscaleParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"assemble-narrated-video\", params?: AssembleNarratedVideoParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"social-search\", params: SocialSearchParams, opts?: RunAndWaitOptions): Promise<SocialSearchJobOutput>\n runAndWait(type: \"video-depth\", params: VideoDepthParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"image-to-image\", params: ImageToImageParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"edit-image\", params: EditImageParams, opts?: RunAndWaitOptions): Promise<NodeJobOutput>\n runAndWait(type: \"generate-mask\", params: GenerateMaskParams, opts?: RunAndWaitOptions): Promise<GenerateMaskJobOutput>\n runAndWait<T extends string>(type: T, params?: Record<string, unknown>, opts?: RunAndWaitOptions): Promise<T extends \"pro-3d-render\" ? Pro3DRenderJobOutput : T extends \"generate-3d-scene\" | \"edit-3d-scene\" ? Scene3DJobOutput : T extends \"generate-mask\" ? GenerateMaskJobOutput : NodeJobOutput>\n async runAndWait(\n type: string,\n params: Record<string, unknown> = {},\n opts: RunAndWaitOptions = {},\n ): Promise<NodeJobOutput> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n // A batch would be waited on as its first job alone — refused before\n // anything is submitted or charged.\n if (isImageBatchRequest(type, params)) throw batchRefusal(`${type} with count above 1 or models makes several jobs`)\n const result = await this.run(type, params, { idempotencyKey: opts.idempotencyKey })\n const ids = batchJobIds(result)\n if (ids && ids.length > 1) throw batchRefusal(`${type} started ${ids.length} jobs (${ids.join(\", \")}), which keep running`)\n const jobId = extractJobId(result, type)\n return this.pollJob(jobId, type, opts)\n }\n\n /**\n * Run a generate-image BATCH — `count` above 1 and/or `models` — to the end:\n * submit it once ({@link run}, with your `idempotencyKey` if any), then wait\n * for every job at once, each the way {@link runAndWait} waits.\n *\n * Resolves `{ batch, results }`: the submit's answer\n * ({@link GenerateImageBatchResult} — ids, groups by model, credits) and one\n * {@link BatchJobOutcome} per job, in `batch.jobIds` order — its output, or the\n * typed error that ended the wait for it. One image failing never discards the\n * others. Rejects only before any job exists (the submit's own errors, e.g.\n * `InsufficientCreditsError` with the batch total as `required`), and on\n * `signal` ({@link JobAbortedError}; `cancelOnAbort` cancels every job).\n *\n * A request for one image is refused before anything is sent (code\n * `not_a_batch`) — wait for it with {@link runAndWait}.\n */\n async runBatchAndWait(\n type: \"generate-image\",\n params: GenerateImageParams,\n opts: RunAndWaitOptions = {},\n ): Promise<GenerateImageBatchOutput> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n if (!isImageBatchRequest(type, params)) {\n throw new NodaroError(\n \"runBatchAndWait waits for a batch — count above 1, or models; wait for one image with nodes.runAndWait().\",\n \"not_a_batch\",\n 0,\n )\n }\n const result = await this.run(type, params, { idempotencyKey: opts.idempotencyKey })\n const batch = batchResultOf(result)\n if (!batch) throw new JobFailedError(`${type} did not answer with a batch`, \"\")\n const modelOf = new Map(batch.groups.flatMap((group) => group.jobIds.map((id) => [id, group.model] as const)))\n const results = await Promise.all(\n batch.jobIds.map(async (jobId): Promise<BatchJobOutcome> => {\n const model = modelOf.get(jobId) ?? \"\"\n try {\n return { jobId, model, status: \"completed\", output: await this.pollJob(jobId, type, opts) }\n } catch (err) {\n if (err instanceof JobAbortedError || !(err instanceof NodaroError)) throw err\n return { jobId, model, status: \"error\", error: err }\n }\n }),\n )\n return { batch, results }\n }\n\n /**\n * Fan out N async runs of the same node `type` to completion concurrently —\n * the candidate-grid path (generate N stills/clips in parallel). Each runs\n * via {@link runAndWait}; resolves once ALL settle, to an array of\n * `{ jobId, output }` in input order. Rejects (and the rejection wins) if any\n * single run rejects — same typed errors as {@link runAndWait}, including\n * {@link JobHeldError} when one candidate is held for review while the\n * others complete. A shared `signal` aborts the whole batch.\n *\n * @param type Node type slug, applied to every entry.\n * @param paramsList One request body per candidate.\n * @param opts Shared `signal` / `cancelOnAbort` / `onProgress` / `pollMs` / `maxMs`.\n */\n async runMany(\n type: string,\n paramsList: Record<string, unknown>[],\n opts: RunAndWaitOptions = {},\n ): Promise<RunManyResult[]> {\n if (opts.signal?.aborted) throw new JobAbortedError()\n // Each entry is waited on as one job: a batch entry is refused up front.\n if (paramsList.some((params) => isImageBatchRequest(type, params))) {\n throw batchRefusal(`a ${type} entry with count above 1 or models makes several jobs`)\n }\n return Promise.all(\n paramsList.map(async (params) => {\n if (opts.signal?.aborted) throw new JobAbortedError()\n const result = await this.run(type, params)\n const jobId = extractJobId(result, type)\n const output = await this.pollJob(jobId, type, opts)\n return { jobId, output }\n }),\n )\n }\n\n /** {@link pollUntilSettled}, plus what an abort does to the job: it keeps\n * running unless `cancelOnAbort` asks to cancel it too, and the rejection\n * always names it (an abort landing mid-sleep carries no id of its own). */\n private async pollJob(\n jobId: string,\n label: string,\n opts: RunAndWaitOptions,\n ): Promise<NodeJobOutput> {\n try {\n return await this.pollUntilSettled(jobId, label, opts)\n } catch (err) {\n if (!(err instanceof JobAbortedError)) throw err\n if (opts.cancelOnAbort) await this.client.jobs.cancel(jobId).catch(() => undefined)\n throw err.jobId ? err : new JobAbortedError(err.message, jobId)\n }\n }\n\n /** Poll an already-kicked job id until it stops moving; resolve output_data\n * or throw. Two non-terminal statuses end the loop: an abort, and\n * `pending_review` (a job policy held the output for a human — see\n * {@link JobHeldError}). */\n private async pollUntilSettled(\n jobId: string,\n label: string,\n opts: RunAndWaitOptions,\n ): Promise<NodeJobOutput> {\n const pollMs = opts.pollMs ?? DEFAULT_POLL_MS\n const maxMs = opts.maxMs ?? DEFAULT_MAX_MS\n const deadline = Date.now() + maxMs\n for (;;) {\n if (opts.signal?.aborted) throw new JobAbortedError(undefined, jobId)\n const { data } = await this.client.jobs.getStatus(jobId)\n opts.onProgress?.(data)\n if (data.status === \"completed\") {\n return (data.output_data ?? {}) as NodeJobOutput\n }\n if (data.status === \"failed\" || data.status === \"cancelled\") {\n throw new JobFailedError(\n data.error_message ?? `${label} ${data.status}`,\n jobId,\n data.status,\n )\n }\n // `pending_review` is in-flight but PARKED ON A HUMAN — it does not\n // progress on its own, so continuing the loop would burn `maxMs` (15 min\n // by default) and then report a `JobTimeoutError` that misdescribes what\n // happened. End here instead, without cancelling: the output exists, the\n // reservation stays reserved, and a reviewer resolves it.\n if (data.status === \"pending_review\") {\n throw new JobHeldError(`${label} is awaiting review`, jobId)\n }\n if (Date.now() > deadline) {\n throw new JobTimeoutError(`${label} timed out`, jobId, maxMs)\n }\n await sleep(pollMs, opts.signal)\n }\n }\n}\n"]}
@@ -1,3 +0,0 @@
1
- export { PROMPT_PREFIX_KEY, PROMPT_SUFFIX_KEY } from '@nodaro/shared/nodes';
2
- //# sourceMappingURL=chunk-W7YI5UT3.js.map
3
- //# sourceMappingURL=chunk-W7YI5UT3.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/resources/edit-use-case-inputs.ts","../src/resources/edit.ts"],"names":["isUseCaseNodeType","NodaroError","USE_CASE_NODE_TYPES","ForbiddenError","resolveEditPlanSources","describeAudioSyncOffsetIssue","transcriptSpeakerLabels","speakerMapResultOf","cameraSwitchSpeakerMap","edl","remapTranscriptThroughEdl"],"mappings":"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","file":"chunk-WUIEZ6Z4.cjs","sourcesContent":["/**\n * The inputs of the podcast use-case runs on the `edit` resource (Tighten\n * Episode, Chapters, Trailer, Clip Pack) and the request body each one sends.\n * Split from `edit.ts`, which holds the methods, the type list and the\n * continue routes; every name here is re-exported from there.\n */\nimport type { EditPlanTier, EdlTargetAspect, SpeakerEmphasisStyle, SpeakerSwitchId } from \"@nodaro/shared/edl\"\nimport type { CaptionStyle } from \"@nodaro/shared/captions\"\n\n/**\n * Input for {@link EditResource.tightenEpisode} and\n * {@link EditResource.estimateTightenEpisode}: one recording in, the tightened\n * episode out. Only `mediaUrl` is required; every other field left out takes\n * the node's default.\n */\nexport interface TightenEpisodeInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl: string\n /** Optional music bed, ducked under the voice and ending with the episode. */\n musicUrl?: string\n /** `\"video\"` or `\"audio\"`; an audio recording always makes an audio episode. */\n output?: \"video\" | \"audio\"\n /**\n * Frame a video episode with Speaker View: an output aspect and a layout,\n * over automatic boxes on the speakers on screen. A layout the aspect does\n * not draw is snapped to one it does. `null` or absent: no framing.\n */\n framing?: {\n aspect: EdlTargetAspect\n layout: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n } | null\n /** Captions on a video episode (on by default). */\n captions?: boolean\n /** The captions' look. */\n captionStyle?: CaptionStyle\n /**\n * Stop at a quick preview for a person to review; render the full-quality\n * episode afterwards with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier. */\n planTier?: EditPlanTier\n /** Editing instructions for the planner (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n /** How far the music dips under the voice, 0–100 (default 75). */\n musicDuckAmount?: number\n}\n\n/**\n * Input for {@link EditResource.episodeChapters} and\n * {@link EditResource.estimateEpisodeChapters}: one recording in, its chapters\n * out. Only `mediaUrl` is required; every other field left out takes the\n * node's default.\n */\nexport interface EpisodeChaptersInput {\n /** The recording — a video or audio URL, up to 3 hours. */\n mediaUrl: string\n /** The planner's tier (default `\"economy\"`). */\n planTier?: EditPlanTier\n /** Instructions for the chapters, e.g. \"5 to 8 chapters\" (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the chapter titles (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The formats a Trailer is delivered in, in delivery order (16:9 is a 1920×1080 landscape). */\nexport const TRAILER_FORMATS = [\"9:16\", \"1:1\", \"4:5\", \"16:9\"] as const\n/** One Trailer format (see {@link TRAILER_FORMATS}). */\nexport type TrailerFormat = (typeof TRAILER_FORMATS)[number]\n\n/**\n * Input for {@link EditResource.episodeTrailer} and\n * {@link EditResource.estimateEpisodeTrailer}: one video recording in, a\n * 20–40 s trailer out in each chosen format. Only `mediaUrl` is required.\n */\nexport interface EpisodeTrailerInput {\n /** The recording — a video URL, up to 3 hours (an audio-only recording is refused). */\n mediaUrl: string\n /** An optional intro card: a short video (15 s at most) played before the trailer. */\n introCardUrl?: string\n /** The formats to deliver, padded, never cropped (default `[\"9:16\", \"1:1\", \"4:5\"]`; an empty list is refused). */\n formats?: TrailerFormat[]\n /** Stop at a preview of the trailer; render every format with {@link EditResource.continueUseCase} (`mode: \"render-final\"`). */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for the trailer (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\n/** The shortest and longest clip a Clip Pack asks the plan for, in seconds. */\nexport const CLIP_PACK_MIN_TARGET_SEC = 5\nexport const CLIP_PACK_MAX_TARGET_SEC = 180\n\n/**\n * A Clip Pack emphasis: `\"none\"`, or one or more of `\"scale\"`, `\"border\"` and\n * `\"dim\"` joined with `\"+\"` (each once — the route refuses a repeat).\n */\nexport type ClipPackEmphasis = SpeakerEmphasisStyle | `${Exclude<SpeakerEmphasisStyle, \"none\">}+${string}`\n\n/**\n * Input for {@link EditResource.clipPack} and\n * {@link EditResource.estimateClipPack}: one recording in, a pack of short\n * clips out. Only `mediaUrl` is required; every other field left out takes\n * the node's default.\n */\nexport interface ClipPackInput {\n /** The recording — a video or audio URL, up to 3 hours. An audio recording makes audio clips (no framing, no captions). */\n mediaUrl: string\n /** How many clips to find, 1–50 (default 8). The plan may find fewer. */\n count?: number\n /** Each clip's target length in seconds, {@link CLIP_PACK_MIN_TARGET_SEC}–{@link CLIP_PACK_MAX_TARGET_SEC} (default 60). */\n targetDurationSec?: number\n /**\n * Frame each video clip with Speaker View on automatic boxes over the\n * episode's speakers: the shape, and optionally the layout (default\n * `\"stacked\"`; one the shape does not draw is snapped), the switch at each\n * speaker change (default `\"pan\"`) and the emphasis (default `\"scale\"`;\n * `\"none\"`, or `\"scale\"`, `\"border\"` and `\"dim\"` joined with `\"+\"`). Left\n * out: 9:16, stacked, pan, scale. `null`: no framing (the recording's shape).\n */\n framing?: {\n aspect: EdlTargetAspect\n layout?: \"auto\" | \"single\" | \"side-by-side\" | \"stacked\" | \"grid\" | \"pip\"\n switchType?: SpeakerSwitchId\n emphasisStyle?: ClipPackEmphasis\n } | null\n /** Caption each video clip (on by default). */\n captions?: boolean\n /** The captions' look (default `\"karaoke\"`). */\n captionStyle?: CaptionStyle\n /**\n * Stop at quick previews of the clips for a person to review; render the\n * kept clips at full quality with {@link EditResource.continueUseCase}\n * (`mode: \"render-final\"`, an `editedEdl` that keeps or drops clips).\n */\n reviewBeforeRender?: boolean\n /** The planner's tier (default `\"standard\"`). */\n planTier?: EditPlanTier\n /** Instructions for picking the clips (up to 4 000 characters). */\n instructions?: string\n /** A style guide for the planner (up to 4 000 characters). */\n styleGuide?: string\n}\n\nexport function tightenBody(input: TightenEpisodeInput): Record<string, unknown> {\n const { mediaUrl, ...rest } = input\n return { mediaUrls: [mediaUrl], ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)) }\n}\n\nexport function chaptersBody(input: EpisodeChaptersInput): Record<string, unknown> {\n return {\n mediaUrls: [input.mediaUrl],\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n }\n}\n\nexport function trailerBody(input: EpisodeTrailerInput): Record<string, unknown> {\n return {\n mediaUrls: [input.mediaUrl],\n ...(input.introCardUrl !== undefined ? { introCardUrl: input.introCardUrl } : {}),\n // An empty list is sent as is: the route refuses it (at least one format).\n ...(input.formats !== undefined ? { formats: input.formats } : {}),\n ...(input.reviewBeforeRender !== undefined ? { reviewBeforeRender: input.reviewBeforeRender } : {}),\n ...(input.planTier !== undefined ? { planTier: input.planTier } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n }\n}\n\n/** Clip Pack's body: what the caller set, `framing: null` included (no framing). */\nexport function clipPackBody(input: ClipPackInput): Record<string, unknown> {\n const { mediaUrl, ...rest } = input\n return { mediaUrls: [mediaUrl], ...Object.fromEntries(Object.entries(rest).filter(([, v]) => v !== undefined)) }\n}\n","import type { NodaroClientCore } from \"../core.js\"\nimport { ForbiddenError, NodaroError } from \"../errors.js\"\nimport {\n remapTranscriptThroughEdl,\n unwrapEditPlanOutput,\n resolveEditPlanSources,\n describeAudioSyncOffsetIssue,\n transcriptSpeakerLabels,\n cameraSwitchSpeakerMap, speakerMapResultOf, type SpeakerMapResult,\n type Edl,\n type Transcript,\n type EditPlanMode,\n type EditPlanTier,\n type EdlClipSet,\n type ChapterSet,\n type EdlTargetAspect\n} from \"@nodaro/shared/edl\"\nimport { USE_CASE_NODE_TYPES, isUseCaseNodeType, type UseCaseNodeType } from \"@nodaro/shared/nodes\"\nimport type { SpeakerMapInput } from \"./speaker-map-inputs.js\"\nimport {\n chaptersBody,\n clipPackBody,\n tightenBody,\n trailerBody,\n type ClipPackInput,\n type EpisodeChaptersInput,\n type EpisodeTrailerInput,\n type TightenEpisodeInput,\n} from \"./edit-use-case-inputs.js\"\n\nexport { CLIP_PACK_MAX_TARGET_SEC, CLIP_PACK_MIN_TARGET_SEC, TRAILER_FORMATS } from \"./edit-use-case-inputs.js\"\nexport type { ClipPackEmphasis, ClipPackInput, EpisodeChaptersInput, EpisodeTrailerInput, TightenEpisodeInput, TrailerFormat } from \"./edit-use-case-inputs.js\"\n\n// Re-export the canonical EDL / transcript vocabulary from `@nodaro/shared`\n// (single source of truth — the shapes live at `packages/shared/src/edl.ts`).\n// `@nodaro/shared` is already a hard dep of this package, so there is no\n// bundle-size cost to importing from it, and an integration builds requests and\n// reads results with one dependency. `EdlClipSet` / `ChapterSet` are the raw\n// `output_data` shapes of the `clips` / `chapters` modes, and\n// `unwrapEditPlanOutput` is the sanctioned way to normalize any edit-plan job's\n// `output_data` (it also strips the relay's `viaNodaroCloud` marker).\nexport type { Edl, Transcript, EditPlanMode, EditPlanTier, EdlClipSet, ChapterSet }\nexport { unwrapEditPlanOutput }\n\n/**\n * A silence-detect job's `output_data.json` — the value to pass as\n * {@link EditPlanInput.silence}. It is NOT what {@link EditResource.silenceDetect}\n * returns (that is `{ jobId }`); fetch the finished job and read its\n * `output_data.json`. The planner reads the `ranges` array — an input without\n * one is silently ignored, so pass this whole object.\n */\nexport interface SilenceRanges {\n /** Wire version of the payload. */\n version?: number\n /** Silence spans on the source clock, in ms. */\n ranges: ReadonlyArray<{ startMs: number; endMs: number }>\n /** Total source duration, in ms. */\n durationMs?: number\n}\n\n/** Every editorial route answers with the queued job to poll. */\nexport interface EditJobResult {\n /** The queued job id — follow it via `client.jobs.getStatus(jobId)`. */\n jobId: string\n}\n\nexport interface SilenceDetectInput {\n /**\n * An audio OR video source. A single ffmpeg `silencedetect` pass runs over the\n * source's audio proxy either way — no provider key required.\n */\n audioUrl: string\n /** dBFS threshold, always ≤ 0. Default -35 (a good podcast default). */\n thresholdDb?: number\n /** Minimum silence length to report, in ms. Default 700. */\n minSilenceMs?: number\n /** Padding kept around speech, in ms — shrinks each reported range inward. Default 120. */\n padMs?: number\n /**\n * Optionally associate this run with a workflow execution (display only — it\n * ties the standalone job back to a workflow for the execution history).\n */\n workflowId?: string\n}\n\n/** One recording for {@link EditResource.audioSync}. */\nexport interface AudioSyncSource {\n /**\n * Your id for this recording (1–200 chars, unique within the request) —\n * echoed back as its offset's `sourceId`. Use the id your EDL gives the same\n * recording (`EdlSource.id`) and the result folds straight into it.\n */\n id: string\n /** An audio OR video URL — the audio track is read either way. */\n url: string\n}\n\nexport interface AudioSyncInput {\n /** The 2–6 recordings of one conversation to line up (camera files and/or a master mic). */\n sources: AudioSyncSource[]\n /**\n * The id of the source every offset is measured against (its own offset is\n * 0). Must be one of `sources`' ids; default: the first source.\n */\n reference?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** One recording's measured clock offset in an {@link AudioSyncResult}. */\nexport interface AudioSyncOffset {\n /** The recording's `id` from the request. */\n sourceId: string\n /**\n * Where this recording sits on the reference's clock, in integer ms:\n * `referenceMs = sourceMs + offsetMs` (the EDL's D19 sign — the value\n * `EdlSource.offsetMs` takes when the reference is the master).\n */\n offsetMs: number\n /** 0–1. Below 0.5 a `notes` line asks for a check by ear. */\n confidence: number\n /**\n * Measured clock drift against the reference, in ms gained per hour; `null`\n * when the shared stretch was too short to measure it. Drift is reported,\n * never corrected — the offset is taken at the middle of the overlap.\n */\n driftMsPerHour: number | null\n}\n\n/**\n * An audio-sync job's `output_data.json`. It is NOT what\n * {@link EditResource.audioSync} returns (that is `{ jobId }`); fetch the\n * finished job and read its `output_data.json`.\n */\nexport interface AudioSyncResult {\n /** Wire version of the payload. */\n version: number\n /** The source every offset is measured against (its own offset is 0). */\n reference: string\n offsets: AudioSyncOffset[]\n /** Human-readable warnings: low confidence, measured drift, no shared sound. */\n notes: string[]\n}\n\nexport interface ApplyEdlInput {\n /** The edit decision list to render. Media resolves from each `edl.sources[i].url`. */\n edl: Edl\n /**\n * Optional media-URL overrides for `edl.sources[i].url`, POSITIONAL in this\n * array's order. Each URL is SSRF-guarded server-side like any media the\n * platform fetches.\n */\n sources?: string[]\n /**\n * Optional transcript to remap through the cut. The rendered job returns the\n * remapped transcript on its `json` output. A word-level multi-hour transcript\n * is several MB — if you only need the re-timed transcript (not a render), use\n * {@link EditResource.remapTranscript} locally instead of sending it here.\n */\n transcript?: Transcript\n /** Render a full video (default) or an audio-only cut. */\n output?: \"video\" | \"audio\"\n /** Fast proxy draft or the final render. Default \"final\". */\n quality?: \"proxy\" | \"final\"\n /**\n * Default crossfade (ms) on boundaries with no explicit transition; 0 = hard\n * cuts. Per-boundary clamped to the ffmpeg-xfade limit server-side. Default 0.\n */\n crossfadeMs?: number\n /**\n * The plan clip this render cuts — `edlSpanKey(clip)` from `@nodaro/shared`\n * (`\"<first inMs>-<last outMs>\"`). The job's result carries it back as\n * `output_data.clipKey`, so renders of a clip set can be matched to their\n * clips. Omit for a render of no plan clip.\n */\n clipKey?: string\n /**\n * The plan value this render cuts — `renderReadBasis(value)` from\n * `@nodaro/shared` (16 lowercase hex digits), where `value` is the Edit\n * Plan's Tighten EDL with any review applied, or the clip this render cuts.\n * The job's result carries it back as `output_data.planBasis`, so a take can\n * be told apart from one cut from an earlier plan. Send it only when the EDL\n * you send IS that value (not one an intermediate step re-cut from an older\n * plan); omit it otherwise. The result's `output_data.renderBasis` (the\n * render's own settings and sources) is stamped by the server.\n */\n planBasis?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * One media source for an edit-plan run. Mirrors the shape the workflow engine\n * sends: a stable `id` (adopted verbatim as the plan's `EdlSource` id), the\n * media `url`, and the `kind`, plus optional per-source annotations the planner\n * honors.\n */\nexport interface EditPlanSource {\n /** Stable id for this source — becomes the `EdlSource` id in the returned plan. */\n id: string\n /** The source's media URL (SSRF-guarded server-side). */\n url: string\n /** This source's medium. */\n kind: \"video\" | \"audio\"\n /** Role annotation. `\"master-audio\"` marks the clock the plan is reserved on. */\n role?: \"master-audio\" | \"camera\" | \"wide\" | \"screen\"\n /** Speaker labels present in this source. */\n speakers?: string[]\n /**\n * This source's origin on the master clock (masterMs = sourceMs + offsetMs).\n * Set by hand, it wins over a measured offset from `offsets`.\n */\n offsetMs?: number\n}\n\nexport interface EditPlanInput {\n /**\n * Planning mode. The finished job's `output_data` carries the plan — read it\n * with {@link unwrapEditPlanOutput}, which also strips the relay's\n * `viaNodaroCloud` marker:\n * - `\"tighten\"` → an `Edl`.\n * - `\"clips\"` → an {@link EdlClipSet} (`{ version, clips: Edl[] }`);\n * `unwrapEditPlanOutput` returns the bare `Edl[]`.\n * - `\"chapters\"` → a {@link ChapterSet} (`{ version, chapters: [...] }`).\n * - `\"trailer\"` → an `Edl` (one short teaser cut from the strongest moments).\n */\n mode: EditPlanMode\n /** Reasoning tier — affects plan quality AND the credit bucket. */\n planTier: EditPlanTier\n /** The timed transcript driving the plan. */\n transcript: Transcript\n /**\n * Optional detected silence ranges. Pass the silence-detect JOB'S RESULT —\n * `getStatus(jobId).data.output_data.json` (a {@link SilenceRanges} object),\n * NOT the `{ jobId }` that {@link EditResource.silenceDetect} returns. The\n * planner reads the `ranges` array; an input without one is silently ignored.\n */\n silence?: SilenceRanges\n /** The recording's media sources (1–6). */\n sources: EditPlanSource[]\n /**\n * Multicam: an {@link AudioSyncResult} (the audio-sync job's\n * `output_data.json`) measured over these same source ids. Each source's\n * measured offset is written onto its `offsetMs` BEFORE the request, on the\n * master's clock (the `master-audio` source, else the first) — a source's\n * own `offsetMs` wins. `editPlan` rejects with a {@link NodaroError}\n * (`code: \"edit_plan_sources\"`), before any request or charge, when a\n * source was not measured or matched weakly (confidence < 0.5 — set its\n * `offsetMs` by hand), or the master was not measured.\n */\n offsets?: AudioSyncResult | string\n /**\n * The id of the source the transcript was made from, when you know it. The\n * plan follows the master's clock, so a transcript made from a source that\n * is off it is rejected the same way.\n */\n transcriptSourceId?: string\n /** Free-text editing steer. */\n instructions?: string\n /** Style-guide text applied to the plan. */\n styleGuide?: string\n /** `\"clips\"` mode only: how many clips to cut. */\n count?: number\n /** `\"clips\"` mode only: target duration per clip, in seconds. */\n targetDurationSec?: number\n /** Target aspect for cut clips (`\"clips\"`) or the teaser (`\"trailer\"`). */\n targetAspect?: \"16:9\" | \"9:16\" | \"1:1\" | \"4:5\"\n /** Target platform hint (e.g. a social platform name). */\n platform?: string\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/** Input for {@link EditResource.cameraSwitch} (podcast B5). */\nexport interface CameraSwitchInput {\n /** One EDL — an `editPlan` result (`unwrapEditPlanOutput`), not a clip set. */\n edl: Edl\n /** The word transcript WITH speaker labels (transcribe with speaker detection). */\n transcript: Transcript\n /** Speaker label → the EDL source id of that speaker's camera. A speaker\n * left out shows the source whose `speakers` list names them, else the\n * `wide` camera, else any camera that filmed them. */\n speakerMap?: Record<string, string>\n /** A {@link EditResource.speakerMap} job's `output_data.json`: a label in `speakerMap`\n * wins, then this result, then the n-th speaker heard gets the n-th camera. */\n speakerMapResult?: SpeakerMapResult | string\n /** Speaker label → display name (on the segments and the returned transcript). */\n speakerNames?: Record<string, string>\n /** The shortest shot in ms (default 2 500): a shorter turn holds the shot. */\n minShotMs?: number\n /** Cut this long before the new speaker starts, ms (default 200). */\n leadMs?: number\n /** With a `wide` source: break to it after this long on one camera, ms (default 20 000). */\n maxShotMs?: number\n /** With a `wide` source: every N-th cut goes to the wide (default 0 = off). */\n wideEvery?: number\n /** Overlapping speech → a layout hint (default false; Apply EDL renders cut-only edits). */\n layoutHints?: boolean\n /** Optionally associate this run with a workflow execution (display only). */\n workflowId?: string\n}\n\n/**\n * The podcast use-case node types, each one a route (`POST /v1/<type>`, and\n * `POST /v1/<type>/continue` to continue one of its runs).\n */\nexport { USE_CASE_NODE_TYPES, type UseCaseNodeType }\n\n/** The route that continues a run of `type`. */\nconst continuePathOf = (type: UseCaseNodeType): `/v1/${UseCaseNodeType}/continue` => `/v1/${type}/continue`\n\n/** What a use-case run (`tightenEpisode`, `episodeChapters`, `episodeTrailer`, `clipPack`) and `continueUseCase` answer: the run's job to poll. */\nexport interface UseCaseJobResult extends EditJobResult {\n /** The run's execution (absent on a deduplicated retry). */\n executionId?: string\n /** `true` when an `Idempotency-Key` retry found the run already started: `jobId` is that run. */\n deduped?: boolean\n}\n\n/** What `tightenEpisode` answers (a {@link UseCaseJobResult}). */\nexport type TightenEpisodeJobResult = UseCaseJobResult\n\n/**\n * What a use-case run is estimated at (`data`). Every credit amount is 0\n * on an edition without credits; `durationSec` is `null` when the recording's\n * length could not be read.\n */\nexport interface UseCaseEstimate {\n /** The whole run: its steps plus the fee. */\n totalCredits: number\n /** The steps at their own rates. */\n stepsCredits: number\n /** The per-run fee. */\n feeCredits: number\n /** The recording's length, in seconds. */\n durationSec: number | null\n}\n\n/** What a Tighten Episode run is estimated at (a {@link UseCaseEstimate}). */\nexport type TightenEpisodeEstimate = UseCaseEstimate\n\n/** Input for {@link EditResource.continueUseCase}. */\nexport interface UseCaseContinueInput {\n /** The run to continue: the job `tightenEpisode`, `episodeChapters`, `episodeTrailer` or `clipPack` (or an earlier continuation) answered. */\n jobId: string\n /**\n * `\"from-failed\"` re-runs what did not finish of a failed run;\n * `\"render-final\"` renders the full-quality result after a review-on preview\n * (a Chapters run has no preview: it is refused `render_final_not_preview`;\n * a Clip Pack edit that drops every clip is refused `use_case_nothing_kept`).\n */\n mode: \"from-failed\" | \"render-final\"\n /** Render final only: the reviewer's edit of the plan (an EDL). */\n editedEdl?: unknown\n /**\n * The run's node type: its continuation goes to `POST /v1/<nodeType>/continue`.\n * Optional: when absent the job is read first (`GET /v1/jobs/:id`, which\n * needs the `jobs:read` scope on an app token) for the type the run records.\n * Required when that read cannot answer: a key without `jobs:read`, or a job\n * that records no use-case type (the call rejects with `use_case_type_unknown`).\n */\n nodeType?: UseCaseNodeType\n}\n\n/** Per-call options for the use-case requests (Tighten Episode, Chapters, Trailer, Clip Pack, and their continuations). */\nexport interface UseCaseRequestOptions {\n /**\n * Sent as the `Idempotency-Key` header. Reuse the same value when retrying\n * the same request and the server answers the run it already started\n * (`deduped: true`) instead of starting a second one.\n */\n idempotencyKey?: string\n}\n\nfunction idempotencyHeaders(options?: UseCaseRequestOptions): { headers?: Record<string, string> } {\n return options?.idempotencyKey ? { headers: { \"Idempotency-Key\": options.idempotencyKey } } : {}\n}\n\n/**\n * Editorial primitives for podcast / long-form video editing, single-camera\n * and multicam: measure the cameras' offsets ({@link audioSync}), plan the cut\n * ({@link editPlan}), put each cut on the speaker's camera\n * ({@link cameraSwitch}), render a Preview to review (`applyEdl` with\n * `quality: \"proxy\"`), then the final (`quality: \"final\"`).\n *\n * - {@link silenceDetect}, {@link audioSync} and {@link applyEdl} are core nodes\n * available on every edition.\n * - {@link editPlan} and {@link cameraSwitch} run on nodaro.ai; a self-hosted\n * install relays them once it is connected (a 503\n * `code: \"nodaro_connection_required\"` otherwise).\n * - {@link tightenEpisode} runs the whole chain (transcribe, plan, render, and\n * optionally captions, music and framing) in one call. It is core, but a\n * self-hosted install needs a nodaro.ai connection for the steps that run\n * there (a 503 `code: \"nodaro_connection_required\"` otherwise).\n * {@link continueUseCase} continues one of its runs.\n * - {@link episodeChapters} lists a recording's chapters (transcribe, then the\n * plan in chapters mode) in one call; nothing renders. Like\n * {@link tightenEpisode}, a self-hosted install needs a nodaro.ai connection.\n * - {@link episodeTrailer} cuts a 20–40 s trailer of a recording and delivers\n * it in each chosen format in one call; the same connection applies.\n * - {@link clipPack} cuts a recording into a pack of short clips (framed and\n * captioned by default) in one call; the same connection applies.\n * - {@link remapTranscript} is a PURE local transform (no request) — the same\n * remap `applyEdl` performs on its `transcript`, exposed for callers that hold\n * an EDL and a transcript and only want the re-timed transcript.\n *\n * Every request method except the estimates ({@link estimateTightenEpisode},\n * {@link estimateEpisodeChapters}, {@link estimateEpisodeTrailer},\n * {@link estimateClipPack}: a quote, `{ data }`) returns\n * {@link EditJobResult} (the use-case runs a {@link UseCaseJobResult}); poll the\n * `jobId` with `client.jobs.getStatus(jobId)`.\n */\nexport class EditResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * Detect silence ranges in an audio/video source (`POST /v1/silence-detect`).\n * Keyless — one ffmpeg pass over the source's audio proxy.\n */\n silenceDetect(input: SilenceDetectInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/silence-detect\", {\n body: {\n audioUrl: input.audioUrl,\n ...(input.thresholdDb !== undefined ? { thresholdDb: input.thresholdDb } : {}),\n ...(input.minSilenceMs !== undefined ? { minSilenceMs: input.minSilenceMs } : {}),\n ...(input.padMs !== undefined ? { padMs: input.padMs } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Measure how far apart the clocks of 2–6 recordings of one conversation are\n * (`POST /v1/audio-sync`), by cross-correlating their audio — keyless, one\n * ffmpeg decode per source. The finished job's `output_data.json` is an\n * {@link AudioSyncResult}. A malformed request (fewer than 2 or more than 6\n * sources, a repeated id, a `reference` that is not one of the ids) throws a\n * typed `NodaroError` (400, `code: \"validation_error\"`) before any credits\n * are reserved.\n */\n audioSync(input: AudioSyncInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/audio-sync\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url })),\n ...(input.reference !== undefined ? { reference: input.reference } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Which camera each speaker is on, from the cameras' own microphones (`POST\n * /v1/speaker-map`), keyless. `output_data.json` is a `SpeakerMapResult`: pass\n * it to {@link cameraSwitch} as `speakerMapResult`. A malformed request throws\n * a `NodaroError` (400 `validation_error`; 422 `no_speakers` for a transcript\n * with no speaker labels) before any credits are reserved.\n */\n speakerMap(input: SpeakerMapInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/speaker-map\", {\n body: {\n sources: input.sources.map((s) => ({ id: s.id, url: s.url, ...(s.kind ? { kind: s.kind } : {}) })),\n transcript: input.transcript,\n ...(input.offsets !== undefined ? { offsets: input.offsets } : {}),\n ...(input.masterSourceId !== undefined ? { masterSourceId: input.masterSourceId } : {}),\n ...(input.wideSourceId !== undefined ? { wideSourceId: input.wideSourceId } : {}),\n ...(input.marginDb !== undefined ? { marginDb: input.marginDb } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Render an edit decision list into a video or audio cut (`POST /v1/apply-edl`).\n * The EDL is validated at ingress — an unresolvable source or a picture-less\n * segment on a video edit throws a typed `NodaroError` (400, `code:\n * \"invalid_edl\"`) before any credits are reserved.\n */\n applyEdl(input: ApplyEdlInput): Promise<EditJobResult> {\n return this.client.request(\"POST\", \"/v1/apply-edl\", {\n body: {\n edl: input.edl,\n ...(input.sources !== undefined ? { sources: input.sources } : {}),\n ...(input.transcript !== undefined ? { transcript: input.transcript } : {}),\n ...(input.output !== undefined ? { output: input.output } : {}),\n ...(input.quality !== undefined ? { quality: input.quality } : {}),\n ...(input.crossfadeMs !== undefined ? { crossfadeMs: input.crossfadeMs } : {}),\n ...(input.clipKey !== undefined ? { clipKey: input.clipKey } : {}),\n ...(input.planBasis !== undefined ? { planBasis: input.planBasis } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Tighten Episode (`POST /v1/tighten-episode`): tighten a podcast recording\n * in one call — transcribe, cut the silences and filler, render, with\n * optional captions, music and framing. Answers the run's job to poll; its\n * `output_data` holds the episode. The run is priced as its steps at their own\n * rates plus the listed per-run fee. Quote it first with\n * {@link estimateTightenEpisode}.\n */\n tightenEpisode(input: TightenEpisodeInput, options?: UseCaseRequestOptions): Promise<TightenEpisodeJobResult> {\n return this.client.request(\"POST\", \"/v1/tighten-episode\", { body: tightenBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Tighten Episode run is estimated at (`POST /v1/tighten-episode/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The estimate leans high: the render is priced at the recording's\n * full length, while the charge follows the minutes actually rendered.\n */\n estimateTightenEpisode(input: TightenEpisodeInput): Promise<{ data: TightenEpisodeEstimate }> {\n return this.client.request(\"POST\", \"/v1/tighten-episode/estimate\", { body: tightenBody(input) })\n }\n\n /**\n * Chapters (`POST /v1/episode-chapters`): transcribe a recording and list its\n * chapters in one call. Answers the run's job to poll; the finished job's\n * `output_data.text` is the list as show notes (one `0:00 Title` line per\n * chapter; once a chapter starts at 1:00:00 or later, every line is\n * `H:MM:SS`), `output_data.json` the list itself (`{ startMs, title }[]`, the\n * same rows; the first starts at 0). A run that finds no chapters fails,\n * and its fee is refunded. Priced as its steps at their own rates plus the\n * listed per-run fee; quote it first with {@link estimateEpisodeChapters}.\n */\n episodeChapters(input: EpisodeChaptersInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return this.client.request(\"POST\", \"/v1/episode-chapters\", { body: chaptersBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Chapters run is estimated at (`POST /v1/episode-chapters/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. A recording whose length cannot be read is estimated at the\n * 180-minute ceiling.\n */\n estimateEpisodeChapters(input: EpisodeChaptersInput): Promise<{ data: UseCaseEstimate }> {\n return this.client.request(\"POST\", \"/v1/episode-chapters/estimate\", { body: chaptersBody(input) })\n }\n\n /**\n * Trailer (`POST /v1/episode-trailer`): cut a 20–40 s trailer of a recording\n * and deliver it in each chosen format (padded, never cropped), optionally\n * after an intro card, in one call. The finished job's `output_data.formats`\n * is `{ format, url }[]` in format order, the first also `output_data.videoUrl`;\n * a format that failed is named in `output_data.failedFormats` (the run keeps\n * the others). With `reviewBeforeRender` the run stops at a preview\n * (`videoUrl`, no formats yet). Priced as its steps at their own rates plus\n * the listed per-run fee; quote it first with {@link estimateEpisodeTrailer}.\n */\n episodeTrailer(input: EpisodeTrailerInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return this.client.request(\"POST\", \"/v1/episode-trailer\", { body: trailerBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Trailer run is estimated at (`POST /v1/episode-trailer/estimate`):\n * its steps at their own rates plus its per-run fee. Nothing is created or\n * charged. The trailer is quoted at 40 seconds, the longest trailer the\n * plan cuts; a Render final is quoted on the trailer the run cut (with the\n * review's edits), plus the intro card.\n */\n estimateEpisodeTrailer(input: EpisodeTrailerInput): Promise<{ data: UseCaseEstimate }> {\n return this.client.request(\"POST\", \"/v1/episode-trailer/estimate\", { body: trailerBody(input) })\n }\n\n /**\n * Clip Pack (`POST /v1/clip-pack`): cut a recording into a pack of short\n * clips in one call — the plan finds the best moments (`count`, default 8,\n * of about `targetDurationSec`, default 60), each is rendered (framed 9:16\n * with the speakers stacked by default; `framing: null` keeps the\n * recording's shape) and captioned. An audio recording makes audio clips.\n * The finished job's `output_data.clips` is `{ url, title?, hook?, clipKey,\n * thumbnailUrl?, captionsFailed? }[]` in plan order, the first also\n * `output_data.videoUrl` (`audioUrl` for audio clips). Every clip is tried:\n * one that cannot be rendered is skipped, not charged and counted on\n * `failedClips` (the pack keeps the others and its fee); a clip whose\n * captions failed comes uncaptioned (`captionsFailed`, counted on\n * `failedCaptions`). When no clip renders the run fails and its fee is\n * refunded. With `reviewBeforeRender` the run stops at previews; Render\n * final ({@link continueUseCase}) renders only the clips the edit keeps.\n * Priced as its steps at their own rates plus the listed per-run fee;\n * quote it first with {@link estimateClipPack}.\n */\n clipPack(input: ClipPackInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n return this.client.request(\"POST\", \"/v1/clip-pack\", { body: clipPackBody(input), ...idempotencyHeaders(options) })\n }\n\n /**\n * What a Clip Pack run is estimated at (`POST /v1/clip-pack/estimate`): its\n * steps at their own rates plus its per-run fee. Nothing is created or\n * charged. Each clip is quoted at twice the target length (the plan may cut\n * a clip longer than asked); the charge follows each clip's real length.\n */\n estimateClipPack(input: ClipPackInput): Promise<{ data: UseCaseEstimate }> {\n return this.client.request(\"POST\", \"/v1/clip-pack/estimate\", { body: clipPackBody(input) })\n }\n\n /**\n * Continue a use-case run as a new job (`POST /v1/<type>/continue`, the\n * run's own type: `input.nodeType`, else the type the job records, read with\n * `GET /v1/jobs/:id`; when that read cannot tell, this rejects with a\n * `NodaroError` coded `use_case_type_unknown` — pass `nodeType` then):\n * `\"from-failed\"` keeps the steps that finished and runs the rest (the fee\n * is held again; the failed run's was refunded);\n * `\"render-final\"` renders the full-quality result after a review-on preview,\n * with the preview's settings and no fee. A refusal that names another job\n * (`already_running`, `continuation_superseded`,\n * `render_final_already_rendered`) throws a `ContinuationRefusedError`\n * carrying that `jobId`.\n */\n async continueUseCase(input: UseCaseContinueInput, options?: UseCaseRequestOptions): Promise<UseCaseJobResult> {\n if (input.nodeType !== undefined && !isUseCaseNodeType(input.nodeType)) {\n throw new NodaroError(\n `continueUseCase: nodeType \"${String(input.nodeType)}\" is not a use-case node type (${USE_CASE_NODE_TYPES.join(\", \")})`,\n \"invalid_node_type\",\n 400,\n )\n }\n const type = input.nodeType ?? (await this.runTypeOf(input.jobId))\n return this.client.request(\"POST\", continuePathOf(type), {\n body: { jobId: input.jobId, mode: input.mode, ...(input.editedEdl !== undefined ? { editedEdl: input.editedEdl } : {}) },\n ...idempotencyHeaders(options),\n })\n }\n\n /**\n * The use-case type a run records (`input_data.type`, else its `job_type`).\n * When the key lacks the `jobs:read` scope (403 naming that scope) or the job\n * names no use-case type, the type cannot be read: this rejects with a\n * `NodaroError` (`use_case_type_unknown`, 400) asking for `nodeType`, and\n * nothing is posted (decided 2026-10-18: no route is guessed for the run).\n * Any other failure of the read, any other 403 included, is thrown as is.\n */\n private async runTypeOf(jobId: string): Promise<UseCaseNodeType> {\n const unknown = () =>\n new NodaroError(\"continueUseCase: the run's node type cannot be read — pass nodeType\", \"use_case_type_unknown\", 400)\n let row: { job_type?: unknown; input_data?: unknown } | undefined\n try {\n row = (await this.client.request<{ data?: { job_type?: unknown; input_data?: unknown } }>(\"GET\", `/v1/jobs/${encodeURIComponent(jobId)}`)).data\n } catch (err) {\n if (err instanceof ForbiddenError && err.missingScope === \"jobs:read\") throw unknown()\n throw err\n }\n const input = row?.input_data\n const recorded = input && typeof input === \"object\" && !Array.isArray(input) ? (input as { type?: unknown }).type : undefined\n const type = typeof recorded === \"string\" && recorded.length > 0 ? recorded : row?.job_type\n if (!isUseCaseNodeType(type)) throw unknown()\n return type\n }\n\n /**\n * Plan a transcript-driven cut (`POST /v1/edit-plan`). Reads a timed\n * transcript (plus optional silence ranges) and the media sources, and plans\n * the edit. The finished job's `output_data` holds the plan: an `Edl`\n * (`\"tighten\"` or `\"trailer\"`), an {@link EdlClipSet} (`\"clips\"`), or a\n * {@link ChapterSet} (`\"chapters\"`) — normalize it with {@link unwrapEditPlanOutput}.\n *\n * On a self-hosted install the request relays to nodaro.ai and needs the\n * install connected (a 503 `code: \"nodaro_connection_required\"` otherwise);\n * on nodaro.ai it runs directly.\n */\n editPlan(input: EditPlanInput): Promise<EditJobResult> {\n // The same pre-dispatch check the canvas and MCP run (B4): the server never\n // sees `offsets` — they are written onto the sources here.\n const planned = resolveEditPlanSources(input.sources, {\n offsets: input.offsets,\n transcriptSourceId: input.transcriptSourceId,\n })\n if (!planned.ok) {\n const message = planned.issues.map((issue) => describeAudioSyncOffsetIssue(issue)).join(\"; \")\n return Promise.reject(new NodaroError(`editPlan: ${message}`, \"edit_plan_sources\", 400))\n }\n const transcript = planned.transcriptSourceId\n ? { ...input.transcript, sourceId: planned.transcriptSourceId }\n : input.transcript\n return this.client.request(\"POST\", \"/v1/edit-plan\", {\n body: {\n mode: input.mode,\n planTier: input.planTier,\n transcript,\n sources: planned.sources,\n ...(input.silence !== undefined ? { silence: input.silence } : {}),\n ...(input.instructions !== undefined ? { instructions: input.instructions } : {}),\n ...(input.styleGuide !== undefined ? { styleGuide: input.styleGuide } : {}),\n ...(input.count !== undefined ? { count: input.count } : {}),\n ...(input.targetDurationSec !== undefined ? { targetDurationSec: input.targetDurationSec } : {}),\n ...(input.targetAspect !== undefined ? { targetAspect: input.targetAspect } : {}),\n ...(input.platform !== undefined ? { platform: input.platform } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Camera Switch (podcast B5): choose which camera shows each cut of `edl`\n * by who is speaking — the sound never changes. Cloud edition; a flat\n * price. The finished job's `output_data.json` is the switched EDL (feed it\n * to {@link applyEdl}) and `output_data.transcript` the transcript with\n * `speakerNames` applied. A transcript with no speaker labels rejects with\n * a {@link NodaroError} (`code: \"no_speakers\"`) before any request or charge.\n */\n cameraSwitch(input: CameraSwitchInput): Promise<EditJobResult> {\n if (transcriptSpeakerLabels(input.transcript).length === 0) {\n return Promise.reject(new NodaroError(\n \"cameraSwitch: the transcript has no speaker labels — transcribe with speaker detection on\",\n \"no_speakers\",\n 422,\n ))\n }\n const result = input.speakerMapResult\n if (result !== undefined && !speakerMapResultOf(result)) return Promise.reject(new NodaroError(\"cameraSwitch: speakerMapResult is not a Speaker Map result — pass that job's output_data.json\", \"not_a_speaker_map\", 400))\n const speakerMap = result !== undefined ? cameraSwitchSpeakerMap({ speakerMap: input.speakerMap }, input.edl, input.transcript, { speakerMap: result }) : input.speakerMap\n return this.client.request(\"POST\", \"/v1/camera-switch\", {\n body: {\n edl: input.edl,\n transcript: input.transcript,\n ...(speakerMap !== undefined ? { speakerMap } : {}),\n ...(input.speakerNames !== undefined ? { speakerNames: input.speakerNames } : {}),\n ...(input.minShotMs !== undefined ? { minShotMs: input.minShotMs } : {}),\n ...(input.leadMs !== undefined ? { leadMs: input.leadMs } : {}),\n ...(input.maxShotMs !== undefined ? { maxShotMs: input.maxShotMs } : {}),\n ...(input.wideEvery !== undefined ? { wideEvery: input.wideEvery } : {}),\n ...(input.layoutHints !== undefined ? { layoutHints: input.layoutHints } : {}),\n ...(input.workflowId !== undefined ? { workflowId: input.workflowId } : {}),\n },\n })\n }\n\n /**\n * Remap a transcript through an EDL — a PURE client-side transform (NO\n * request). Returns a new transcript whose word (and segment) timings are on\n * the EDL's rendered output clock, dropping words that fall in cut regions and\n * clipping straddlers. Runs `@nodaro/shared`'s canonical\n * `remapTranscriptThroughEdl`, the same remap `applyEdl` performs server-side.\n */\n remapTranscript(edl: Edl, transcript: Transcript): Transcript {\n return remapTranscriptThroughEdl(edl, transcript)\n }\n}\n"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/resources/credits.ts"],"names":[],"mappings":";AA2KA,IAAM,iBAAA,GAAoB,EAAA;AAEnB,IAAM,kBAAN,MAAsB;AAAA,EAC3B,YAAoB,MAAA,EAA0B;AAA1B,IAAA,IAAA,CAAA,MAAA,GAAA,MAAA;AAAA,EAA2B;AAAA,EAA3B,MAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOpB,MAAM,OAAA,GAAgC;AACpC,IAAA,MAAM,GAAA,GAAM,MAAM,IAAA,CAAK,MAAA,CAAO,OAAA;AAAA,MAC5B,KAAA;AAAA,MACA;AAAA,KACF;AACA,IAAA,OAAO,GAAA,CAAI,IAAA;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,YAAA,CAAa,OAAA,GAAqC,EAAC,EAAoC;AACrF,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAgC,KAAA,EAAO,0BAAA,EAA4B;AAAA,MACpF,OAAO,EAAE,KAAA,EAAO,QAAQ,KAAA,EAAO,MAAA,EAAQ,QAAQ,MAAA;AAAO,KACvD,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,WAAW,GAAA,EAA0C;AACnD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAA0B,MAAA,EAAQ,yBAAA,EAA2B;AAAA,MAC9E,MAAM,EAAE,MAAA,EAAQ,IAAI,KAAA,CAAM,CAAA,EAAG,iBAAiB,CAAA;AAAE,KACjD,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,gBAAA,CAAiB,IAAA,GAAmB,EAAC,EAAoC;AACvE,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAgC,MAAA,EAAQ,gCAAA,EAAkC;AAAA,MAC3F,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,iBAAA,CAAkB,KAAA,GAAgC,EAAC,EAAqC;AACtF,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAiC,MAAA,EAAQ,iCAAA,EAAmC;AAAA,MAC7F,IAAA,EAAM;AAAA,KACP,CAAA;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,oBAAA,GAAwD;AACtD,IAAA,OAAO,IAAA,CAAK,MAAA,CAAO,OAAA,CAAgC,MAAA,EAAQ,gCAAgC,CAAA;AAAA,EAC7F;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AASF","file":"chunk-XQAIEPAZ.js","sourcesContent":["import type { NodaroClientCore } from \"../core.js\"\n\n/**\n * Authenticated user's credit balance — the shape of `GET /v1/user/credits`'s\n * `data` field. Mirrors the canonical `UserBalance` from the backend billing\n * module (`backend/src/ee/billing/credits.ts`), the source of truth: keep this\n * in sync if that interface changes.\n */\nexport interface UserBalance {\n total: number\n subscription: number\n topup: number\n dailySpent: number\n dailyLimit: number | null\n monthlyAllocation: number\n /** Stored billing tier. Display should prefer `effectiveTier`. */\n tier: string\n /** Derived entitlement tier: \"payg\" when stored-free with purchased credits. */\n effectiveTier: string\n features: Record<string, unknown>\n periodEnd: string | null\n /** Credits earned for app usage (free tier only — earned by running flows). */\n appCreditsAllowance: number\n /** Present when a deployment uses an external shared wallet. Raw credits;\n * null means unavailable. Do not substitute the personal `total` balance. */\n externalWallet?: { available: number | null }\n /**\n * Where the account's free signup grant stands. `\"withheld\"` means the\n * account works but the grant did not land — show the activation path.\n * Absent where the deployment has no signup grant.\n */\n freeGrantState?: FreeGrantState\n /**\n * Present only with `freeGrantState: \"withheld\"`, when the claim showed no\n * browser signals at all: the app may say \"free credits need a regular\n * browser — reload\", because a reload in one judges the account again.\n */\n freeGrantNotice?: \"browser_unmeasured\"\n /**\n * The welcome-credits opt-in. PRESENT only while the deployment has the\n * offer switched on — absent means \"no popup, no banner, no block\".\n */\n welcomeOffer?: WelcomeOfferState\n}\n\n/** Where an account's free signup grant stands. */\nexport type FreeGrantState = \"unclaimed\" | \"granted\" | \"withheld\" | \"revoked\"\n\n/** `UserBalance.welcomeOffer` — what an app needs to show the welcome offer. */\nexport interface WelcomeOfferState {\n /** The one-time popup was already shown (see `markWelcomeOfferSeen`). */\n popupSeen: boolean\n /**\n * The account already has its credits (granted elsewhere) but still owes\n * the email consent: the web apps block creation until it is given — the\n * claim records it.\n */\n consentPending: boolean\n /** The credits a claim grants. Name this figure in the offer; never hard-code it. */\n credits: number\n}\n\n/** What `claimWelcomeOffer` sends: the browser signals plus the app answering. */\nexport interface WelcomeOfferClaimInput extends SignupKeys {\n /**\n * The answering app's slug (lowercase letters, digits and `-`, up to 40\n * characters), recorded with the consent. A value outside that grammar is\n * stored as absent, never refused.\n */\n sourceApp?: string\n}\n\n/** The answer of `POST /v1/credits/welcome-offer/claim`. */\nexport interface WelcomeOfferClaimResult {\n /** The email consent is recorded. */\n consent: \"granted\"\n /** Where the free grant stands after the claim — `\"withheld\"` when the abuse gate refused it. */\n grant: FreeGrantState\n /** The grant's figure. A grant tops the subscription credits UP TO it (it is not added on top), so a claim that lands it leaves the subscription credits at no less than this. */\n credits: number\n}\n\n/** The answer of `POST /v1/credits/welcome-offer/seen`. */\nexport interface WelcomeOfferSeenResult {\n ok: true\n}\n\n/**\n * Result of `POST /v1/credits/model-costs` — a batch cost lookup for editor\n * cost previews. `data` maps each priced identifier → its credit cost.\n *\n * Per-model fault isolation (the route runs the lookups under\n * `Promise.allSettled`): identifiers with no pricing row are reported in\n * `missing` (undisplayable until an operator seeds a price) and lookup\n * failures in `errors`, instead of failing the whole batch. Callers typically\n * render `'—'` for any identifier that lands in `missing`. The hard-fail\n * policy still triggers at reservation time when the user actually runs the\n * node — this preview lookup is intentionally lenient.\n */\nexport interface ModelCostsResult {\n data: Record<string, number>\n missing: string[]\n errors: string[]\n}\n\n/**\n * The two browser signals a claim reports — SHA-256 hex digests computed by\n * `@nodaro/sdk/signup-signals` (`collectSignupKeys`). Either may be absent:\n * a browser that cannot be measured still claims, and the server treats the\n * absence as what it is. Any other value is stored as absent server-side.\n */\nexport interface SignupKeys {\n browserKey?: string\n deviceKey?: string\n}\n\n/** The answer of `POST /v1/credits/claim-signup-grant`. */\nexport interface SignupGrantClaimResult {\n /** Where the account's free grant stands after this call. */\n state: FreeGrantState\n /** True only when THIS call moved the credits — a repeat is `false` with the same state. */\n granted: boolean\n}\n\n/**\n * One row of `GET /v1/credits/transactions` — a run's reservation and how it\n * settled.\n *\n * Which amount to read: a run first HOLDS credits and is then settled — charged\n * all or part of the hold, the rest returned. `credits_charged` is what the run\n * cost (`null` while the hold is still open, `0` once refunded); sum it for\n * spend. `credits_reserved` is the hold, which never changes. `credits_used`\n * is the same hold under its original name, kept for existing integrations.\n */\nexport interface CreditTransaction {\n id: string\n /** ISO-8601. Also the paging cursor: pass the last row's value as `cursor`. */\n created_at: string\n /** Settled charge; `null` while `status` is `\"reserved\"`, `0` when refunded. */\n credits_charged: number | null\n /** The hold taken when the run was accepted. */\n credits_reserved: number | null\n /** The hold, under its original name. Prefer `credits_charged`. */\n credits_used: number | null\n /** The model or operation the credits were for. */\n action: string | null\n provider: string | null\n /** Reservation lifecycle: `\"reserved\"`, `\"committed\"` or `\"refunded\"`. */\n status: string | null\n /** The caller's own billing details for the run (model, which pool paid, …). */\n metadata: Record<string, unknown>\n /** `\"workspace\"` when a class or team budget paid, not your balance. */\n payer: \"user\" | \"workspace\"\n workspaceId: string | null\n}\n\n/** A page of {@link CreditTransaction}s, newest first. */\nexport interface CreditTransactionsPage {\n data: CreditTransaction[]\n /** Pass as `cursor` for the next page; `null` when there are no more rows. */\n nextCursor: string | null\n}\n\nexport interface CreditTransactionsOptions {\n /** Rows per page, 1–50 (server default 20). */\n limit?: number\n /** The `nextCursor` of the previous page. */\n cursor?: string\n}\n\n/** Max identifiers per `model-costs` batch — the route caps the request at 50. */\nconst MODEL_COSTS_LIMIT = 50\n\nexport class CreditsResource {\n constructor(private client: NodaroClientCore) {}\n\n /**\n * `GET /v1/user/credits` → the authenticated user's credit balance and tier\n * info. Throws `UnauthorizedError` (401) when signed out, and the SDK's\n * other typed errors on the usual statuses.\n */\n async balance(): Promise<UserBalance> {\n const res = await this.client.request<{ data: UserBalance }>(\n \"GET\",\n \"/v1/user/credits\",\n )\n return res.data\n }\n\n /**\n * `GET /v1/credits/transactions` → one page of the caller's credit\n * transactions, newest first, with `nextCursor` for the next page (`null`\n * at the end). Read `credits_charged` for what each run cost — it can be\n * below the hold (`credits_reserved`) and is `null` while the hold is open.\n * Cloud-only route: a self-hosted instance answers 404.\n */\n transactions(options: CreditTransactionsOptions = {}): Promise<CreditTransactionsPage> {\n return this.client.request<CreditTransactionsPage>(\"GET\", \"/v1/credits/transactions\", {\n query: { limit: options.limit, cursor: options.cursor },\n })\n }\n\n /**\n * `POST /v1/credits/model-costs` → per-identifier credit cost, for editor\n * cost previews. Capped at the first {@link MODEL_COSTS_LIMIT} identifiers\n * (the route's request limit). Preserves the `{ data, missing, errors }`\n * fault-isolation shape verbatim (see {@link ModelCostsResult}).\n */\n modelCosts(ids: string[]): Promise<ModelCostsResult> {\n return this.client.request<ModelCostsResult>(\"POST\", \"/v1/credits/model-costs\", {\n body: { models: ids.slice(0, MODEL_COSTS_LIMIT) },\n })\n }\n\n /**\n * `POST /v1/credits/claim-signup-grant` → claims the account's free signup\n * grant, once, with the browser signals of the page making the claim.\n *\n * Call it once per page load as soon as the session is known, and never\n * wait on it for rendering: it is idempotent (a decided account answers\n * with its state and `granted: false`), it costs the user nothing when it\n * fails (the next load asks again), and the signals are what make the\n * claim worth sending — collect them with `collectSignupKeys()` from\n * `@nodaro/sdk/signup-signals` and pass whatever came back, `{}` included.\n * Cloud-only route: a self-hosted instance answers 404.\n */\n claimSignupGrant(keys: SignupKeys = {}): Promise<SignupGrantClaimResult> {\n return this.client.request<SignupGrantClaimResult>(\"POST\", \"/v1/credits/claim-signup-grant\", {\n body: keys,\n })\n }\n\n /**\n * `POST /v1/credits/welcome-offer/claim` → the user's \"yes\" to the welcome\n * offer, as ONE server action: the marketing-email consent is recorded,\n * then the free signup grant is claimed with that consent. The abuse gate\n * still decides — consent is necessary, not sufficient — so read `grant`:\n * `\"granted\"` (the grant has landed: this call topped the subscription credits up to `credits` — not added on top — or it had landed before), `\"withheld\"` (refused), `\"revoked\"` (taken back earlier; nothing moves), `\"unclaimed\"` (nothing moved — treat as a failure and let the user retry).\n * Pass the browser signals from `collectSignupKeys()` and your app's slug.\n *\n * **Browser session only.** Usable from a first-party web app signed in\n * with a Supabase session (the consent is a person's choice in a browser);\n * an API key or OAuth app token is refused with 403. 409\n * `welcome_offer_disabled` while the deployment has the offer off — show\n * it only when `balance().welcomeOffer` is present. Cloud-only route: a\n * self-hosted instance answers 404.\n */\n claimWelcomeOffer(input: WelcomeOfferClaimInput = {}): Promise<WelcomeOfferClaimResult> {\n return this.client.request<WelcomeOfferClaimResult>(\"POST\", \"/v1/credits/welcome-offer/claim\", {\n body: input,\n })\n }\n\n /**\n * `POST /v1/credits/welcome-offer/seen` → records that the one-time welcome\n * popup was shown, so `balance().welcomeOffer.popupSeen` turns true on every\n * app. Idempotent (the first stamp wins); a failed call only means the popup\n * may show once more, so callers usually ignore errors.\n *\n * **Browser session only**, like {@link claimWelcomeOffer}: 403 for an API\n * key or OAuth app token, 409 `welcome_offer_disabled` while the offer is\n * off, 404 on a self-hosted instance.\n */\n markWelcomeOfferSeen(): Promise<WelcomeOfferSeenResult> {\n return this.client.request<WelcomeOfferSeenResult>(\"POST\", \"/v1/credits/welcome-offer/seen\")\n }\n\n // NOTE: no `estimate(...)` helper. The backend exposes\n // `POST /v1/credits/estimate-workflow` (body `{ nodes }` → `{ data: {\n // totalCredits, nodeCount } }`), but no consumer has a settled shape for it\n // yet (studio pre-checks `balance >= Σ modelCosts` client-side rather than\n // calling an estimate endpoint). Adding it now would be inventing an API\n // surface ahead of a real caller, so it's deliberately omitted — add it when\n // a consumer needs it, shaped to that need.\n}\n"]}
@@ -1,3 +0,0 @@
1
- export { unwrapEditPlanOutput } from '@nodaro/shared/edl';
2
- //# sourceMappingURL=chunk-ZH4F2P2G.js.map
3
- //# sourceMappingURL=chunk-ZH4F2P2G.js.map