@nodaro/sdk 2.5.0 → 2.7.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.
package/dist/index.cjs CHANGED
@@ -146,6 +146,20 @@ var JobAbortedError = class extends NodaroError {
146
146
  }
147
147
  jobId;
148
148
  };
149
+ var StudioPreviewUnavailable = class extends NodaroError {
150
+ constructor(message = "This deployment does not preview operation batches; nothing was sent") {
151
+ super(message, "studio_preview_unavailable", 0);
152
+ this.name = "StudioPreviewUnavailable";
153
+ }
154
+ };
155
+ var StudioPreviewAppliedError = class extends NodaroError {
156
+ constructor(applied, message = applied ? "Asked for a preview and this deployment APPLIED the batch; the change is written \u2014 see `applied`" : "Asked for a preview and this deployment answered with neither one nor a body to read; the batch may have been applied \u2014 re-read the production before deciding anything") {
157
+ super(message, "studio_preview_applied", 0);
158
+ this.applied = applied;
159
+ this.name = "StudioPreviewAppliedError";
160
+ }
161
+ applied;
162
+ };
149
163
  var JobHeldError = class extends NodaroError {
150
164
  constructor(message, jobId) {
151
165
  super(message, "job_held", 0);
@@ -2037,6 +2051,47 @@ var LlmResource = class {
2037
2051
  }
2038
2052
  };
2039
2053
 
2054
+ // src/sse.ts
2055
+ async function* readSseStream(res, opts = {}) {
2056
+ if (!res.ok) {
2057
+ let errBody = {};
2058
+ try {
2059
+ errBody = await res.json();
2060
+ } catch {
2061
+ }
2062
+ throwFromResponse(res.status, errBody);
2063
+ }
2064
+ if (!res.body) {
2065
+ throw new NodaroError(`${opts.label ?? "event stream"} has no response body`, "empty_stream", res.status);
2066
+ }
2067
+ const reader = res.body.getReader();
2068
+ const decoder = new TextDecoder();
2069
+ let buffer = "";
2070
+ try {
2071
+ for (; ; ) {
2072
+ const { done, value } = await reader.read();
2073
+ if (done) break;
2074
+ buffer += decoder.decode(value, { stream: true });
2075
+ let sep;
2076
+ while ((sep = buffer.indexOf("\n\n")) >= 0) {
2077
+ const frame = buffer.slice(0, sep);
2078
+ buffer = buffer.slice(sep + 2);
2079
+ for (const line of frame.split("\n")) {
2080
+ if (!line.startsWith("data:")) continue;
2081
+ try {
2082
+ yield JSON.parse(line.slice(5).trim());
2083
+ } catch {
2084
+ }
2085
+ }
2086
+ }
2087
+ }
2088
+ } finally {
2089
+ reader.releaseLock();
2090
+ await res.body.cancel().catch(() => {
2091
+ });
2092
+ }
2093
+ }
2094
+
2040
2095
  // src/resources/media.ts
2041
2096
  var MediaResource = class {
2042
2097
  constructor(client) {
@@ -2073,43 +2128,7 @@ var MediaResource = class {
2073
2128
  headers: token ? { Authorization: `Bearer ${token}` } : {},
2074
2129
  signal: opts.signal
2075
2130
  });
2076
- if (!res.ok) {
2077
- let errBody = {};
2078
- try {
2079
- errBody = await res.json();
2080
- } catch {
2081
- }
2082
- throwFromResponse(res.status, errBody);
2083
- }
2084
- if (!res.body) {
2085
- throw new NodaroError("progress stream has no response body", "empty_stream", res.status);
2086
- }
2087
- const reader = res.body.getReader();
2088
- const decoder = new TextDecoder();
2089
- let buffer = "";
2090
- try {
2091
- for (; ; ) {
2092
- const { done, value } = await reader.read();
2093
- if (done) break;
2094
- buffer += decoder.decode(value, { stream: true });
2095
- let sep;
2096
- while ((sep = buffer.indexOf("\n\n")) >= 0) {
2097
- const frame = buffer.slice(0, sep);
2098
- buffer = buffer.slice(sep + 2);
2099
- for (const line of frame.split("\n")) {
2100
- if (!line.startsWith("data:")) continue;
2101
- try {
2102
- yield JSON.parse(line.slice(5).trim());
2103
- } catch {
2104
- }
2105
- }
2106
- }
2107
- }
2108
- } finally {
2109
- reader.releaseLock();
2110
- await res.body.cancel().catch(() => {
2111
- });
2112
- }
2131
+ yield* readSseStream(res, { label: "progress stream" });
2113
2132
  }
2114
2133
  /**
2115
2134
  * Copy an external media URL into your Nodaro storage (`POST /v1/save-to-storage`)
@@ -2154,6 +2173,31 @@ var MediaResource = class {
2154
2173
  imageOverlay(input) {
2155
2174
  return this.client.request("POST", "/v1/image-overlay", { body: input });
2156
2175
  }
2176
+ /**
2177
+ * Ask a vision model WHERE one overlay layer should sit on a base image
2178
+ * (`POST /v1/image-overlay/suggest-placement`) — it reads the picture and
2179
+ * keeps the element off the faces, the subject and the busiest texture.
2180
+ * The answer comes back in {@link MediaResource.imageOverlay}'s own percent
2181
+ * units — `anchor`, `x`/`y` offsets, `width` — so it drops straight onto a
2182
+ * layer (`const { reason, ...box } = placement`), plus a one-sentence
2183
+ * `reason` you can show a user. Nothing is composited here: apply the
2184
+ * placement yourself.
2185
+ *
2186
+ * `layerAspect` is the element's width / height (1 = square, the default) so
2187
+ * the proposed box stays in proportion; `intent` says what the element is
2188
+ * ("a logo", "a price badge"); `safeArea` is the always-visible region as
2189
+ * fractions of the canvas (a platform preset's safe area), which the
2190
+ * placement is kept inside. Unlike the other media calls this one answers
2191
+ * synchronously — there is nothing to poll; `jobId` is the billing record
2192
+ * (one image-to-text call).
2193
+ */
2194
+ suggestOverlayPlacement(input) {
2195
+ return this.client.request(
2196
+ "POST",
2197
+ "/v1/image-overlay/suggest-placement",
2198
+ { body: input }
2199
+ );
2200
+ }
2157
2201
  /**
2158
2202
  * Trim a video to a range (`POST /v1/trim-video`). Give the range in whichever
2159
2203
  * unit fits: `startTime`/`endTime` seconds, `trim*Frames`, `trim*Seconds`, or
@@ -2647,19 +2691,27 @@ var StudioProductionsResource = class {
2647
2691
  );
2648
2692
  return res.data;
2649
2693
  }
2650
- /**
2651
- * Apply a batch of operations. Atomic: one bad operation refuses the whole
2652
- * batch with a `StudioOpError` naming its index, and nothing is written. On
2653
- * success adopt `production` wholesale and carry `version` forward as the
2654
- * next `baseVersion`.
2655
- */
2656
2694
  async ops(productionId, input) {
2657
- const res = await this.client.request(
2695
+ if (input.dryRun !== true) {
2696
+ const res2 = await this.client.request(
2697
+ "POST",
2698
+ this.path(productionId, "/ops"),
2699
+ { body: input }
2700
+ );
2701
+ return res2.data;
2702
+ }
2703
+ const ping = await this.client.request(
2658
2704
  "POST",
2659
2705
  this.path(productionId, "/ops"),
2660
- { body: input }
2706
+ { body: { ops: [], dryRun: true } }
2661
2707
  );
2662
- return res.data;
2708
+ if (ping?.data?.dryRun !== true) throw new StudioPreviewUnavailable();
2709
+ const res = await this.client.request("POST", this.path(productionId, "/ops"), { body: input });
2710
+ const data = res?.data;
2711
+ if (data?.dryRun !== true) {
2712
+ throw new StudioPreviewAppliedError(data);
2713
+ }
2714
+ return data;
2663
2715
  }
2664
2716
  /**
2665
2717
  * Land every generation that has finished since you last looked, and report
@@ -2867,6 +2919,103 @@ var StudioResource = class {
2867
2919
  }
2868
2920
  };
2869
2921
 
2922
+ // src/resources/copilot.ts
2923
+ var COPILOT_FRAME_TYPES = /* @__PURE__ */ new Set([
2924
+ "metadata",
2925
+ "token",
2926
+ "tool_call",
2927
+ "workflow_updated",
2928
+ "workflow_created",
2929
+ "run_proposed",
2930
+ "memory_saved",
2931
+ "usage",
2932
+ "done",
2933
+ "error"
2934
+ ]);
2935
+ function isCopilotFrame(value) {
2936
+ if (typeof value !== "object" || value === null) return false;
2937
+ const type = value.type;
2938
+ return typeof type === "string" && COPILOT_FRAME_TYPES.has(type);
2939
+ }
2940
+ var CopilotResource = class {
2941
+ constructor(client) {
2942
+ this.client = client;
2943
+ }
2944
+ client;
2945
+ /**
2946
+ * Open a conversation (`POST /v1/copilot/threads`) — on `workflowId`, or on
2947
+ * a workflow the server creates from `prompt`. Re-opening a workflow that
2948
+ * already has an active thread answers THAT thread rather than a second one.
2949
+ */
2950
+ create(input) {
2951
+ return this.client.request("POST", "/v1/copilot/threads", { body: input });
2952
+ }
2953
+ /**
2954
+ * The active thread for a workflow (`GET /v1/copilot/threads?workflowId=…`),
2955
+ * or `null` when the user has none open on it.
2956
+ */
2957
+ list(params) {
2958
+ return this.client.request("GET", "/v1/copilot/threads", { query: { workflowId: params.workflowId } });
2959
+ }
2960
+ /**
2961
+ * One thread with its messages (`GET /v1/copilot/threads/:id`). `after` reads
2962
+ * only what followed that sequence number (the panel's catch-up); `limit`
2963
+ * caps the page at the server's own ceiling.
2964
+ */
2965
+ get(id, opts = {}) {
2966
+ return this.client.request("GET", `/v1/copilot/threads/${encodeURIComponent(id)}`, {
2967
+ query: { ...opts.after !== void 0 ? { after: opts.after } : {}, ...opts.limit !== void 0 ? { limit: opts.limit } : {} }
2968
+ });
2969
+ }
2970
+ /**
2971
+ * Close a conversation (`DELETE /v1/copilot/threads/:id`). Archival, not
2972
+ * deletion — the messages stay readable. Refused while a turn is running.
2973
+ */
2974
+ archive(id) {
2975
+ return this.client.request("DELETE", `/v1/copilot/threads/${encodeURIComponent(id)}`);
2976
+ }
2977
+ /**
2978
+ * Stop the running turn (`POST /v1/copilot/threads/:id/cancel`). Answers the
2979
+ * turn it asked to stop; the turn's own stream ends with a `done` frame.
2980
+ */
2981
+ cancel(id) {
2982
+ return this.client.request("POST", `/v1/copilot/threads/${encodeURIComponent(id)}/cancel`);
2983
+ }
2984
+ /**
2985
+ * Send one message and iterate the turn's frames
2986
+ * (`POST /v1/copilot/threads/:id/messages`, server-sent events).
2987
+ *
2988
+ * Deliberately NOT routed through `client.request`: that path arms an abort
2989
+ * timer around the whole exchange, and a turn legitimately runs for minutes —
2990
+ * inheriting `timeoutMs` would cut the assistant off mid-answer. The caller
2991
+ * owns the lifetime instead: pass `signal` to end the stream, or stop
2992
+ * iterating (the reader cancels the body, which ends the request).
2993
+ *
2994
+ * A frame kind this package does not model is skipped rather than thrown, so
2995
+ * a newer server cannot break an older caller.
2996
+ */
2997
+ async *stream(threadId, opts) {
2998
+ const url = `${this.client.baseUrl}/v1/copilot/threads/${encodeURIComponent(threadId)}/messages`;
2999
+ const token = await this.client.auth.getToken();
3000
+ const res = await this.client.fetch(url, {
3001
+ method: "POST",
3002
+ headers: {
3003
+ "Content-Type": "application/json",
3004
+ ...token ? { Authorization: `Bearer ${token}` } : {}
3005
+ },
3006
+ body: JSON.stringify({
3007
+ message: opts.message,
3008
+ ...opts.baseVersion !== void 0 ? { baseVersion: opts.baseVersion } : {},
3009
+ ...opts.tier ? { tier: opts.tier } : {}
3010
+ }),
3011
+ signal: opts.signal
3012
+ });
3013
+ for await (const value of readSseStream(res, { label: "copilot stream" })) {
3014
+ if (isCopilotFrame(value)) yield value;
3015
+ }
3016
+ }
3017
+ };
3018
+
2870
3019
  // src/resources/community.ts
2871
3020
  var CommunityResource = class {
2872
3021
  constructor(client) {
@@ -3248,7 +3397,7 @@ var WorkspacesResource = class {
3248
3397
  return this.client.requestText("GET", `/v1/workspaces/${encodeURIComponent(id)}/usage`, { query: { ...opts, format: "csv" } });
3249
3398
  }
3250
3399
  };
3251
- var SDK_VERSION = "2.5.0" ;
3400
+ var SDK_VERSION = "2.7.0" ;
3252
3401
  var CLIENT_HEADER = "X-Nodaro-Client";
3253
3402
  var isBrowser = () => typeof window !== "undefined" && typeof window.document !== "undefined";
3254
3403
  var NodaroClient = class _NodaroClient {
@@ -3300,6 +3449,7 @@ var NodaroClient = class _NodaroClient {
3300
3449
  shots;
3301
3450
  recast;
3302
3451
  studio;
3452
+ copilot;
3303
3453
  community;
3304
3454
  templates;
3305
3455
  tutorials;
@@ -3346,6 +3496,7 @@ var NodaroClient = class _NodaroClient {
3346
3496
  this.shots = new ShotsResource(this);
3347
3497
  this.recast = new RecastResource(this);
3348
3498
  this.studio = new StudioResource(this);
3499
+ this.copilot = new CopilotResource(this);
3349
3500
  this.community = new CommunityResource(this);
3350
3501
  this.templates = new TemplatesResource(this);
3351
3502
  this.tutorials = new TutorialsResource(this);
@@ -3598,6 +3749,7 @@ exports.CallbackAuth = CallbackAuth;
3598
3749
  exports.CatalogsResource = CatalogsResource;
3599
3750
  exports.CharactersResource = CharactersResource;
3600
3751
  exports.CommunityResource = CommunityResource;
3752
+ exports.CopilotResource = CopilotResource;
3601
3753
  exports.CreaturesResource = CreaturesResource;
3602
3754
  exports.CreditsResource = CreditsResource;
3603
3755
  exports.DeveloperAppsResource = DeveloperAppsResource;
@@ -3635,6 +3787,8 @@ exports.ShotsResource = ShotsResource;
3635
3787
  exports.StaticTokenAuth = StaticTokenAuth;
3636
3788
  exports.StorageExceededError = StorageExceededError;
3637
3789
  exports.StudioOpError = StudioOpError;
3790
+ exports.StudioPreviewAppliedError = StudioPreviewAppliedError;
3791
+ exports.StudioPreviewUnavailable = StudioPreviewUnavailable;
3638
3792
  exports.StudioProductionsResource = StudioProductionsResource;
3639
3793
  exports.StudioResource = StudioResource;
3640
3794
  exports.TemplatesResource = TemplatesResource;