@nodaro/sdk 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +296 -0
- package/dist/index.cjs +2085 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +4047 -0
- package/dist/index.d.ts +4047 -0
- package/dist/index.js +1977 -0
- package/dist/index.js.map +1 -0
- package/package.json +59 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1977 @@
|
|
|
1
|
+
import { buildPersonHints } from '@nodaro/prompts';
|
|
2
|
+
export { PEOPLE, PERSON_DIMENSION_LABELS, PERSON_DIMENSION_ORDER, buildPersonHints } from '@nodaro/prompts';
|
|
3
|
+
export { CHARACTER_ASPECT_DEFAULTS, CHARACTER_ASPECT_OPTIONS, CHARACTER_STYLES, OBJECT_ASPECT_DEFAULTS as CREATURE_ASPECT_DEFAULTS, OBJECT_ASPECT_OPTIONS as CREATURE_ASPECT_OPTIONS, CREATURE_ATTACH_COLUMNS, LOCATION_ASSET_TYPES, LOCATION_ATTACH_COLUMNS, OBJECT_ASPECT_DEFAULTS, OBJECT_ASPECT_OPTIONS, OBJECT_ASSET_TYPES, OBJECT_ATTACH_COLUMNS, SURROUND_DIRECTIONS } from '@nodaro/shared';
|
|
4
|
+
|
|
5
|
+
// src/errors.ts
|
|
6
|
+
var NodaroError = class extends Error {
|
|
7
|
+
constructor(message, code, status) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.code = code;
|
|
10
|
+
this.status = status;
|
|
11
|
+
this.name = "NodaroError";
|
|
12
|
+
}
|
|
13
|
+
code;
|
|
14
|
+
status;
|
|
15
|
+
};
|
|
16
|
+
var UnauthorizedError = class extends NodaroError {
|
|
17
|
+
constructor(message = "Authentication required") {
|
|
18
|
+
super(message, "unauthorized", 401);
|
|
19
|
+
this.name = "UnauthorizedError";
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
var ForbiddenError = class extends NodaroError {
|
|
23
|
+
constructor(message = "Forbidden", missingScope) {
|
|
24
|
+
super(message, "forbidden", 403);
|
|
25
|
+
this.missingScope = missingScope;
|
|
26
|
+
this.name = "ForbiddenError";
|
|
27
|
+
}
|
|
28
|
+
missingScope;
|
|
29
|
+
};
|
|
30
|
+
var NotFoundError = class extends NodaroError {
|
|
31
|
+
constructor(message = "Not found") {
|
|
32
|
+
super(message, "not_found", 404);
|
|
33
|
+
this.name = "NotFoundError";
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
var RateLimitedError = class extends NodaroError {
|
|
37
|
+
constructor(message = "Rate limited") {
|
|
38
|
+
super(message, "rate_limited", 429);
|
|
39
|
+
this.name = "RateLimitedError";
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
var InsufficientCreditsError = class extends NodaroError {
|
|
43
|
+
constructor(message = "Insufficient credits", required, available) {
|
|
44
|
+
super(message, "insufficient_credits", 402);
|
|
45
|
+
this.required = required;
|
|
46
|
+
this.available = available;
|
|
47
|
+
this.name = "InsufficientCreditsError";
|
|
48
|
+
}
|
|
49
|
+
required;
|
|
50
|
+
available;
|
|
51
|
+
};
|
|
52
|
+
var StorageExceededError = class extends NodaroError {
|
|
53
|
+
constructor(message = "Storage exceeded", limitBytes) {
|
|
54
|
+
super(message, "storage_exceeded", 413);
|
|
55
|
+
this.limitBytes = limitBytes;
|
|
56
|
+
this.name = "StorageExceededError";
|
|
57
|
+
}
|
|
58
|
+
limitBytes;
|
|
59
|
+
};
|
|
60
|
+
var JobFailedError = class extends NodaroError {
|
|
61
|
+
constructor(message, jobId, jobStatus = "failed") {
|
|
62
|
+
super(message, "job_failed", 0);
|
|
63
|
+
this.jobId = jobId;
|
|
64
|
+
this.jobStatus = jobStatus;
|
|
65
|
+
this.name = "JobFailedError";
|
|
66
|
+
}
|
|
67
|
+
jobId;
|
|
68
|
+
jobStatus;
|
|
69
|
+
};
|
|
70
|
+
var JobTimeoutError = class extends NodaroError {
|
|
71
|
+
constructor(message, jobId, timeoutMs) {
|
|
72
|
+
super(message, "job_timeout", 0);
|
|
73
|
+
this.jobId = jobId;
|
|
74
|
+
this.timeoutMs = timeoutMs;
|
|
75
|
+
this.name = "JobTimeoutError";
|
|
76
|
+
}
|
|
77
|
+
jobId;
|
|
78
|
+
timeoutMs;
|
|
79
|
+
};
|
|
80
|
+
var JobAbortedError = class extends NodaroError {
|
|
81
|
+
constructor(message = "Aborted", jobId) {
|
|
82
|
+
super(message, "job_aborted", 0);
|
|
83
|
+
this.jobId = jobId;
|
|
84
|
+
this.name = "JobAbortedError";
|
|
85
|
+
}
|
|
86
|
+
jobId;
|
|
87
|
+
};
|
|
88
|
+
function throwFromResponse(status, body) {
|
|
89
|
+
const code = body.error?.code ?? "internal_error";
|
|
90
|
+
const message = body.error?.message ?? "Request failed";
|
|
91
|
+
if (status === 401) throw new UnauthorizedError(message);
|
|
92
|
+
if (status === 403 && code === "insufficient_scope") {
|
|
93
|
+
throw new ForbiddenError(message, body.error?.missingScope);
|
|
94
|
+
}
|
|
95
|
+
if (status === 403) throw new ForbiddenError(message);
|
|
96
|
+
if (status === 404) throw new NotFoundError(message);
|
|
97
|
+
if (status === 429) throw new RateLimitedError(message);
|
|
98
|
+
if (status === 402) {
|
|
99
|
+
throw new InsufficientCreditsError(message, body.error?.required, body.error?.available);
|
|
100
|
+
}
|
|
101
|
+
if (status === 413) throw new StorageExceededError(message, body.error?.limitBytes);
|
|
102
|
+
throw new NodaroError(message, code, status);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/resources/workflows.ts
|
|
106
|
+
var WorkflowsResource = class {
|
|
107
|
+
constructor(client) {
|
|
108
|
+
this.client = client;
|
|
109
|
+
}
|
|
110
|
+
client;
|
|
111
|
+
/** List workflows for a project. Returns metadata only — `nodes`/`edges` are not included. */
|
|
112
|
+
list(params) {
|
|
113
|
+
return this.client.request(
|
|
114
|
+
"GET",
|
|
115
|
+
`/v1/projects/${encodeURIComponent(params.projectId)}/workflows`
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
/** Get a workflow including its full nodes/edges/settings. */
|
|
119
|
+
get(id) {
|
|
120
|
+
return this.client.request("GET", `/v1/workflows/${encodeURIComponent(id)}`);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Get a PUBLICLY-SHARED workflow by id (`GET /v1/public/workflows/:id`) — the
|
|
124
|
+
* unauthenticated share-by-link read. Returns the workflow's nodes/edges/
|
|
125
|
+
* settings ONLY when it's opted into sharing server-side (`settings.studio.shared
|
|
126
|
+
* === true`); otherwise the route 404s (→ `NotFoundError`). No auth required —
|
|
127
|
+
* a share viewer has no session; the SDK omits the bearer when no token exists.
|
|
128
|
+
*/
|
|
129
|
+
getPublic(id) {
|
|
130
|
+
return this.client.request("GET", `/v1/public/workflows/${encodeURIComponent(id)}`);
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Create a workflow under a project. Returns the full record.
|
|
134
|
+
* NOTE: server route is `POST /v1/projects/:projectId/workflows`.
|
|
135
|
+
*/
|
|
136
|
+
create(input) {
|
|
137
|
+
const { projectId, ...body } = input;
|
|
138
|
+
return this.client.request(
|
|
139
|
+
"POST",
|
|
140
|
+
`/v1/projects/${encodeURIComponent(projectId)}/workflows`,
|
|
141
|
+
{ body }
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
/** Patch a workflow. Returns the full updated record. */
|
|
145
|
+
update(id, input) {
|
|
146
|
+
return this.client.request(
|
|
147
|
+
"PATCH",
|
|
148
|
+
`/v1/workflows/${encodeURIComponent(id)}`,
|
|
149
|
+
{ body: input }
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
/** Delete a workflow. Returns `{ success: true }`. */
|
|
153
|
+
delete(id) {
|
|
154
|
+
return this.client.request("DELETE", `/v1/workflows/${encodeURIComponent(id)}`);
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Run a workflow. Returns the executionId for polling via
|
|
158
|
+
* `client.executions.get(executionId)`.
|
|
159
|
+
*/
|
|
160
|
+
run(id, params = {}) {
|
|
161
|
+
return this.client.request(
|
|
162
|
+
"POST",
|
|
163
|
+
`/v1/workflows/${encodeURIComponent(id)}/run`,
|
|
164
|
+
{ body: params }
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Export a workflow as a portable JSON bundle.
|
|
169
|
+
* Pass `opts.assets = true` to include character/object/location entity data.
|
|
170
|
+
*/
|
|
171
|
+
export(workflowId, opts) {
|
|
172
|
+
return this.client.request(
|
|
173
|
+
"GET",
|
|
174
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/export`,
|
|
175
|
+
{ query: { assets: opts?.assets ?? false } }
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Import a `WorkflowExport` bundle into the specified project.
|
|
180
|
+
* Re-creates any bundled assets (characters, objects, locations) under your account.
|
|
181
|
+
*/
|
|
182
|
+
import(input) {
|
|
183
|
+
const { projectId, ...workflowJson } = input;
|
|
184
|
+
return this.client.request("POST", "/v1/workflows/import", {
|
|
185
|
+
body: { projectId, workflow_json: workflowJson }
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
// src/resources/projects.ts
|
|
191
|
+
var ProjectsResource = class {
|
|
192
|
+
constructor(client) {
|
|
193
|
+
this.client = client;
|
|
194
|
+
}
|
|
195
|
+
client;
|
|
196
|
+
/** List the authenticated user's projects. */
|
|
197
|
+
list() {
|
|
198
|
+
return this.client.request("GET", "/v1/projects");
|
|
199
|
+
}
|
|
200
|
+
/** Get a project by ID. */
|
|
201
|
+
get(id) {
|
|
202
|
+
return this.client.request("GET", `/v1/projects/${encodeURIComponent(id)}`);
|
|
203
|
+
}
|
|
204
|
+
/** Create a new project. */
|
|
205
|
+
create(input) {
|
|
206
|
+
return this.client.request("POST", "/v1/projects", { body: input });
|
|
207
|
+
}
|
|
208
|
+
/** Update a project. At least one field must be provided. */
|
|
209
|
+
update(id, input) {
|
|
210
|
+
return this.client.request(
|
|
211
|
+
"PATCH",
|
|
212
|
+
`/v1/projects/${encodeURIComponent(id)}`,
|
|
213
|
+
{ body: input }
|
|
214
|
+
);
|
|
215
|
+
}
|
|
216
|
+
/** Delete a project. Returns `{ success: true }`. */
|
|
217
|
+
delete(id) {
|
|
218
|
+
return this.client.request("DELETE", `/v1/projects/${encodeURIComponent(id)}`);
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
// src/resources/jobs.ts
|
|
223
|
+
var JobsResource = class {
|
|
224
|
+
constructor(client) {
|
|
225
|
+
this.client = client;
|
|
226
|
+
}
|
|
227
|
+
client;
|
|
228
|
+
/** Get a single job by ID. */
|
|
229
|
+
get(id) {
|
|
230
|
+
return this.client.request("GET", `/v1/jobs/${encodeURIComponent(id)}`);
|
|
231
|
+
}
|
|
232
|
+
/**
|
|
233
|
+
* Get the lean status of a single job (poll-loop friendly).
|
|
234
|
+
* Hits `GET /v1/jobs/:id/status` — returns only id/status/progress/
|
|
235
|
+
* output_data/error_message, with far less wire/CPU cost than `get()`.
|
|
236
|
+
* Same auth + ownership semantics as {@link get}.
|
|
237
|
+
*/
|
|
238
|
+
getStatus(id) {
|
|
239
|
+
return this.client.request(
|
|
240
|
+
"GET",
|
|
241
|
+
`/v1/jobs/${encodeURIComponent(id)}/status`
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Cancel a job. Server route is `POST /v1/jobs/:jobId/cancel`.
|
|
246
|
+
* Refunds any reserved credit holds.
|
|
247
|
+
*/
|
|
248
|
+
cancel(id) {
|
|
249
|
+
return this.client.request(
|
|
250
|
+
"POST",
|
|
251
|
+
`/v1/jobs/${encodeURIComponent(id)}/cancel`
|
|
252
|
+
);
|
|
253
|
+
}
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// src/resources/executions.ts
|
|
257
|
+
var ExecutionsResource = class {
|
|
258
|
+
constructor(client) {
|
|
259
|
+
this.client = client;
|
|
260
|
+
}
|
|
261
|
+
client;
|
|
262
|
+
/** Get an execution by ID. Falls back to standalone single-node jobs server-side. */
|
|
263
|
+
get(id) {
|
|
264
|
+
return this.client.request(
|
|
265
|
+
"GET",
|
|
266
|
+
`/v1/workflow-executions/${encodeURIComponent(id)}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
/** List executions for a workflow. Merges workflow_executions + standalone single-node jobs. */
|
|
270
|
+
listForWorkflow(workflowId, params = {}) {
|
|
271
|
+
return this.client.request(
|
|
272
|
+
"GET",
|
|
273
|
+
`/v1/workflows/${encodeURIComponent(workflowId)}/executions`,
|
|
274
|
+
{
|
|
275
|
+
query: {
|
|
276
|
+
limit: params.limit,
|
|
277
|
+
cursor: params.cursor,
|
|
278
|
+
status: params.status,
|
|
279
|
+
source: params.source
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
/** Cancel an execution. Returns `{ success: true }`. */
|
|
285
|
+
cancel(id, params = {}) {
|
|
286
|
+
return this.client.request(
|
|
287
|
+
"POST",
|
|
288
|
+
`/v1/workflow-executions/${encodeURIComponent(id)}/cancel`,
|
|
289
|
+
{ body: params }
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
// src/resources/nodes.ts
|
|
295
|
+
var DEFAULT_POLL_MS = 2e3;
|
|
296
|
+
var DEFAULT_MAX_MS = 15 * 60 * 1e3;
|
|
297
|
+
var sleep = (ms, signal) => new Promise((resolve, reject) => {
|
|
298
|
+
if (signal?.aborted) {
|
|
299
|
+
reject(new JobAbortedError());
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const timer = setTimeout(() => {
|
|
303
|
+
signal?.removeEventListener("abort", onAbort);
|
|
304
|
+
resolve();
|
|
305
|
+
}, ms);
|
|
306
|
+
function onAbort() {
|
|
307
|
+
clearTimeout(timer);
|
|
308
|
+
reject(new JobAbortedError());
|
|
309
|
+
}
|
|
310
|
+
signal?.addEventListener("abort", onAbort, { once: true });
|
|
311
|
+
});
|
|
312
|
+
function extractJobId(result, label) {
|
|
313
|
+
if (result && typeof result === "object" && "jobId" in result) {
|
|
314
|
+
const jobId = result.jobId;
|
|
315
|
+
if (typeof jobId === "string") return jobId;
|
|
316
|
+
}
|
|
317
|
+
throw new JobFailedError(`${label} did not return a jobId`, "");
|
|
318
|
+
}
|
|
319
|
+
var NodesResource = class {
|
|
320
|
+
constructor(client) {
|
|
321
|
+
this.client = client;
|
|
322
|
+
}
|
|
323
|
+
client;
|
|
324
|
+
/** List all known node descriptors. Server caches publicly for 5 minutes. */
|
|
325
|
+
list() {
|
|
326
|
+
return this.client.request("GET", "/v1/nodes");
|
|
327
|
+
}
|
|
328
|
+
/** Get a single node descriptor by type slug (e.g. "generate-image"). */
|
|
329
|
+
get(type) {
|
|
330
|
+
return this.client.request("GET", `/v1/nodes/${encodeURIComponent(type)}`);
|
|
331
|
+
}
|
|
332
|
+
run(type, params = {}) {
|
|
333
|
+
return this.client.request("POST", `/v1/${encodeURIComponent(type)}`, { body: params });
|
|
334
|
+
}
|
|
335
|
+
async runAndWait(type, params = {}, opts = {}) {
|
|
336
|
+
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
337
|
+
const result = await this.run(type, params);
|
|
338
|
+
const jobId = extractJobId(result, type);
|
|
339
|
+
return this.pollJob(jobId, type, opts);
|
|
340
|
+
}
|
|
341
|
+
/**
|
|
342
|
+
* Fan out N async runs of the same node `type` to completion concurrently —
|
|
343
|
+
* the candidate-grid path (generate N stills/clips in parallel). Each runs
|
|
344
|
+
* via {@link runAndWait}; resolves once ALL settle, to an array of
|
|
345
|
+
* `{ jobId, output }` in input order. Rejects (and the rejection wins) if any
|
|
346
|
+
* single run rejects — same typed errors as {@link runAndWait}. A shared
|
|
347
|
+
* `signal` aborts the whole batch.
|
|
348
|
+
*
|
|
349
|
+
* @param type Node type slug, applied to every entry.
|
|
350
|
+
* @param paramsList One request body per candidate.
|
|
351
|
+
* @param opts Shared `signal` / `onProgress` / `pollMs` / `maxMs`.
|
|
352
|
+
*/
|
|
353
|
+
async runMany(type, paramsList, opts = {}) {
|
|
354
|
+
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
355
|
+
return Promise.all(
|
|
356
|
+
paramsList.map(async (params) => {
|
|
357
|
+
if (opts.signal?.aborted) throw new JobAbortedError();
|
|
358
|
+
const result = await this.run(type, params);
|
|
359
|
+
const jobId = extractJobId(result, type);
|
|
360
|
+
const output = await this.pollJob(jobId, type, opts);
|
|
361
|
+
return { jobId, output };
|
|
362
|
+
})
|
|
363
|
+
);
|
|
364
|
+
}
|
|
365
|
+
/** Poll an already-kicked job id until terminal; resolve output_data or throw. */
|
|
366
|
+
async pollJob(jobId, label, opts) {
|
|
367
|
+
const pollMs = opts.pollMs ?? DEFAULT_POLL_MS;
|
|
368
|
+
const maxMs = opts.maxMs ?? DEFAULT_MAX_MS;
|
|
369
|
+
const deadline = Date.now() + maxMs;
|
|
370
|
+
for (; ; ) {
|
|
371
|
+
if (opts.signal?.aborted) throw new JobAbortedError(void 0, jobId);
|
|
372
|
+
const { data } = await this.client.jobs.getStatus(jobId);
|
|
373
|
+
opts.onProgress?.(data);
|
|
374
|
+
if (data.status === "completed") {
|
|
375
|
+
return data.output_data ?? {};
|
|
376
|
+
}
|
|
377
|
+
if (data.status === "failed" || data.status === "cancelled") {
|
|
378
|
+
throw new JobFailedError(
|
|
379
|
+
data.error_message ?? `${label} ${data.status}`,
|
|
380
|
+
jobId,
|
|
381
|
+
data.status
|
|
382
|
+
);
|
|
383
|
+
}
|
|
384
|
+
if (Date.now() > deadline) {
|
|
385
|
+
throw new JobTimeoutError(`${label} timed out`, jobId, maxMs);
|
|
386
|
+
}
|
|
387
|
+
await sleep(pollMs, opts.signal);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
// src/resources/developer-apps.ts
|
|
393
|
+
var DeveloperAppsResource = class {
|
|
394
|
+
constructor(client) {
|
|
395
|
+
this.client = client;
|
|
396
|
+
}
|
|
397
|
+
client;
|
|
398
|
+
/** List the authenticated user's developer apps. */
|
|
399
|
+
list() {
|
|
400
|
+
return this.client.request("GET", "/v1/developer-apps");
|
|
401
|
+
}
|
|
402
|
+
/** Get a developer app by ID. */
|
|
403
|
+
get(id) {
|
|
404
|
+
return this.client.request("GET", `/v1/developer-apps/${encodeURIComponent(id)}`);
|
|
405
|
+
}
|
|
406
|
+
/**
|
|
407
|
+
* Create a new developer app. Returns the app PLUS a one-time `clientSecret`
|
|
408
|
+
* — store it now, the secret hash is the only copy kept server-side.
|
|
409
|
+
*/
|
|
410
|
+
create(input) {
|
|
411
|
+
return this.client.request("POST", "/v1/developer-apps", { body: input });
|
|
412
|
+
}
|
|
413
|
+
/** Update a developer app's metadata, redirect URIs, origins, or requested scopes. */
|
|
414
|
+
update(id, input) {
|
|
415
|
+
return this.client.request(
|
|
416
|
+
"PATCH",
|
|
417
|
+
`/v1/developer-apps/${encodeURIComponent(id)}`,
|
|
418
|
+
{ body: input }
|
|
419
|
+
);
|
|
420
|
+
}
|
|
421
|
+
/** Delete a developer app. Returns `{ success: true }`. */
|
|
422
|
+
delete(id) {
|
|
423
|
+
return this.client.request(
|
|
424
|
+
"DELETE",
|
|
425
|
+
`/v1/developer-apps/${encodeURIComponent(id)}`
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* Generate a new `clientSecret`. The previous secret is invalidated.
|
|
430
|
+
* Server returns ONLY the new secret, not the full app record.
|
|
431
|
+
*/
|
|
432
|
+
rotateSecret(id) {
|
|
433
|
+
return this.client.request(
|
|
434
|
+
"POST",
|
|
435
|
+
`/v1/developer-apps/${encodeURIComponent(id)}/rotate-secret`
|
|
436
|
+
);
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
// src/resources/oauth.ts
|
|
441
|
+
var OAuthResource = class {
|
|
442
|
+
constructor(client) {
|
|
443
|
+
this.client = client;
|
|
444
|
+
}
|
|
445
|
+
client;
|
|
446
|
+
/**
|
|
447
|
+
* Server-side authorization-code exchange. Sends the standard OAuth 2.0
|
|
448
|
+
* `application/json` body to `POST /v1/oauth/token`.
|
|
449
|
+
*
|
|
450
|
+
* NEVER call this from a browser — `client_secret` must stay on the server.
|
|
451
|
+
*/
|
|
452
|
+
exchangeCode(input) {
|
|
453
|
+
return this.client.request("POST", "/v1/oauth/token", {
|
|
454
|
+
body: { grant_type: "authorization_code", ...input }
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
/**
|
|
458
|
+
* Revoke an access token (RFC 7009). Always returns `{ success: true }`,
|
|
459
|
+
* even for unknown tokens — the spec forbids leaking token validity.
|
|
460
|
+
*/
|
|
461
|
+
revoke(token) {
|
|
462
|
+
return this.client.request("POST", "/v1/oauth/revoke", { body: { token } });
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Get public app metadata for a consent screen.
|
|
466
|
+
* `GET /v1/oauth/app-info?client_id=<id>`. Public route — no auth needed.
|
|
467
|
+
*/
|
|
468
|
+
getAppInfo(clientId) {
|
|
469
|
+
return this.client.request("GET", "/v1/oauth/app-info", {
|
|
470
|
+
query: { client_id: clientId }
|
|
471
|
+
});
|
|
472
|
+
}
|
|
473
|
+
};
|
|
474
|
+
|
|
475
|
+
// src/resources/apps.ts
|
|
476
|
+
var AppsResource = class {
|
|
477
|
+
constructor(client) {
|
|
478
|
+
this.client = client;
|
|
479
|
+
}
|
|
480
|
+
client;
|
|
481
|
+
/** List published apps. Public — no auth required for community apps. */
|
|
482
|
+
list(params = {}) {
|
|
483
|
+
const qs = new URLSearchParams();
|
|
484
|
+
if (params.search) qs.set("search", params.search);
|
|
485
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
486
|
+
if (params.cursor) qs.set("cursor", params.cursor);
|
|
487
|
+
if (params.category) qs.set("category", params.category);
|
|
488
|
+
const query = qs.toString();
|
|
489
|
+
return this.client.request("GET", `/v1/apps/browse${query ? `?${query}` : ""}`);
|
|
490
|
+
}
|
|
491
|
+
/** Get one app's metadata + input schema by slug. */
|
|
492
|
+
get(slug) {
|
|
493
|
+
return this.client.request("GET", `/v1/app/${encodeURIComponent(slug)}`);
|
|
494
|
+
}
|
|
495
|
+
/**
|
|
496
|
+
* Trigger an app run with the given input values. The keys in `inputs` must
|
|
497
|
+
* match the app's input-schema field names (see `get(slug).inputSchema`).
|
|
498
|
+
* Returns the execution-id for status polling via client.executions.get().
|
|
499
|
+
*/
|
|
500
|
+
run(slug, inputs = {}) {
|
|
501
|
+
return this.client.request("POST", `/v1/app/${encodeURIComponent(slug)}/run`, {
|
|
502
|
+
body: { inputs }
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
/** List past runs for an app (the caller must own the app or the runs). */
|
|
506
|
+
listRuns(slug, params = {}) {
|
|
507
|
+
const qs = new URLSearchParams();
|
|
508
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
509
|
+
if (params.cursor) qs.set("cursor", params.cursor);
|
|
510
|
+
const query = qs.toString();
|
|
511
|
+
return this.client.request(
|
|
512
|
+
"GET",
|
|
513
|
+
`/v1/app/${encodeURIComponent(slug)}/runs${query ? `?${query}` : ""}`
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
/** Get one app-run by id. */
|
|
517
|
+
getRun(slug, runId) {
|
|
518
|
+
return this.client.request(
|
|
519
|
+
"GET",
|
|
520
|
+
`/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`
|
|
521
|
+
);
|
|
522
|
+
}
|
|
523
|
+
/**
|
|
524
|
+
* Archive (soft-delete) a published-app run. The run is hidden from the
|
|
525
|
+
* default run list and can be restored or permanently deleted from the
|
|
526
|
+
* archive view at https://app.nodaro.ai/archived-runs.
|
|
527
|
+
*
|
|
528
|
+
* @param slug The published app's slug (the last path segment of its URL).
|
|
529
|
+
* @param runId The run's UUID.
|
|
530
|
+
*/
|
|
531
|
+
deleteRun(slug, runId) {
|
|
532
|
+
return this.client.request(
|
|
533
|
+
"DELETE",
|
|
534
|
+
`/v1/app/${encodeURIComponent(slug)}/runs/${encodeURIComponent(runId)}`
|
|
535
|
+
);
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
var CharactersResource = class {
|
|
539
|
+
constructor(client) {
|
|
540
|
+
this.client = client;
|
|
541
|
+
}
|
|
542
|
+
client;
|
|
543
|
+
/**
|
|
544
|
+
* List the caller's characters. By default returns active characters only;
|
|
545
|
+
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
546
|
+
* When `projectId` is set, only characters belonging to that project are
|
|
547
|
+
* returned.
|
|
548
|
+
*/
|
|
549
|
+
list(params = {}) {
|
|
550
|
+
const query = {};
|
|
551
|
+
if (params.projectId) query.projectId = params.projectId;
|
|
552
|
+
if (params.archived) query.archived = "true";
|
|
553
|
+
if (params.limit !== void 0) query.limit = String(params.limit);
|
|
554
|
+
return this.client.request("GET", "/v1/characters", { query });
|
|
555
|
+
}
|
|
556
|
+
/**
|
|
557
|
+
* Fetch a single character including in-flight portrait / asset job state.
|
|
558
|
+
* Soft-deleted (archived) rows are returned by id intentionally so canvas
|
|
559
|
+
* nodes that hold a stale `characterDbId` keep loading.
|
|
560
|
+
*/
|
|
561
|
+
get(id) {
|
|
562
|
+
return this.client.request("GET", `/v1/characters/${encodeURIComponent(id)}`);
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Create or update a character. Omit `id` to create; supply it to update
|
|
566
|
+
* (only the fields you pass get written — undefined keys are untouched).
|
|
567
|
+
*
|
|
568
|
+
* If the caller-supplied `name` collides with an existing active character
|
|
569
|
+
* for this user, the request returns 409 `name_taken`. To auto-number a
|
|
570
|
+
* placeholder, pass the placeholder name from `@nodaro/shared` and the
|
|
571
|
+
* server will derive "Untitled character 2", "Untitled character 3", etc.
|
|
572
|
+
*/
|
|
573
|
+
upsert(input) {
|
|
574
|
+
return this.client.request("POST", "/v1/characters", { body: input });
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Convenience wrapper around `upsert()` for creating new characters.
|
|
578
|
+
* Equivalent to `upsert({ ...input, id: undefined })`. `name` is REQUIRED
|
|
579
|
+
* on create — the route 400s on INSERT-without-name; we narrow the type
|
|
580
|
+
* here so callers fail at compile-time rather than runtime.
|
|
581
|
+
*/
|
|
582
|
+
create(input) {
|
|
583
|
+
return this.upsert(input);
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Convenience wrapper around `upsert()` for updating an existing character.
|
|
587
|
+
* Equivalent to `upsert({ ...input, id })`.
|
|
588
|
+
*/
|
|
589
|
+
update(id, input) {
|
|
590
|
+
return this.upsert({ ...input, id });
|
|
591
|
+
}
|
|
592
|
+
/**
|
|
593
|
+
* Soft-delete (archive) a character. The row is hidden from `list()` by
|
|
594
|
+
* default but still loadable via `get(id)` so canvas nodes pointing at it
|
|
595
|
+
* keep working. Restore with `restore(id)`.
|
|
596
|
+
*/
|
|
597
|
+
delete(id) {
|
|
598
|
+
return this.client.request("DELETE", `/v1/characters/${encodeURIComponent(id)}`);
|
|
599
|
+
}
|
|
600
|
+
/**
|
|
601
|
+
* Un-archive a character. If the original name now collides with an
|
|
602
|
+
* active row, the server auto-suffixes "(restored)" and returns the
|
|
603
|
+
* effective name.
|
|
604
|
+
*/
|
|
605
|
+
restore(id) {
|
|
606
|
+
return this.client.request("POST", `/v1/characters/${encodeURIComponent(id)}/restore`);
|
|
607
|
+
}
|
|
608
|
+
/**
|
|
609
|
+
* Duplicate (fork) a character to a new row with a `"(copy)"` suffix.
|
|
610
|
+
* Asset URLs are shared by reference — the new row can diverge by
|
|
611
|
+
* regenerating any of them.
|
|
612
|
+
*/
|
|
613
|
+
duplicate(id, input = {}) {
|
|
614
|
+
return this.client.request(
|
|
615
|
+
"POST",
|
|
616
|
+
`/v1/characters/${encodeURIComponent(id)}/duplicate`,
|
|
617
|
+
{ body: input }
|
|
618
|
+
);
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Count of the caller's workflows that reference this character. Powers the
|
|
622
|
+
* library "Archive" confirmation modal in the editor.
|
|
623
|
+
*/
|
|
624
|
+
usage(id) {
|
|
625
|
+
return this.client.request("GET", `/v1/characters/${encodeURIComponent(id)}/usage`);
|
|
626
|
+
}
|
|
627
|
+
/**
|
|
628
|
+
* Fire `POST /v1/generate-character` to produce one or more portrait
|
|
629
|
+
* candidates. With `count > 1`, all jobs are reserved up-front before any
|
|
630
|
+
* is enqueued — mid-batch failures roll back atomically.
|
|
631
|
+
*
|
|
632
|
+
* When `attachToCharacterId` is set, the worker writes the result directly
|
|
633
|
+
* to the row's `source_image_url`; otherwise you must call
|
|
634
|
+
* `approvePortrait()` after picking a candidate.
|
|
635
|
+
*/
|
|
636
|
+
generate(input) {
|
|
637
|
+
return this.client.request("POST", "/v1/generate-character", { body: input });
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Fire `POST /v1/generate-character-asset` to produce a single
|
|
641
|
+
* expression / pose / angle / lighting variant. When the studio path is
|
|
642
|
+
* set (`attachToCharacterId` + `attachToColumn` + `attachName`), the
|
|
643
|
+
* worker appends `{ name: attachName, url: <result> }` to the named
|
|
644
|
+
* JSONB array column on completion.
|
|
645
|
+
*/
|
|
646
|
+
generateAsset(input) {
|
|
647
|
+
return this.client.request("POST", "/v1/generate-character-asset", { body: input });
|
|
648
|
+
}
|
|
649
|
+
/**
|
|
650
|
+
* Fire `POST /v1/generate-character-motion` to animate the character's
|
|
651
|
+
* portrait into a motion clip. The result is appended to the character's
|
|
652
|
+
* `motions[]` bucket when `attachToCharacterId` is set.
|
|
653
|
+
*/
|
|
654
|
+
generateMotion(input) {
|
|
655
|
+
return this.client.request("POST", "/v1/generate-character-motion", { body: input });
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* Approve a completed `generate-character` job as the character's portrait.
|
|
659
|
+
* Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
|
|
660
|
+
* inline. Returns the new portrait URL plus the caption — `canonicalDescription`
|
|
661
|
+
* is `null` if the LLM call sub-failed (portrait still set; retry via `recaption()`).
|
|
662
|
+
*/
|
|
663
|
+
approvePortrait(id, candidateJobId) {
|
|
664
|
+
return this.client.request(
|
|
665
|
+
"POST",
|
|
666
|
+
`/v1/characters/${encodeURIComponent(id)}/approve-portrait`,
|
|
667
|
+
{ body: { candidateJobId } }
|
|
668
|
+
);
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Re-fire the LLM caption against the character's current portrait. 502s on
|
|
672
|
+
* LLM failure; returns 400 `no_portrait` if no portrait is set yet.
|
|
673
|
+
*/
|
|
674
|
+
recaption(id) {
|
|
675
|
+
return this.client.request(
|
|
676
|
+
"POST",
|
|
677
|
+
`/v1/characters/${encodeURIComponent(id)}/llm-caption`
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
function buildPersonSeedPrompt(value) {
|
|
682
|
+
return buildPersonHints(value).join(", ");
|
|
683
|
+
}
|
|
684
|
+
var LocationsResource = class {
|
|
685
|
+
constructor(client) {
|
|
686
|
+
this.client = client;
|
|
687
|
+
}
|
|
688
|
+
client;
|
|
689
|
+
/**
|
|
690
|
+
* List the caller's locations. By default returns active locations only;
|
|
691
|
+
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
692
|
+
*/
|
|
693
|
+
list(params = {}) {
|
|
694
|
+
const query = {};
|
|
695
|
+
if (params.archived) query.archived = "true";
|
|
696
|
+
return this.client.request("GET", "/v1/locations", { query });
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
|
|
700
|
+
* rows so callers can drive a UI "Archived" tab without re-encoding the
|
|
701
|
+
* query param. Mirrors `ObjectsResource.listArchived`.
|
|
702
|
+
*
|
|
703
|
+
* `archived` is omitted from the param type — it's always set to `true` here.
|
|
704
|
+
*/
|
|
705
|
+
listArchived(params = {}) {
|
|
706
|
+
return this.list({ ...params, archived: true });
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Fetch a single location including in-flight asset job state. Soft-deleted
|
|
710
|
+
* (archived) rows are returned by id intentionally so canvas nodes that
|
|
711
|
+
* hold a stale `locationDbId` keep loading.
|
|
712
|
+
*/
|
|
713
|
+
async get(id) {
|
|
714
|
+
const res = await this.client.request(
|
|
715
|
+
"GET",
|
|
716
|
+
`/v1/locations/${encodeURIComponent(id)}`
|
|
717
|
+
);
|
|
718
|
+
return { ...res, canonicalDescription: res.canonicalDescription || null };
|
|
719
|
+
}
|
|
720
|
+
/**
|
|
721
|
+
* Create a new location. `name` + `nodeId` are required — the route 400s
|
|
722
|
+
* otherwise. Returns the new row's id.
|
|
723
|
+
*
|
|
724
|
+
* Note: the underlying route is the same `POST /v1/locations` upsert that
|
|
725
|
+
* powers `update()`. This convenience wrapper enforces the INSERT-required
|
|
726
|
+
* fields at the type level and never sends an `id`.
|
|
727
|
+
*/
|
|
728
|
+
create(data) {
|
|
729
|
+
return this.client.request("POST", "/v1/locations", { body: data });
|
|
730
|
+
}
|
|
731
|
+
/**
|
|
732
|
+
* Update a location. Only the fields you pass are written — undefined keys
|
|
733
|
+
* are NOT touched on the row. Worker-owned asset buckets are intentionally
|
|
734
|
+
* not exposed on this surface (see `UpdateLocationInput` for the rationale).
|
|
735
|
+
*
|
|
736
|
+
* Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
|
|
737
|
+
* `updated_at` still matches; on mismatch the route returns 409
|
|
738
|
+
* `concurrent_modification`. The SDK surfaces that as a generic
|
|
739
|
+
* `NodaroError` with the same code.
|
|
740
|
+
*/
|
|
741
|
+
update(id, data) {
|
|
742
|
+
return this.client.request("POST", "/v1/locations", {
|
|
743
|
+
body: { id, ...data }
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
/**
|
|
747
|
+
* Soft-delete (archive) a location. The row is hidden from `list()` by
|
|
748
|
+
* default but still loadable via `get(id)` so canvas nodes pointing at it
|
|
749
|
+
* keep working. Restore with `restore(id)`.
|
|
750
|
+
*/
|
|
751
|
+
delete(id) {
|
|
752
|
+
return this.client.request("DELETE", `/v1/locations/${encodeURIComponent(id)}`);
|
|
753
|
+
}
|
|
754
|
+
/**
|
|
755
|
+
* Un-archive a location. If the original name now collides (case-
|
|
756
|
+
* insensitive) with an active row, the server auto-suffixes "(restored)"
|
|
757
|
+
* and returns the effective name.
|
|
758
|
+
*/
|
|
759
|
+
restore(id) {
|
|
760
|
+
return this.client.request("POST", `/v1/locations/${encodeURIComponent(id)}/restore`);
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* Fire `POST /v1/generate-location` to produce one or more candidate main
|
|
764
|
+
* images. With `count > 1`, all jobs are reserved up-front before any
|
|
765
|
+
* is enqueued — mid-batch failures roll back atomically.
|
|
766
|
+
*
|
|
767
|
+
* When `attachToLocationId` is set AND `count === 1`, the worker writes
|
|
768
|
+
* the result directly to the row's `source_image_url`; otherwise you must
|
|
769
|
+
* call `approveMainImage()` after picking a candidate.
|
|
770
|
+
*/
|
|
771
|
+
async generate(data) {
|
|
772
|
+
const res = await this.client.request(
|
|
773
|
+
"POST",
|
|
774
|
+
"/v1/generate-location",
|
|
775
|
+
{ body: data }
|
|
776
|
+
);
|
|
777
|
+
const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : []);
|
|
778
|
+
return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds };
|
|
779
|
+
}
|
|
780
|
+
/**
|
|
781
|
+
* Fire `POST /v1/generate-location-asset` to produce a single variant.
|
|
782
|
+
* When the studio path is set (`attachToLocationId` + `attachToColumn` +
|
|
783
|
+
* `attachName`), the worker appends `{ name: attachName, url: <result> }`
|
|
784
|
+
* to the named JSONB array column on completion.
|
|
785
|
+
*/
|
|
786
|
+
generateAsset(data) {
|
|
787
|
+
return this.client.request("POST", "/v1/generate-location-asset", { body: data });
|
|
788
|
+
}
|
|
789
|
+
/**
|
|
790
|
+
* Fire `POST /v1/generate-surround-continuation` to produce one seamless 360°
|
|
791
|
+
* ring view as an i2i continuation of `referenceImageUrl`. The platform builds
|
|
792
|
+
* the half-carry composite, paints the missing half, and color-harmonizes it
|
|
793
|
+
* to the carried half (no tonal seam; carried half stays pixel-exact). When the
|
|
794
|
+
* studio path is set, the worker appends the result to the location's bucket.
|
|
795
|
+
*/
|
|
796
|
+
generateSurroundContinuation(data) {
|
|
797
|
+
return this.client.request("POST", "/v1/generate-surround-continuation", { body: data });
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Fire `POST /v1/generate-location-motion` to animate the location's
|
|
801
|
+
* establishing shot into an atmospheric motion clip. Image-to-video, single
|
|
802
|
+
* clip per call; the attach column is hardcoded to `atmosphere_motions`
|
|
803
|
+
* server-side (locations have a single motion bucket so the caller doesn't
|
|
804
|
+
* supply `attachToColumn`). When the studio path is set
|
|
805
|
+
* (`attachToLocationId` + `attachName`), the worker appends
|
|
806
|
+
* `{ name: attachName, url: <result> }` to the row's `atmosphere_motions`
|
|
807
|
+
* column on completion.
|
|
808
|
+
*/
|
|
809
|
+
generateMotion(data) {
|
|
810
|
+
return this.client.request("POST", "/v1/generate-location-motion", { body: data });
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* Atomically remove ONE asset take (every entry matching `url`) from a
|
|
814
|
+
* worker-owned bucket column — `POST /v1/locations/:id/remove-asset`. The
|
|
815
|
+
* worker-owned buckets are deliberately not writable through `update()`
|
|
816
|
+
* (a stale snapshot would race concurrent worker appends), so deleting a
|
|
817
|
+
* take — e.g. a 360° surround view being regenerated — goes through this
|
|
818
|
+
* single-statement server-side filter instead. 404s (`NotFoundError`) when
|
|
819
|
+
* the url isn't in that bucket or the location isn't yours.
|
|
820
|
+
*/
|
|
821
|
+
removeAsset(id, data) {
|
|
822
|
+
return this.client.request(
|
|
823
|
+
"POST",
|
|
824
|
+
`/v1/locations/${encodeURIComponent(id)}/remove-asset`,
|
|
825
|
+
{ body: data }
|
|
826
|
+
);
|
|
827
|
+
}
|
|
828
|
+
/**
|
|
829
|
+
* Approve a completed `generate-location` job as the location's main image.
|
|
830
|
+
* Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
|
|
831
|
+
* inline. Returns the new main-image URL plus the caption.
|
|
832
|
+
*
|
|
833
|
+
* Caption-failure semantics: the route still sends `""` on LLM sub-failure,
|
|
834
|
+
* but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
|
|
835
|
+
* the same `string | null` semantics as characters. The main image is still
|
|
836
|
+
* set; call `recaption()` to retry.
|
|
837
|
+
*/
|
|
838
|
+
async approveMainImage(id, candidateJobId) {
|
|
839
|
+
const res = await this.client.request(
|
|
840
|
+
"POST",
|
|
841
|
+
`/v1/locations/${encodeURIComponent(id)}/approve-main-image`,
|
|
842
|
+
{ body: { candidateJobId } }
|
|
843
|
+
);
|
|
844
|
+
return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null };
|
|
845
|
+
}
|
|
846
|
+
/**
|
|
847
|
+
* Re-fire the LLM caption against the location's current main image. 502s
|
|
848
|
+
* on LLM failure (unlike `approveMainImage` which preserves the side-effect
|
|
849
|
+
* and returns ""); returns 400 `no_source_image` if no main image is set
|
|
850
|
+
* yet.
|
|
851
|
+
*/
|
|
852
|
+
recaption(id) {
|
|
853
|
+
return this.client.request(
|
|
854
|
+
"POST",
|
|
855
|
+
`/v1/locations/${encodeURIComponent(id)}/llm-caption`
|
|
856
|
+
);
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
var ObjectsResource = class {
|
|
860
|
+
constructor(client) {
|
|
861
|
+
this.client = client;
|
|
862
|
+
}
|
|
863
|
+
client;
|
|
864
|
+
/**
|
|
865
|
+
* List the caller's objects. By default returns active objects only;
|
|
866
|
+
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
867
|
+
* Optional `projectId` scopes the result to a single project.
|
|
868
|
+
*/
|
|
869
|
+
list(params = {}) {
|
|
870
|
+
const query = {};
|
|
871
|
+
if (params.archived) query.archived = "true";
|
|
872
|
+
if (params.projectId) query.projectId = params.projectId;
|
|
873
|
+
return this.client.request("GET", "/v1/objects", { query });
|
|
874
|
+
}
|
|
875
|
+
/**
|
|
876
|
+
* Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
|
|
877
|
+
* rows so callers can drive a UI "Archived" tab without re-encoding the
|
|
878
|
+
* query param.
|
|
879
|
+
*
|
|
880
|
+
* `archived` is omitted from the param type — it's always set to `true` here.
|
|
881
|
+
*/
|
|
882
|
+
listArchived(params = {}) {
|
|
883
|
+
return this.list({ ...params, archived: true });
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Fetch a single object including in-flight asset job state. Soft-deleted
|
|
887
|
+
* (archived) rows are NOT returned by id — the route enforces
|
|
888
|
+
* `deleted_at IS NULL` so archived objects 404 (uniform Pass 10 F-90b
|
|
889
|
+
* "not_found" — does not leak the deleted vs non-existent distinction).
|
|
890
|
+
*/
|
|
891
|
+
async get(id) {
|
|
892
|
+
const res = await this.client.request(
|
|
893
|
+
"GET",
|
|
894
|
+
`/v1/objects/${encodeURIComponent(id)}`
|
|
895
|
+
);
|
|
896
|
+
return { ...res, canonicalDescription: res.canonicalDescription || null };
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Create a new object. `name` + `nodeId` are required — the route 400s
|
|
900
|
+
* otherwise. Returns the new row's id.
|
|
901
|
+
*
|
|
902
|
+
* Note: the underlying route is the same `POST /v1/objects` upsert that
|
|
903
|
+
* powers `update()`. This convenience wrapper enforces the INSERT-required
|
|
904
|
+
* fields at the type level and never sends an `id`.
|
|
905
|
+
*/
|
|
906
|
+
create(data) {
|
|
907
|
+
return this.client.request("POST", "/v1/objects", { body: data });
|
|
908
|
+
}
|
|
909
|
+
/**
|
|
910
|
+
* Update an object. Only the fields you pass are written — undefined keys
|
|
911
|
+
* are NOT touched on the row. Worker-owned asset buckets are intentionally
|
|
912
|
+
* not exposed on this surface (see `UpdateObjectInput` for the rationale).
|
|
913
|
+
*
|
|
914
|
+
* Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
|
|
915
|
+
* `updated_at` still matches; on mismatch the route returns 409
|
|
916
|
+
* `concurrent_modification` carrying the fresh `updatedAt`. The SDK
|
|
917
|
+
* surfaces that as a generic `NodaroError` with the same code (per Phase
|
|
918
|
+
* E1 calibration finding — error centralization in `throwApiError`).
|
|
919
|
+
*/
|
|
920
|
+
update(id, data) {
|
|
921
|
+
return this.client.request("POST", "/v1/objects", {
|
|
922
|
+
body: { id, ...data }
|
|
923
|
+
});
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Soft-delete (archive) an object. The row is hidden from `list()` by
|
|
927
|
+
* default but recoverable via `restore(id)` or visible under
|
|
928
|
+
* `listArchived()`. Idempotent — repeating a delete on an already-archived
|
|
929
|
+
* row is a no-op.
|
|
930
|
+
*/
|
|
931
|
+
delete(id) {
|
|
932
|
+
return this.client.request("DELETE", `/v1/objects/${encodeURIComponent(id)}`);
|
|
933
|
+
}
|
|
934
|
+
/**
|
|
935
|
+
* Hard-delete (permanent) an object — the row + every R2 asset it
|
|
936
|
+
* references. Archived rows ONLY: active objects return 400 `not_archived`.
|
|
937
|
+
* Call `delete()` first to archive, then `permanentDelete()` to destroy.
|
|
938
|
+
*
|
|
939
|
+
* Mirrors the `app_runs` permanent-delete pattern (archive-first) so a
|
|
940
|
+
* stray SDK / curl caller cannot bypass the studio's archive-first UI
|
|
941
|
+
* flow.
|
|
942
|
+
*/
|
|
943
|
+
permanentDelete(id) {
|
|
944
|
+
return this.client.request("DELETE", `/v1/objects/${encodeURIComponent(id)}`, {
|
|
945
|
+
query: { permanent: "true" }
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Un-archive an object. If the original name now collides (case-
|
|
950
|
+
* insensitive) with an active row, the server auto-suffixes "(restored)"
|
|
951
|
+
* and returns the effective name.
|
|
952
|
+
*/
|
|
953
|
+
restore(id) {
|
|
954
|
+
return this.client.request("POST", `/v1/objects/${encodeURIComponent(id)}/restore`);
|
|
955
|
+
}
|
|
956
|
+
/**
|
|
957
|
+
* Fire `POST /v1/generate-object` to produce one or more candidate main
|
|
958
|
+
* images. With `count > 1`, all jobs are reserved up-front before any
|
|
959
|
+
* is enqueued — mid-batch failures roll back atomically.
|
|
960
|
+
*
|
|
961
|
+
* When `attachToObjectId` is set AND `count === 1`, the worker writes
|
|
962
|
+
* the result directly to the row's `source_image_url`; otherwise you must
|
|
963
|
+
* call `approveMainImage()` after picking a candidate.
|
|
964
|
+
*/
|
|
965
|
+
async generate(data) {
|
|
966
|
+
const res = await this.client.request(
|
|
967
|
+
"POST",
|
|
968
|
+
"/v1/generate-object",
|
|
969
|
+
{ body: data }
|
|
970
|
+
);
|
|
971
|
+
const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : []);
|
|
972
|
+
return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds };
|
|
973
|
+
}
|
|
974
|
+
/**
|
|
975
|
+
* Fire `POST /v1/generate-object-asset` to produce a single variant.
|
|
976
|
+
* When the studio path is set (`attachToObjectId` + `attachToColumn` +
|
|
977
|
+
* `attachName`), the worker appends `{ name: attachName, url: <result> }`
|
|
978
|
+
* to the named JSONB array column on completion.
|
|
979
|
+
*
|
|
980
|
+
* Note: `attachToColumn` is REQUIRED for `assetType === "custom"` — the
|
|
981
|
+
* worker can't infer the bucket from the asset type. For canonical asset
|
|
982
|
+
* types (`angles` / `materials` / `variations` / `motion`), the column is
|
|
983
|
+
* derived automatically by the route.
|
|
984
|
+
*/
|
|
985
|
+
generateAsset(data) {
|
|
986
|
+
return this.client.request("POST", "/v1/generate-object-asset", { body: data });
|
|
987
|
+
}
|
|
988
|
+
/**
|
|
989
|
+
* Fire `POST /v1/generate-object-motion` to animate the object's main
|
|
990
|
+
* image into a motion clip. Image-to-video, single clip per call; the
|
|
991
|
+
* attach column is hardcoded to `motion_clips` server-side (objects have a
|
|
992
|
+
* single motion bucket so the caller doesn't supply `attachToColumn`).
|
|
993
|
+
* When the studio path is set (`attachToObjectId` + `attachName`), the
|
|
994
|
+
* worker appends `{ name: attachName, url: <result> }` to the row's
|
|
995
|
+
* `motion_clips` column on completion.
|
|
996
|
+
*
|
|
997
|
+
* Defaults: `provider` → `"kling-turbo"`, `aspectRatio` → `"1:1"` (set
|
|
998
|
+
* server-side via `resolveObjectAspectRatio({ assetType: "motion" })`).
|
|
999
|
+
*/
|
|
1000
|
+
generateMotion(data) {
|
|
1001
|
+
return this.client.request("POST", "/v1/generate-object-motion", { body: data });
|
|
1002
|
+
}
|
|
1003
|
+
/**
|
|
1004
|
+
* Approve a completed `generate-object` job as the object's main image.
|
|
1005
|
+
* Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
|
|
1006
|
+
* inline. Returns the new main-image URL plus the caption.
|
|
1007
|
+
*
|
|
1008
|
+
* Caption-failure semantics: the route still sends `""` on LLM sub-failure,
|
|
1009
|
+
* but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
|
|
1010
|
+
* the same `string | null` semantics as characters. The main image is still
|
|
1011
|
+
* set; call `recaption()` to retry.
|
|
1012
|
+
*
|
|
1013
|
+
* Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on
|
|
1014
|
+
* the row's current `updated_at`; on mismatch the route returns 409
|
|
1015
|
+
* `concurrent_modification` carrying the fresh token.
|
|
1016
|
+
*/
|
|
1017
|
+
async approveMainImage(id, candidateJobId, expectedUpdatedAt) {
|
|
1018
|
+
const res = await this.client.request(
|
|
1019
|
+
"POST",
|
|
1020
|
+
`/v1/objects/${encodeURIComponent(id)}/approve-main-image`,
|
|
1021
|
+
{ body: { candidateJobId, expectedUpdatedAt } }
|
|
1022
|
+
);
|
|
1023
|
+
return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null };
|
|
1024
|
+
}
|
|
1025
|
+
/**
|
|
1026
|
+
* Re-fire the LLM caption against the object's current main image. 502s
|
|
1027
|
+
* on LLM failure (unlike `approveMainImage` which preserves the side-effect
|
|
1028
|
+
* and returns ""); returns 400 `main_image_required` if no main image is
|
|
1029
|
+
* set yet.
|
|
1030
|
+
*
|
|
1031
|
+
* The route is a pure idempotent retry — it does NOT accept an
|
|
1032
|
+
* `expectedUpdatedAt` token (per Phase E1 calibration finding: backend
|
|
1033
|
+
* route is idempotent retry, not gated on optimistic-concurrency).
|
|
1034
|
+
*/
|
|
1035
|
+
recaption(id) {
|
|
1036
|
+
return this.client.request(
|
|
1037
|
+
"POST",
|
|
1038
|
+
`/v1/objects/${encodeURIComponent(id)}/llm-caption`
|
|
1039
|
+
);
|
|
1040
|
+
}
|
|
1041
|
+
};
|
|
1042
|
+
var CREATURE_ASSET_TYPES = ["angles", "poses", "variations", "custom"];
|
|
1043
|
+
var CreaturesResource = class {
|
|
1044
|
+
constructor(client) {
|
|
1045
|
+
this.client = client;
|
|
1046
|
+
}
|
|
1047
|
+
client;
|
|
1048
|
+
/**
|
|
1049
|
+
* List the caller's creatures. By default returns active creatures only;
|
|
1050
|
+
* pass `archived: true` to fetch soft-deleted rows for an "archive" view.
|
|
1051
|
+
* Optional `projectId` scopes the result to a single project.
|
|
1052
|
+
*/
|
|
1053
|
+
list(params = {}) {
|
|
1054
|
+
const query = {};
|
|
1055
|
+
if (params.archived) query.archived = "true";
|
|
1056
|
+
if (params.projectId) query.projectId = params.projectId;
|
|
1057
|
+
return this.client.request("GET", "/v1/creatures", { query });
|
|
1058
|
+
}
|
|
1059
|
+
/**
|
|
1060
|
+
* Convenience wrapper for `list({ archived: true })`. Returns soft-deleted
|
|
1061
|
+
* rows so callers can drive a UI "Archived" tab without re-encoding the
|
|
1062
|
+
* query param.
|
|
1063
|
+
*
|
|
1064
|
+
* `archived` is omitted from the param type — it's always set to `true` here.
|
|
1065
|
+
*/
|
|
1066
|
+
listArchived(params = {}) {
|
|
1067
|
+
return this.list({ ...params, archived: true });
|
|
1068
|
+
}
|
|
1069
|
+
/**
|
|
1070
|
+
* Fetch a single creature including in-flight asset job state. Soft-deleted
|
|
1071
|
+
* (archived) rows are NOT returned by id — the route enforces
|
|
1072
|
+
* `deleted_at IS NULL` so archived creatures 404 (uniform "not_found" — does
|
|
1073
|
+
* not leak the deleted vs non-existent distinction).
|
|
1074
|
+
*/
|
|
1075
|
+
async get(id) {
|
|
1076
|
+
const res = await this.client.request(
|
|
1077
|
+
"GET",
|
|
1078
|
+
`/v1/creatures/${encodeURIComponent(id)}`
|
|
1079
|
+
);
|
|
1080
|
+
return { ...res, canonicalDescription: res.canonicalDescription || null };
|
|
1081
|
+
}
|
|
1082
|
+
/**
|
|
1083
|
+
* Create a new creature. `name` + `nodeId` are required — the route 400s
|
|
1084
|
+
* otherwise. Returns the new row's id.
|
|
1085
|
+
*
|
|
1086
|
+
* Note: the underlying route is the same `POST /v1/creatures` upsert that
|
|
1087
|
+
* powers `update()`. This convenience wrapper enforces the INSERT-required
|
|
1088
|
+
* fields at the type level and never sends an `id`.
|
|
1089
|
+
*/
|
|
1090
|
+
create(data) {
|
|
1091
|
+
return this.client.request("POST", "/v1/creatures", { body: data });
|
|
1092
|
+
}
|
|
1093
|
+
/**
|
|
1094
|
+
* Update a creature. Only the fields you pass are written — undefined keys
|
|
1095
|
+
* are NOT touched on the row. Worker-owned asset buckets are intentionally
|
|
1096
|
+
* not exposed on this surface (see `UpdateCreatureInput` for the rationale).
|
|
1097
|
+
*
|
|
1098
|
+
* Optimistic-concurrency: pass `expectedUpdatedAt` to require the row's
|
|
1099
|
+
* `updated_at` still matches; on mismatch the route returns 409
|
|
1100
|
+
* `concurrent_modification` carrying the fresh `updatedAt`. The SDK
|
|
1101
|
+
* surfaces that as a generic `NodaroError` with the same code.
|
|
1102
|
+
*/
|
|
1103
|
+
update(id, data) {
|
|
1104
|
+
return this.client.request("POST", "/v1/creatures", {
|
|
1105
|
+
body: { id, ...data }
|
|
1106
|
+
});
|
|
1107
|
+
}
|
|
1108
|
+
/**
|
|
1109
|
+
* Soft-delete (archive) a creature. The row is hidden from `list()` by
|
|
1110
|
+
* default but recoverable via `restore(id)` or visible under
|
|
1111
|
+
* `listArchived()`. Idempotent — repeating a delete on an already-archived
|
|
1112
|
+
* row is a no-op.
|
|
1113
|
+
*/
|
|
1114
|
+
delete(id) {
|
|
1115
|
+
return this.client.request("DELETE", `/v1/creatures/${encodeURIComponent(id)}`);
|
|
1116
|
+
}
|
|
1117
|
+
/**
|
|
1118
|
+
* Hard-delete (permanent) a creature — the row + every R2 asset it
|
|
1119
|
+
* references. Archived rows ONLY: active creatures return 400 `not_archived`.
|
|
1120
|
+
* Call `delete()` first to archive, then `permanentDelete()` to destroy.
|
|
1121
|
+
*
|
|
1122
|
+
* Mirrors the `app_runs` permanent-delete pattern (archive-first) so a
|
|
1123
|
+
* stray SDK / curl caller cannot bypass the studio's archive-first UI
|
|
1124
|
+
* flow.
|
|
1125
|
+
*/
|
|
1126
|
+
permanentDelete(id) {
|
|
1127
|
+
return this.client.request("DELETE", `/v1/creatures/${encodeURIComponent(id)}`, {
|
|
1128
|
+
query: { permanent: "true" }
|
|
1129
|
+
});
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Un-archive a creature. If the original name now collides (case-
|
|
1133
|
+
* insensitive) with an active row, the server auto-suffixes "(restored)"
|
|
1134
|
+
* and returns the effective name.
|
|
1135
|
+
*/
|
|
1136
|
+
restore(id) {
|
|
1137
|
+
return this.client.request("POST", `/v1/creatures/${encodeURIComponent(id)}/restore`);
|
|
1138
|
+
}
|
|
1139
|
+
/**
|
|
1140
|
+
* Fire `POST /v1/generate-creature` to produce one or more candidate main
|
|
1141
|
+
* images. With `count > 1`, all jobs are reserved up-front before any
|
|
1142
|
+
* is enqueued — mid-batch failures roll back atomically.
|
|
1143
|
+
*
|
|
1144
|
+
* When `attachToCreatureId` is set AND `count === 1`, the worker writes
|
|
1145
|
+
* the result directly to the row's `source_image_url`; otherwise you must
|
|
1146
|
+
* call `approveMainImage()` after picking a candidate.
|
|
1147
|
+
*/
|
|
1148
|
+
async generate(data) {
|
|
1149
|
+
const res = await this.client.request(
|
|
1150
|
+
"POST",
|
|
1151
|
+
"/v1/generate-creature",
|
|
1152
|
+
{ body: data }
|
|
1153
|
+
);
|
|
1154
|
+
const jobIds = res.jobIds ?? (res.jobId ? [res.jobId] : []);
|
|
1155
|
+
return res.jobId ? { jobIds, jobId: res.jobId } : { jobIds };
|
|
1156
|
+
}
|
|
1157
|
+
/**
|
|
1158
|
+
* Fire `POST /v1/generate-creature-asset` to produce a single variant.
|
|
1159
|
+
* When the studio path is set (`attachToCreatureId` + `attachToColumn` +
|
|
1160
|
+
* `attachName`), the worker appends `{ name: attachName, url: <result> }`
|
|
1161
|
+
* to the named JSONB array column on completion.
|
|
1162
|
+
*
|
|
1163
|
+
* Note: `attachToColumn` is REQUIRED for `assetType === "custom"` — the
|
|
1164
|
+
* worker can't infer the bucket from the asset type. For canonical asset
|
|
1165
|
+
* types (`angles` / `poses` / `variations`), the column is derived
|
|
1166
|
+
* automatically by the route.
|
|
1167
|
+
*/
|
|
1168
|
+
generateAsset(data) {
|
|
1169
|
+
return this.client.request("POST", "/v1/generate-creature-asset", { body: data });
|
|
1170
|
+
}
|
|
1171
|
+
/**
|
|
1172
|
+
* Fire `POST /v1/generate-creature-motion` to animate the creature's main
|
|
1173
|
+
* image into a motion clip. Image-to-video, single clip per call; the
|
|
1174
|
+
* attach column is hardcoded to `motion_clips` server-side (creatures have a
|
|
1175
|
+
* single motion bucket so the caller doesn't supply `attachToColumn`).
|
|
1176
|
+
* When the studio path is set (`attachToCreatureId` + `attachName`), the
|
|
1177
|
+
* worker appends `{ name: attachName, url: <result> }` to the row's
|
|
1178
|
+
* `motion_clips` column on completion.
|
|
1179
|
+
*
|
|
1180
|
+
* Defaults: `provider` → `"kling-turbo"`, `aspectRatio` → `"1:1"` (set
|
|
1181
|
+
* server-side via `resolveObjectAspectRatio({ assetType: "motion" })`).
|
|
1182
|
+
*/
|
|
1183
|
+
generateMotion(data) {
|
|
1184
|
+
return this.client.request("POST", "/v1/generate-creature-motion", { body: data });
|
|
1185
|
+
}
|
|
1186
|
+
/**
|
|
1187
|
+
* Approve a completed `generate-creature` job as the creature's main image.
|
|
1188
|
+
* Sets `source_image_url` and fires the LLM caption (Claude Sonnet vision)
|
|
1189
|
+
* inline. Returns the new main-image URL plus the caption.
|
|
1190
|
+
*
|
|
1191
|
+
* Caption-failure semantics: the route still sends `""` on LLM sub-failure,
|
|
1192
|
+
* but the SDK normalizes `""` → `null` here so `canonicalDescription` carries
|
|
1193
|
+
* the same `string | null` semantics as characters. The main image is still
|
|
1194
|
+
* set; call `recaption()` to retry.
|
|
1195
|
+
*
|
|
1196
|
+
* Optimistic-concurrency: pass `expectedUpdatedAt` to gate the update on
|
|
1197
|
+
* the row's current `updated_at`; on mismatch the route returns 409
|
|
1198
|
+
* `concurrent_modification` carrying the fresh token.
|
|
1199
|
+
*/
|
|
1200
|
+
async approveMainImage(id, candidateJobId, expectedUpdatedAt) {
|
|
1201
|
+
const res = await this.client.request(
|
|
1202
|
+
"POST",
|
|
1203
|
+
`/v1/creatures/${encodeURIComponent(id)}/approve-main-image`,
|
|
1204
|
+
{ body: { candidateJobId, expectedUpdatedAt } }
|
|
1205
|
+
);
|
|
1206
|
+
return { sourceImageUrl: res.sourceImageUrl, canonicalDescription: res.canonicalDescription || null };
|
|
1207
|
+
}
|
|
1208
|
+
/**
|
|
1209
|
+
* Re-fire the LLM caption against the creature's current main image. 502s
|
|
1210
|
+
* on LLM failure (unlike `approveMainImage` which preserves the side-effect
|
|
1211
|
+
* and returns ""); returns 400 `main_image_required` if no main image is
|
|
1212
|
+
* set yet.
|
|
1213
|
+
*
|
|
1214
|
+
* The route is a pure idempotent retry — it does NOT accept an
|
|
1215
|
+
* `expectedUpdatedAt` token (backend route is idempotent retry, not gated on
|
|
1216
|
+
* optimistic-concurrency).
|
|
1217
|
+
*/
|
|
1218
|
+
recaption(id) {
|
|
1219
|
+
return this.client.request(
|
|
1220
|
+
"POST",
|
|
1221
|
+
`/v1/creatures/${encodeURIComponent(id)}/llm-caption`
|
|
1222
|
+
);
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
|
|
1226
|
+
// src/resources/pipelines.ts
|
|
1227
|
+
var PipelinesResource = class {
|
|
1228
|
+
constructor(client) {
|
|
1229
|
+
this.client = client;
|
|
1230
|
+
}
|
|
1231
|
+
client;
|
|
1232
|
+
/**
|
|
1233
|
+
* Start a new pipeline (headless film generation) — the programmatic
|
|
1234
|
+
* equivalent of the studio's "Create film". In Auto mode the engine
|
|
1235
|
+
* self-advances to completion; poll {@link get} for status and
|
|
1236
|
+
* {@link getTimeline} for the assembled output. In manual/guided mode, drive
|
|
1237
|
+
* it with {@link pendingApprovals} + {@link approveStage} /
|
|
1238
|
+
* {@link approveSubGate}.
|
|
1239
|
+
*
|
|
1240
|
+
* Requires `pipelines:execute` scope. Returns the new pipeline id.
|
|
1241
|
+
*/
|
|
1242
|
+
create(input) {
|
|
1243
|
+
return this.client.request("POST", "/v1/pipelines", { body: input });
|
|
1244
|
+
}
|
|
1245
|
+
/**
|
|
1246
|
+
* Fetch current pipeline state: `status`, `current_stage`, credit counters,
|
|
1247
|
+
* `mode`, and `failure_reason` (set when `status='failed'`). Poll this to
|
|
1248
|
+
* track a headless Auto run to completion. Requires `pipelines:read`.
|
|
1249
|
+
*/
|
|
1250
|
+
get(id) {
|
|
1251
|
+
return this.client.request(
|
|
1252
|
+
"GET",
|
|
1253
|
+
`/v1/pipelines/${encodeURIComponent(id)}`
|
|
1254
|
+
);
|
|
1255
|
+
}
|
|
1256
|
+
/** List the caller's pipelines (most recent first). Requires `pipelines:read`. */
|
|
1257
|
+
list() {
|
|
1258
|
+
return this.client.request("GET", "/v1/pipelines");
|
|
1259
|
+
}
|
|
1260
|
+
/**
|
|
1261
|
+
* Cancel a running pipeline. Unspent reserved credits refund. Idempotent on
|
|
1262
|
+
* an already-terminal pipeline. Requires `pipelines:execute`.
|
|
1263
|
+
*/
|
|
1264
|
+
cancel(id) {
|
|
1265
|
+
return this.client.request(
|
|
1266
|
+
"POST",
|
|
1267
|
+
`/v1/pipelines/${encodeURIComponent(id)}/cancel`,
|
|
1268
|
+
{ body: {} }
|
|
1269
|
+
);
|
|
1270
|
+
}
|
|
1271
|
+
/**
|
|
1272
|
+
* Stages currently `awaiting_approval`. Empty in a clean Auto run (the engine
|
|
1273
|
+
* self-approves); populated in manual/guided mode at each gate. Requires
|
|
1274
|
+
* `pipelines:read`.
|
|
1275
|
+
*/
|
|
1276
|
+
pendingApprovals(id) {
|
|
1277
|
+
return this.client.request(
|
|
1278
|
+
"GET",
|
|
1279
|
+
`/v1/pipelines/${encodeURIComponent(id)}/pending-approvals`
|
|
1280
|
+
);
|
|
1281
|
+
}
|
|
1282
|
+
/**
|
|
1283
|
+
* Approve a stage so the engine advances to the next one. An optional `edits`
|
|
1284
|
+
* JSON-Patch is applied to the stage output before approval. Requires
|
|
1285
|
+
* `pipelines:approve`.
|
|
1286
|
+
*/
|
|
1287
|
+
approveStage(id, stage, edits) {
|
|
1288
|
+
return this.client.request(
|
|
1289
|
+
"POST",
|
|
1290
|
+
`/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/approve`,
|
|
1291
|
+
{ body: edits ? { edits } : {} }
|
|
1292
|
+
);
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Reject a stage with feedback; the engine re-runs it incorporating the note.
|
|
1296
|
+
* Requires `pipelines:approve`.
|
|
1297
|
+
*/
|
|
1298
|
+
rejectStage(id, stage, feedback) {
|
|
1299
|
+
return this.client.request(
|
|
1300
|
+
"POST",
|
|
1301
|
+
`/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}/reject`,
|
|
1302
|
+
{ body: { feedback } }
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Approve a Stage-7 sub-gate (`dialogue_recheck` / `silent_cut`) so the
|
|
1307
|
+
* orchestrator resumes from the next sub-step. Requires `pipelines:approve`.
|
|
1308
|
+
*/
|
|
1309
|
+
approveSubGate(id, gate) {
|
|
1310
|
+
return this.client.request(
|
|
1311
|
+
"POST",
|
|
1312
|
+
`/v1/pipelines/${encodeURIComponent(id)}/sub-gates/${encodeURIComponent(gate)}/approve`,
|
|
1313
|
+
{ body: {} }
|
|
1314
|
+
);
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Read a single stage's `status`, `output`, and `critic_feedback`. Useful for
|
|
1318
|
+
* inspecting the script/plan before approving. Requires `pipelines:read`.
|
|
1319
|
+
*/
|
|
1320
|
+
getStage(id, stage) {
|
|
1321
|
+
return this.client.request(
|
|
1322
|
+
"GET",
|
|
1323
|
+
`/v1/pipelines/${encodeURIComponent(id)}/stages/${encodeURIComponent(stage)}`
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Assembled timeline — ordered scene composites + durations + audio URLs +
|
|
1328
|
+
* live animate progress. The output a headless caller renders or hands to a
|
|
1329
|
+
* downstream editor. Requires `pipelines:read`.
|
|
1330
|
+
*/
|
|
1331
|
+
getTimeline(id) {
|
|
1332
|
+
return this.client.request(
|
|
1333
|
+
"GET",
|
|
1334
|
+
`/v1/pipelines/${encodeURIComponent(id)}/timeline`
|
|
1335
|
+
);
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Branch a completed pipeline into a new pipeline that re-runs from the
|
|
1339
|
+
* given stage. The original pipeline's upstream stages and entities are
|
|
1340
|
+
* cloned into the new pipeline; downstream stages are created by the
|
|
1341
|
+
* orchestrator as it advances.
|
|
1342
|
+
*
|
|
1343
|
+
* Requires `pipelines:execute` scope.
|
|
1344
|
+
* The source pipeline must have `status='completed'`.
|
|
1345
|
+
*
|
|
1346
|
+
* @returns 201 with `{ pipelineId, clonedStages, clonedEntities }`.
|
|
1347
|
+
*/
|
|
1348
|
+
branch(id, input) {
|
|
1349
|
+
return this.client.request(
|
|
1350
|
+
"POST",
|
|
1351
|
+
`/v1/pipelines/${encodeURIComponent(id)}/branch`,
|
|
1352
|
+
{ body: input }
|
|
1353
|
+
);
|
|
1354
|
+
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Send a chat message to the Showrunner Refinement Director (Guided Mode).
|
|
1357
|
+
* Persists user + assistant turns; returns the assistant's reply and an
|
|
1358
|
+
* optional `proposed_change` the user can `applyChatProposal` to commit.
|
|
1359
|
+
*
|
|
1360
|
+
* Requires `pipelines:approve` scope. The pipeline must have
|
|
1361
|
+
* `mode='guided'` and the stage must be `awaiting_approval`.
|
|
1362
|
+
*
|
|
1363
|
+
* Only the Script stage ships a wired specialist in Phase 1D.2b — the other
|
|
1364
|
+
* chat-enabled stages (`shot_list`, `post_merge`) return 501 until 1D.2d.
|
|
1365
|
+
*/
|
|
1366
|
+
chatStage(pipelineId, stage, message) {
|
|
1367
|
+
return this.client.request(
|
|
1368
|
+
"POST",
|
|
1369
|
+
`/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`,
|
|
1370
|
+
{ body: { message } }
|
|
1371
|
+
);
|
|
1372
|
+
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Accept a proposed change from a prior assistant turn. Routes through
|
|
1375
|
+
* `applyStageEdit` (validates JSON Patch + per-stage schema +
|
|
1376
|
+
* reference-integrity, inserts a new pipeline_stage_attempts row, CAS-flips
|
|
1377
|
+
* the stage to approved, emits `chat:proposal_applied` SSE).
|
|
1378
|
+
*
|
|
1379
|
+
* Requires `pipelines:approve` scope.
|
|
1380
|
+
*
|
|
1381
|
+
* Returns `{ applied: true, attemptId, newOutput }` on success, or
|
|
1382
|
+
* `{ applied: false, error }` on recoverable failures (the backend already
|
|
1383
|
+
* inserted a follow-up assistant turn with a hint). Hard failures
|
|
1384
|
+
* (`patch_invalid`, `stage_not_awaiting`) throw via the standard error
|
|
1385
|
+
* pipeline (HTTP 409).
|
|
1386
|
+
*/
|
|
1387
|
+
applyChatProposal(pipelineId, stage, turnId) {
|
|
1388
|
+
return this.client.request(
|
|
1389
|
+
"POST",
|
|
1390
|
+
`/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat/turns/${encodeURIComponent(turnId)}/apply`,
|
|
1391
|
+
{ body: {} }
|
|
1392
|
+
);
|
|
1393
|
+
}
|
|
1394
|
+
/**
|
|
1395
|
+
* Fetch the chat history for a stage. Returns an empty array when no turns
|
|
1396
|
+
* exist yet (e.g., stage has not been started or the user hasn't sent any
|
|
1397
|
+
* messages). Used by the frontend chat panel on initial mount; subsequent
|
|
1398
|
+
* updates arrive via SSE (`chat:turn` events).
|
|
1399
|
+
*
|
|
1400
|
+
* Requires `pipelines:read` scope.
|
|
1401
|
+
*/
|
|
1402
|
+
getStageChat(pipelineId, stage) {
|
|
1403
|
+
return this.client.request(
|
|
1404
|
+
"GET",
|
|
1405
|
+
`/v1/pipelines/${encodeURIComponent(pipelineId)}/stages/${encodeURIComponent(stage)}/chat`
|
|
1406
|
+
);
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
|
|
1410
|
+
// src/resources/reduce.ts
|
|
1411
|
+
var ReduceResource = class {
|
|
1412
|
+
constructor(client) {
|
|
1413
|
+
this.client = client;
|
|
1414
|
+
}
|
|
1415
|
+
client;
|
|
1416
|
+
/**
|
|
1417
|
+
* Run the Reduce (fan-in) node directly — useful for scripted batch
|
|
1418
|
+
* scoring, picking the best of N generations outside a workflow, or
|
|
1419
|
+
* one-shot programmatic merges.
|
|
1420
|
+
*
|
|
1421
|
+
* Throws `NodaroError` on 4xx/5xx responses (e.g. `code: "no_valid_inputs"`
|
|
1422
|
+
* with status 400 when every input is empty / whitespace; the underlying
|
|
1423
|
+
* `EmptyInputError` is mapped to a 400 server-side).
|
|
1424
|
+
*/
|
|
1425
|
+
run(input) {
|
|
1426
|
+
return this.client.request("POST", "/v1/reduce", {
|
|
1427
|
+
body: {
|
|
1428
|
+
strategyId: input.strategyId,
|
|
1429
|
+
strategyConfig: input.strategyConfig ?? {},
|
|
1430
|
+
inputs: input.inputs,
|
|
1431
|
+
...input.workflowId !== void 0 ? { workflowId: input.workflowId } : {}
|
|
1432
|
+
}
|
|
1433
|
+
});
|
|
1434
|
+
}
|
|
1435
|
+
};
|
|
1436
|
+
|
|
1437
|
+
// src/resources/prompt-helper.ts
|
|
1438
|
+
var PromptHelperResource = class {
|
|
1439
|
+
constructor(client) {
|
|
1440
|
+
this.client = client;
|
|
1441
|
+
}
|
|
1442
|
+
client;
|
|
1443
|
+
analyze(input) {
|
|
1444
|
+
return this.client.request("POST", "/v1/prompt-helper/wizard", { body: { action: "analyze", ...input } });
|
|
1445
|
+
}
|
|
1446
|
+
generate(input) {
|
|
1447
|
+
return this.client.request("POST", "/v1/prompt-helper/wizard", { body: { action: "generate", ...input } });
|
|
1448
|
+
}
|
|
1449
|
+
enhance(input) {
|
|
1450
|
+
return this.client.request("POST", "/v1/prompt-helper/wizard", { body: { action: "enhance", ...input } });
|
|
1451
|
+
}
|
|
1452
|
+
};
|
|
1453
|
+
|
|
1454
|
+
// src/resources/voices.ts
|
|
1455
|
+
var VoicesResource = class {
|
|
1456
|
+
constructor(client) {
|
|
1457
|
+
this.client = client;
|
|
1458
|
+
}
|
|
1459
|
+
client;
|
|
1460
|
+
/**
|
|
1461
|
+
* List the premade ElevenLabs voices (`GET /v1/voices`). Falls back to a
|
|
1462
|
+
* curated set server-side when no ElevenLabs API key is configured.
|
|
1463
|
+
*/
|
|
1464
|
+
async list() {
|
|
1465
|
+
const res = await this.client.request("GET", "/v1/voices");
|
|
1466
|
+
return res.voices;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Search the shared/community Voice Library (`GET /v1/voices/library`). All
|
|
1470
|
+
* params are optional and forwarded as a querystring; `undefined` / `null` /
|
|
1471
|
+
* empty-string values are omitted so the server defaults apply. `hasMore`
|
|
1472
|
+
* drives "load more" pagination.
|
|
1473
|
+
*/
|
|
1474
|
+
searchLibrary(params = {}) {
|
|
1475
|
+
const query = {};
|
|
1476
|
+
for (const [k, v] of Object.entries(params)) {
|
|
1477
|
+
if (v !== void 0 && v !== null && v !== "") query[k] = v;
|
|
1478
|
+
}
|
|
1479
|
+
return this.client.request("GET", "/v1/voices/library", { query });
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* List the signed-in user's voice clones (`GET /v1/voice-clones`). The route
|
|
1483
|
+
* wraps the rows in `{ voiceClones }`; we unwrap to the bare array.
|
|
1484
|
+
*/
|
|
1485
|
+
async listClones() {
|
|
1486
|
+
const res = await this.client.request("GET", "/v1/voice-clones");
|
|
1487
|
+
return res.voiceClones;
|
|
1488
|
+
}
|
|
1489
|
+
/**
|
|
1490
|
+
* Clone a voice from an already-uploaded audio URL
|
|
1491
|
+
* (`POST /v1/voice-clones/from-url`). Costs credits. Returns the create
|
|
1492
|
+
* subset of `VoiceClone` (`elevenlabsVoiceId` is the id to use at
|
|
1493
|
+
* text-to-speech time).
|
|
1494
|
+
*/
|
|
1495
|
+
createClone(input) {
|
|
1496
|
+
return this.client.request("POST", "/v1/voice-clones/from-url", { body: input });
|
|
1497
|
+
}
|
|
1498
|
+
/** Delete one of the user's voice clones (`DELETE /v1/voice-clones/:id`). */
|
|
1499
|
+
async deleteClone(id) {
|
|
1500
|
+
await this.client.request("DELETE", `/v1/voice-clones/${encodeURIComponent(id)}`);
|
|
1501
|
+
}
|
|
1502
|
+
/**
|
|
1503
|
+
* Replace the voice in a recording — or in a whole talking video — with a
|
|
1504
|
+
* different voice (`POST /v1/voice-changer`). Pass `audioUrl` to revoice
|
|
1505
|
+
* audio→audio, or `videoUrl` to revoice an entire clip (the server demuxes
|
|
1506
|
+
* the audio, runs speech-to-speech, and remuxes onto the original video,
|
|
1507
|
+
* returning the video plus the new audio track). Exactly one of `audioUrl` /
|
|
1508
|
+
* `videoUrl` is required; when both are sent, video wins. `removeBackgroundNoise`
|
|
1509
|
+
* off keeps the music/SFX bed under the new voice; on yields a clean voice-only
|
|
1510
|
+
* result. Costs credits and runs async — poll `jobs.get(jobId)` for the result
|
|
1511
|
+
* (`output_data.videoUrl` + `output_data.audioUrl` in video mode).
|
|
1512
|
+
*/
|
|
1513
|
+
change(input) {
|
|
1514
|
+
return this.client.request("POST", "/v1/voice-changer", { body: input });
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Recast each detected speaker in a multi-speaker recording to a different
|
|
1518
|
+
* voice (`POST /v1/voice-changer-pro`). `orderedVoices` maps speaker positions to
|
|
1519
|
+
* voices in detection order — speaker 0 → `orderedVoices[0]`, speaker 1 →
|
|
1520
|
+
* `orderedVoices[1]`, etc. Speakers beyond the end of `orderedVoices` keep
|
|
1521
|
+
* their original voice. Each entry is EITHER a bare voice id (premade name or
|
|
1522
|
+
* ElevenLabs UUID) OR a {@link VoiceChangerProVoice} object carrying per-voice
|
|
1523
|
+
* ElevenLabs speech-to-speech settings (stability / similarityBoost / style /
|
|
1524
|
+
* useSpeakerBoost / `seed`) plus a loudness `volumeMode` (and a manual
|
|
1525
|
+
* `volume`). A per-voice `seed` makes that speaker's recast reproducible.
|
|
1526
|
+
*
|
|
1527
|
+
* Pass `audioUrl` for audio-only recast or `videoUrl` to recast the audio
|
|
1528
|
+
* track of a video clip (the server demuxes, recasts, and remuxes).
|
|
1529
|
+
*
|
|
1530
|
+
* Voice and music are ALWAYS separated first — ElevenLabs only ever sees the
|
|
1531
|
+
* isolated vocal stem, never the music bed. `preserveBackground` (default
|
|
1532
|
+
* `true`) only controls whether that music/instrumental stem is mixed back
|
|
1533
|
+
* under the new voices; set it `false` for a clean voice-only result.
|
|
1534
|
+
* `separationQuality` selects the demucs model used for that split: `"fast"`
|
|
1535
|
+
* (default, htdemucs — preserves more of the voice) or `"best"` (htdemucs_ft —
|
|
1536
|
+
* finer separation). `removeBackgroundNoise` additionally denoises the result.
|
|
1537
|
+
* `musicVolumeMode` sets the level of that preserved background (only relevant
|
|
1538
|
+
* when `preserveBackground` is on): `"match"` (default) keeps the original
|
|
1539
|
+
* level, `"normalize"` loudnorms it, `"manual"` uses `musicVolume`%.
|
|
1540
|
+
* `voiceFx` applies a reverb/echo to the COMBINED recast voices BEFORE the
|
|
1541
|
+
* background is mixed back in (effect sits on the voices, not the music bed).
|
|
1542
|
+
*
|
|
1543
|
+
* Cloud-only — costs credits and runs async; poll `jobs.get(jobId)` for the
|
|
1544
|
+
* result (`output_data.videoUrl` + `output_data.audioUrl` in video mode).
|
|
1545
|
+
*/
|
|
1546
|
+
recast(input) {
|
|
1547
|
+
return this.client.request("POST", "/v1/voice-changer-pro", { body: input });
|
|
1548
|
+
}
|
|
1549
|
+
};
|
|
1550
|
+
|
|
1551
|
+
// src/resources/credits.ts
|
|
1552
|
+
var MODEL_COSTS_LIMIT = 50;
|
|
1553
|
+
var CreditsResource = class {
|
|
1554
|
+
constructor(client) {
|
|
1555
|
+
this.client = client;
|
|
1556
|
+
}
|
|
1557
|
+
client;
|
|
1558
|
+
/**
|
|
1559
|
+
* `GET /v1/user/credits` → the authenticated user's credit balance and tier
|
|
1560
|
+
* info. Throws `UnauthorizedError` (401) when signed out, and the SDK's
|
|
1561
|
+
* other typed errors on the usual statuses.
|
|
1562
|
+
*/
|
|
1563
|
+
async balance() {
|
|
1564
|
+
const res = await this.client.request(
|
|
1565
|
+
"GET",
|
|
1566
|
+
"/v1/user/credits"
|
|
1567
|
+
);
|
|
1568
|
+
return res.data;
|
|
1569
|
+
}
|
|
1570
|
+
/**
|
|
1571
|
+
* `POST /v1/credits/model-costs` → per-identifier credit cost, for editor
|
|
1572
|
+
* cost previews. Capped at the first {@link MODEL_COSTS_LIMIT} identifiers
|
|
1573
|
+
* (the route's request limit). Preserves the `{ data, missing, errors }`
|
|
1574
|
+
* fault-isolation shape verbatim (see {@link ModelCostsResult}).
|
|
1575
|
+
*/
|
|
1576
|
+
modelCosts(ids) {
|
|
1577
|
+
return this.client.request("POST", "/v1/credits/model-costs", {
|
|
1578
|
+
body: { models: ids.slice(0, MODEL_COSTS_LIMIT) }
|
|
1579
|
+
});
|
|
1580
|
+
}
|
|
1581
|
+
// NOTE: no `estimate(...)` helper. The backend exposes
|
|
1582
|
+
// `POST /v1/credits/estimate-workflow` (body `{ nodes }` → `{ data: {
|
|
1583
|
+
// totalCredits, nodeCount } }`), but no consumer has a settled shape for it
|
|
1584
|
+
// yet (studio pre-checks `balance >= Σ modelCosts` client-side rather than
|
|
1585
|
+
// calling an estimate endpoint). Adding it now would be inventing an API
|
|
1586
|
+
// surface ahead of a real caller, so it's deliberately omitted — add it when
|
|
1587
|
+
// a consumer needs it, shaped to that need.
|
|
1588
|
+
};
|
|
1589
|
+
|
|
1590
|
+
// src/resources/uploads.ts
|
|
1591
|
+
var UploadsResource = class {
|
|
1592
|
+
constructor(client) {
|
|
1593
|
+
this.client = client;
|
|
1594
|
+
}
|
|
1595
|
+
client;
|
|
1596
|
+
/**
|
|
1597
|
+
* Upload one file (`POST /v1/upload`, multipart — the file rides the `file`
|
|
1598
|
+
* field). The SDK's `request` detects the `FormData` body and lets the
|
|
1599
|
+
* runtime set the multipart boundary. Returns the persisted asset's public
|
|
1600
|
+
* URL + storage metadata (unwraps the `{ data }` envelope). Throws
|
|
1601
|
+
* `StorageExceededError` (413) over the storage cap and the SDK's other typed
|
|
1602
|
+
* errors on the usual statuses.
|
|
1603
|
+
*/
|
|
1604
|
+
async upload(file) {
|
|
1605
|
+
const form = new FormData();
|
|
1606
|
+
form.append("file", file);
|
|
1607
|
+
const res = await this.client.request(
|
|
1608
|
+
"POST",
|
|
1609
|
+
"/v1/upload",
|
|
1610
|
+
{ body: form }
|
|
1611
|
+
);
|
|
1612
|
+
return res.data;
|
|
1613
|
+
}
|
|
1614
|
+
};
|
|
1615
|
+
|
|
1616
|
+
// src/resources/library.ts
|
|
1617
|
+
var LibraryResource = class {
|
|
1618
|
+
constructor(client) {
|
|
1619
|
+
this.client = client;
|
|
1620
|
+
}
|
|
1621
|
+
client;
|
|
1622
|
+
/**
|
|
1623
|
+
* `GET /v1/library` → a page of the caller's media assets (newest first) plus
|
|
1624
|
+
* a `nextCursor`. Pass the returned `nextCursor` back as `cursor` for the next
|
|
1625
|
+
* page. Filter by `type` and a filename `search`; `owned: true` returns the
|
|
1626
|
+
* full Storage set (uploads + generations), the default only library-saved +
|
|
1627
|
+
* shared items.
|
|
1628
|
+
*/
|
|
1629
|
+
list(params = {}) {
|
|
1630
|
+
const qs = new URLSearchParams();
|
|
1631
|
+
if (params.type) qs.set("type", params.type);
|
|
1632
|
+
if (params.search) qs.set("search", params.search);
|
|
1633
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
1634
|
+
if (params.cursor) qs.set("cursor", params.cursor);
|
|
1635
|
+
if (params.owned !== void 0) qs.set("owned", String(params.owned));
|
|
1636
|
+
const query = qs.toString();
|
|
1637
|
+
return this.client.request("GET", `/v1/library${query ? `?${query}` : ""}`);
|
|
1638
|
+
}
|
|
1639
|
+
};
|
|
1640
|
+
|
|
1641
|
+
// src/resources/node-presets.ts
|
|
1642
|
+
var PresetsResource = class {
|
|
1643
|
+
constructor(client) {
|
|
1644
|
+
this.client = client;
|
|
1645
|
+
}
|
|
1646
|
+
client;
|
|
1647
|
+
/**
|
|
1648
|
+
* `GET /v1/node-presets` → your custom presets, newest first. Pass `nodeType`
|
|
1649
|
+
* (e.g. `"generate-image"`) to filter to one node type.
|
|
1650
|
+
*/
|
|
1651
|
+
async list(nodeType) {
|
|
1652
|
+
const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : "";
|
|
1653
|
+
const res = await this.client.request("GET", `/v1/node-presets${qs}`);
|
|
1654
|
+
return res.data;
|
|
1655
|
+
}
|
|
1656
|
+
/**
|
|
1657
|
+
* `GET /v1/node-preset-groups` → your preset folders/sections, in display
|
|
1658
|
+
* order. Pass `nodeType` to filter to one node type.
|
|
1659
|
+
*/
|
|
1660
|
+
async listGroups(nodeType) {
|
|
1661
|
+
const qs = nodeType ? `?nodeType=${encodeURIComponent(nodeType)}` : "";
|
|
1662
|
+
const res = await this.client.request("GET", `/v1/node-preset-groups${qs}`);
|
|
1663
|
+
return res.data;
|
|
1664
|
+
}
|
|
1665
|
+
/**
|
|
1666
|
+
* `GET /v1/node-presets/factory` → the built-in catalog for `nodeType`. These
|
|
1667
|
+
* ship with the app (no account needed to exist), so they're a good starting
|
|
1668
|
+
* point for "what configs are available".
|
|
1669
|
+
*/
|
|
1670
|
+
listFactory(nodeType) {
|
|
1671
|
+
return this.client.request(
|
|
1672
|
+
"GET",
|
|
1673
|
+
`/v1/node-presets/factory?nodeType=${encodeURIComponent(nodeType)}`
|
|
1674
|
+
);
|
|
1675
|
+
}
|
|
1676
|
+
};
|
|
1677
|
+
|
|
1678
|
+
// src/resources/picker-catalogs.ts
|
|
1679
|
+
var PickerCatalogsResource = class {
|
|
1680
|
+
constructor(client) {
|
|
1681
|
+
this.client = client;
|
|
1682
|
+
}
|
|
1683
|
+
client;
|
|
1684
|
+
/** List every parameter-picker node type + its option count. Cached publicly 5 min. */
|
|
1685
|
+
list() {
|
|
1686
|
+
return this.client.request("GET", "/v1/picker-catalogs");
|
|
1687
|
+
}
|
|
1688
|
+
/** Get one picker's catalog of valid values. */
|
|
1689
|
+
get(nodeType, opts = {}) {
|
|
1690
|
+
const qs = new URLSearchParams();
|
|
1691
|
+
if (opts.detail) qs.set("detail", opts.detail);
|
|
1692
|
+
if (opts.category) qs.set("category", opts.category);
|
|
1693
|
+
if (opts.field) qs.set("field", opts.field);
|
|
1694
|
+
const query = qs.toString();
|
|
1695
|
+
return this.client.request(
|
|
1696
|
+
"GET",
|
|
1697
|
+
`/v1/picker-catalogs/${encodeURIComponent(nodeType)}${query ? `?${query}` : ""}`
|
|
1698
|
+
);
|
|
1699
|
+
}
|
|
1700
|
+
};
|
|
1701
|
+
|
|
1702
|
+
// src/resources/community.ts
|
|
1703
|
+
var CommunityResource = class {
|
|
1704
|
+
constructor(client) {
|
|
1705
|
+
this.client = client;
|
|
1706
|
+
}
|
|
1707
|
+
client;
|
|
1708
|
+
/**
|
|
1709
|
+
* `GET /v1/community/browse` → a page of public listings plus a `nextCursor`.
|
|
1710
|
+
* Pass the returned `nextCursor` back as `cursor` to fetch the next page.
|
|
1711
|
+
*/
|
|
1712
|
+
browse(params = {}) {
|
|
1713
|
+
const qs = new URLSearchParams();
|
|
1714
|
+
if (params.entityType) qs.set("entityType", params.entityType);
|
|
1715
|
+
if (params.q) qs.set("q", params.q);
|
|
1716
|
+
if (params.category) qs.set("category", params.category);
|
|
1717
|
+
if (params.sort) qs.set("sort", params.sort);
|
|
1718
|
+
if (params.cursor) qs.set("cursor", params.cursor);
|
|
1719
|
+
if (params.limit !== void 0) qs.set("limit", String(params.limit));
|
|
1720
|
+
const query = qs.toString();
|
|
1721
|
+
return this.client.request(
|
|
1722
|
+
"GET",
|
|
1723
|
+
`/v1/community/browse${query ? `?${query}` : ""}`
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
/** `GET /v1/community/detail/:slug` → a single listing by its slug. */
|
|
1727
|
+
get(slug) {
|
|
1728
|
+
return this.client.request(
|
|
1729
|
+
"GET",
|
|
1730
|
+
`/v1/community/detail/${encodeURIComponent(slug)}`
|
|
1731
|
+
);
|
|
1732
|
+
}
|
|
1733
|
+
/**
|
|
1734
|
+
* `GET /v1/community/detail/:slug/full` → the full read-only detail (card
|
|
1735
|
+
* identity + the stored public snapshot). Like {@link get}, but includes the
|
|
1736
|
+
* snapshot asset/voice/text blob needed to render the full cross-user view.
|
|
1737
|
+
*/
|
|
1738
|
+
getFull(slug) {
|
|
1739
|
+
return this.client.request(
|
|
1740
|
+
"GET",
|
|
1741
|
+
`/v1/community/detail/${encodeURIComponent(slug)}/full`
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
/** `GET /v1/community/favorites` → the listings you've favorited. */
|
|
1745
|
+
favorites() {
|
|
1746
|
+
return this.client.request("GET", "/v1/community/favorites");
|
|
1747
|
+
}
|
|
1748
|
+
/**
|
|
1749
|
+
* `POST /v1/community/listings/:id/clone` → copy a listing into your library.
|
|
1750
|
+
* Returns the new asset's `entityType` and `id`. Requires the `assets:write`
|
|
1751
|
+
* scope when called with an OAuth app token.
|
|
1752
|
+
*/
|
|
1753
|
+
clone(id, entityType) {
|
|
1754
|
+
return this.client.request(
|
|
1755
|
+
"POST",
|
|
1756
|
+
`/v1/community/listings/${encodeURIComponent(id)}/clone`,
|
|
1757
|
+
{ body: { entityType } }
|
|
1758
|
+
);
|
|
1759
|
+
}
|
|
1760
|
+
/**
|
|
1761
|
+
* `POST /v1/community/listings/:id/favorite` → toggle a favorite. Returns the
|
|
1762
|
+
* resulting `favorited` state (`true` after adding, `false` after removing).
|
|
1763
|
+
*/
|
|
1764
|
+
favorite(id) {
|
|
1765
|
+
return this.client.request(
|
|
1766
|
+
"POST",
|
|
1767
|
+
`/v1/community/listings/${encodeURIComponent(id)}/favorite`
|
|
1768
|
+
);
|
|
1769
|
+
}
|
|
1770
|
+
/**
|
|
1771
|
+
* `POST /v1/community/listings/:id/report` → flag a listing for moderation.
|
|
1772
|
+
* `reason` must be one of {@link CommunityReportReason}.
|
|
1773
|
+
*/
|
|
1774
|
+
report(id, reason) {
|
|
1775
|
+
return this.client.request(
|
|
1776
|
+
"POST",
|
|
1777
|
+
`/v1/community/listings/${encodeURIComponent(id)}/report`,
|
|
1778
|
+
{ body: { reason } }
|
|
1779
|
+
);
|
|
1780
|
+
}
|
|
1781
|
+
/**
|
|
1782
|
+
* `POST /v1/admin/community/:entityType/:id/publish` → share one of YOUR
|
|
1783
|
+
* entities to the community, returning the new listing's `slug` + `id`.
|
|
1784
|
+
*
|
|
1785
|
+
* **Requires an admin token** (the route is `requireAdmin`) AND the caller
|
|
1786
|
+
* must own the source entity. Personal/OAuth tokens without admin role get a
|
|
1787
|
+
* 401. For `character` listings, `params.likenessAttestation` must be `true`.
|
|
1788
|
+
*/
|
|
1789
|
+
publish(entityType, entityId, params) {
|
|
1790
|
+
return this.client.request(
|
|
1791
|
+
"POST",
|
|
1792
|
+
`/v1/admin/community/${encodeURIComponent(entityType)}/${encodeURIComponent(entityId)}/publish`,
|
|
1793
|
+
{ body: params }
|
|
1794
|
+
);
|
|
1795
|
+
}
|
|
1796
|
+
/**
|
|
1797
|
+
* `DELETE /v1/admin/community/listings/:id` → unshare (deactivate) a listing
|
|
1798
|
+
* you published. **Requires an admin token** (the route is `requireAdmin`).
|
|
1799
|
+
*/
|
|
1800
|
+
unpublish(listingId) {
|
|
1801
|
+
return this.client.request(
|
|
1802
|
+
"DELETE",
|
|
1803
|
+
`/v1/admin/community/listings/${encodeURIComponent(listingId)}`
|
|
1804
|
+
);
|
|
1805
|
+
}
|
|
1806
|
+
/**
|
|
1807
|
+
* `GET /v1/admin/community/by-source/:entityType/:sourceId` → look up YOUR
|
|
1808
|
+
* existing listing (if any) for a source entity. Returns `{ data: null }`
|
|
1809
|
+
* when the entity hasn't been shared. **Requires an admin token** (the route
|
|
1810
|
+
* is `requireAdmin`); only returns listings created by the caller.
|
|
1811
|
+
*/
|
|
1812
|
+
sharedListing(entityType, sourceId) {
|
|
1813
|
+
return this.client.request(
|
|
1814
|
+
"GET",
|
|
1815
|
+
`/v1/admin/community/by-source/${encodeURIComponent(entityType)}/${encodeURIComponent(sourceId)}`
|
|
1816
|
+
);
|
|
1817
|
+
}
|
|
1818
|
+
};
|
|
1819
|
+
|
|
1820
|
+
// src/client.ts
|
|
1821
|
+
var NodaroClient = class {
|
|
1822
|
+
baseUrl;
|
|
1823
|
+
auth;
|
|
1824
|
+
timeoutMs;
|
|
1825
|
+
fetchOverride;
|
|
1826
|
+
/**
|
|
1827
|
+
* Resolved lazily so consumers can swap `globalThis.fetch` after the
|
|
1828
|
+
* client has been constructed (e.g. test mocks). Always rebound to the
|
|
1829
|
+
* global object — native fetch throws "Illegal invocation" when its
|
|
1830
|
+
* `this` is anything else.
|
|
1831
|
+
*/
|
|
1832
|
+
get fetch() {
|
|
1833
|
+
return this.fetchOverride ?? globalThis.fetch.bind(globalThis);
|
|
1834
|
+
}
|
|
1835
|
+
workflows;
|
|
1836
|
+
projects;
|
|
1837
|
+
jobs;
|
|
1838
|
+
executions;
|
|
1839
|
+
nodes;
|
|
1840
|
+
developerApps;
|
|
1841
|
+
oauth;
|
|
1842
|
+
apps;
|
|
1843
|
+
characters;
|
|
1844
|
+
locations;
|
|
1845
|
+
objects;
|
|
1846
|
+
creatures;
|
|
1847
|
+
pipelines;
|
|
1848
|
+
reduce;
|
|
1849
|
+
promptHelper;
|
|
1850
|
+
voices;
|
|
1851
|
+
credits;
|
|
1852
|
+
uploads;
|
|
1853
|
+
library;
|
|
1854
|
+
presets;
|
|
1855
|
+
pickerCatalogs;
|
|
1856
|
+
community;
|
|
1857
|
+
constructor(opts) {
|
|
1858
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
1859
|
+
this.auth = opts.auth;
|
|
1860
|
+
this.fetchOverride = opts.fetch;
|
|
1861
|
+
this.timeoutMs = opts.timeoutMs ?? 6e4;
|
|
1862
|
+
this.workflows = new WorkflowsResource(this);
|
|
1863
|
+
this.projects = new ProjectsResource(this);
|
|
1864
|
+
this.jobs = new JobsResource(this);
|
|
1865
|
+
this.executions = new ExecutionsResource(this);
|
|
1866
|
+
this.nodes = new NodesResource(this);
|
|
1867
|
+
this.developerApps = new DeveloperAppsResource(this);
|
|
1868
|
+
this.oauth = new OAuthResource(this);
|
|
1869
|
+
this.apps = new AppsResource(this);
|
|
1870
|
+
this.characters = new CharactersResource(this);
|
|
1871
|
+
this.locations = new LocationsResource(this);
|
|
1872
|
+
this.objects = new ObjectsResource(this);
|
|
1873
|
+
this.creatures = new CreaturesResource(this);
|
|
1874
|
+
this.pipelines = new PipelinesResource(this);
|
|
1875
|
+
this.reduce = new ReduceResource(this);
|
|
1876
|
+
this.promptHelper = new PromptHelperResource(this);
|
|
1877
|
+
this.voices = new VoicesResource(this);
|
|
1878
|
+
this.credits = new CreditsResource(this);
|
|
1879
|
+
this.uploads = new UploadsResource(this);
|
|
1880
|
+
this.library = new LibraryResource(this);
|
|
1881
|
+
this.presets = new PresetsResource(this);
|
|
1882
|
+
this.pickerCatalogs = new PickerCatalogsResource(this);
|
|
1883
|
+
this.community = new CommunityResource(this);
|
|
1884
|
+
}
|
|
1885
|
+
async request(method, path, options = {}) {
|
|
1886
|
+
const url = this.buildUrl(path, options.query);
|
|
1887
|
+
const token = await this.auth.getToken();
|
|
1888
|
+
const isFormData = typeof FormData !== "undefined" && options.body instanceof FormData;
|
|
1889
|
+
const headers = {
|
|
1890
|
+
...isFormData ? {} : { "Content-Type": "application/json" },
|
|
1891
|
+
...options.headers ?? {}
|
|
1892
|
+
};
|
|
1893
|
+
if (token) headers["Authorization"] = `Bearer ${token}`;
|
|
1894
|
+
const ac = new AbortController();
|
|
1895
|
+
const timeoutId = setTimeout(() => ac.abort(), this.timeoutMs);
|
|
1896
|
+
if (options.signal) {
|
|
1897
|
+
options.signal.addEventListener("abort", () => ac.abort(), { once: true });
|
|
1898
|
+
}
|
|
1899
|
+
try {
|
|
1900
|
+
const res = await this.fetch(url, {
|
|
1901
|
+
method,
|
|
1902
|
+
headers,
|
|
1903
|
+
body: options.body === void 0 ? void 0 : isFormData ? options.body : JSON.stringify(options.body),
|
|
1904
|
+
signal: ac.signal
|
|
1905
|
+
});
|
|
1906
|
+
if (!res.ok) {
|
|
1907
|
+
let errBody = {};
|
|
1908
|
+
try {
|
|
1909
|
+
errBody = await res.json();
|
|
1910
|
+
} catch {
|
|
1911
|
+
}
|
|
1912
|
+
throwFromResponse(res.status, errBody);
|
|
1913
|
+
}
|
|
1914
|
+
if (res.status === 204) return void 0;
|
|
1915
|
+
return await res.json();
|
|
1916
|
+
} finally {
|
|
1917
|
+
clearTimeout(timeoutId);
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
/**
|
|
1921
|
+
* `GET /v1/me` → the authenticated user's identity (see {@link UserIdentity}).
|
|
1922
|
+
* Unwraps the `{ data }` envelope. Throws `UnauthorizedError` (401) when the
|
|
1923
|
+
* token is missing/invalid, and the SDK's other typed errors as usual.
|
|
1924
|
+
*/
|
|
1925
|
+
async me() {
|
|
1926
|
+
const res = await this.request("GET", "/v1/me");
|
|
1927
|
+
return res.data;
|
|
1928
|
+
}
|
|
1929
|
+
buildUrl(path, query) {
|
|
1930
|
+
const base = this.baseUrl || (typeof window !== "undefined" ? window.location.origin : "http://placeholder");
|
|
1931
|
+
const url = new URL(path, base);
|
|
1932
|
+
const fullUrl = this.baseUrl ? url.toString() : url.pathname + url.search;
|
|
1933
|
+
if (query) {
|
|
1934
|
+
const u = new URL(this.baseUrl ? fullUrl : fullUrl, base);
|
|
1935
|
+
for (const [k, v] of Object.entries(query)) {
|
|
1936
|
+
if (v !== void 0) u.searchParams.set(k, String(v));
|
|
1937
|
+
}
|
|
1938
|
+
return this.baseUrl ? u.toString() : u.pathname + u.search;
|
|
1939
|
+
}
|
|
1940
|
+
return fullUrl;
|
|
1941
|
+
}
|
|
1942
|
+
};
|
|
1943
|
+
function createClient(opts) {
|
|
1944
|
+
return new NodaroClient(opts);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// src/auth.ts
|
|
1948
|
+
var StaticTokenAuth = class {
|
|
1949
|
+
constructor(token) {
|
|
1950
|
+
this.token = token;
|
|
1951
|
+
}
|
|
1952
|
+
token;
|
|
1953
|
+
async getToken() {
|
|
1954
|
+
return this.token;
|
|
1955
|
+
}
|
|
1956
|
+
};
|
|
1957
|
+
var CallbackAuth = class {
|
|
1958
|
+
constructor(fn) {
|
|
1959
|
+
this.fn = fn;
|
|
1960
|
+
}
|
|
1961
|
+
fn;
|
|
1962
|
+
async getToken() {
|
|
1963
|
+
return this.fn();
|
|
1964
|
+
}
|
|
1965
|
+
};
|
|
1966
|
+
function supabaseAuth(supabase) {
|
|
1967
|
+
return {
|
|
1968
|
+
async getToken() {
|
|
1969
|
+
const { data } = await supabase.auth.getSession();
|
|
1970
|
+
return data.session?.access_token ?? null;
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
}
|
|
1974
|
+
|
|
1975
|
+
export { AppsResource, CREATURE_ASSET_TYPES, CallbackAuth, CharactersResource, CommunityResource, CreaturesResource, CreditsResource, DeveloperAppsResource, ExecutionsResource, ForbiddenError, InsufficientCreditsError, JobAbortedError, JobFailedError, JobTimeoutError, JobsResource, LibraryResource, LocationsResource, NodaroClient, NodaroError, NodesResource, NotFoundError, OAuthResource, ObjectsResource, PickerCatalogsResource, PipelinesResource, PresetsResource, ProjectsResource, PromptHelperResource, RateLimitedError, ReduceResource, StaticTokenAuth, StorageExceededError, UnauthorizedError, UploadsResource, VoicesResource, WorkflowsResource, buildPersonSeedPrompt, createClient, supabaseAuth, throwFromResponse };
|
|
1976
|
+
//# sourceMappingURL=index.js.map
|
|
1977
|
+
//# sourceMappingURL=index.js.map
|