@curvet/sdk 0.1.0 → 0.3.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.d.ts CHANGED
@@ -29,7 +29,7 @@ interface FetchResponse {
29
29
  interface FetchInit {
30
30
  method?: string;
31
31
  headers?: Record<string, string>;
32
- body?: string;
32
+ body?: string | FormData;
33
33
  signal?: AbortSignal;
34
34
  }
35
35
  /** Injectable fetch implementation (defaults to global fetch on Node 18+). */
@@ -173,6 +173,17 @@ interface VideoGenerateParams {
173
173
  resolution?: string;
174
174
  [key: string]: unknown;
175
175
  }
176
+ interface AudioGenerateParams {
177
+ model: ModelId;
178
+ prompt: string;
179
+ voice?: string;
180
+ [key: string]: unknown;
181
+ }
182
+ interface ThreeDGenerateParams {
183
+ model: ModelId;
184
+ prompt: string;
185
+ [key: string]: unknown;
186
+ }
176
187
  interface PollOptions {
177
188
  /** Poll interval in ms (default 2500). */
178
189
  pollIntervalMs?: number;
@@ -211,32 +222,46 @@ declare class Job {
211
222
  wait(opts?: PollOptions): Promise<MediaJob>;
212
223
  }
213
224
 
225
+ interface MediaParamsBase {
226
+ model: string;
227
+ prompt: string;
228
+ [key: string]: unknown;
229
+ }
214
230
  /**
215
- * Video generation. Backed by an async job queue: `generate()` submits and
216
- * polls to completion (the common case); `submit()` fires and returns the job
217
- * handle for manual polling.
231
+ * Generic engine for the async media endpoints (video / audio / 3d). They all
232
+ * enqueue the same server-side job queue, so they share one implementation
233
+ * parameterized by `path` and request param type.
218
234
  *
219
- * The same implementation backs audio and 3D (v1.1) via the `path` arg.
235
+ * `generate()` submits and polls to completion; `submit()` fires without polling.
220
236
  */
221
- declare class Video {
222
- private client;
223
- private defaults;
224
- private path;
225
- constructor(client: HttpClient, defaults: JobDefaults, path?: string);
226
- /**
227
- * Submit a job WITHOUT polling. Returns once the server responds — either the
228
- * 200 fast-path (already done) or a 202 with a jobId.
229
- *
230
- * The media POST long-polls server-side and can block well past a normal
231
- * request timeout, so we default its timeout to the poll budget and disable
232
- * auto-retry (a retried POST would enqueue a duplicate, double-charged job).
233
- */
234
- submit(params: VideoGenerateParams, options?: RequestOptions): Promise<MediaJob>;
237
+ declare class MediaResource<P extends MediaParamsBase> {
238
+ protected client: HttpClient;
239
+ protected defaults: JobDefaults;
240
+ protected path: string;
241
+ constructor(client: HttpClient, defaults: JobDefaults, path: string);
235
242
  /**
236
- * Submit and resolve to the finished media. Handles the 200-vs-202 split and
237
- * polls `/jobs/:id` internally. Throws JobFailedError / JobTimeoutError.
243
+ * Submit WITHOUT polling. The media POST long-polls server-side and can block
244
+ * well past a normal request timeout, so we default its timeout to the poll
245
+ * budget and disable auto-retry (a retried POST would enqueue a duplicate job).
238
246
  */
239
- generate(params: VideoGenerateParams, options?: RequestOptions & PollOptions): Promise<MediaJob>;
247
+ submit(params: P, options?: RequestOptions): Promise<MediaJob>;
248
+ /** Submit and resolve to the finished media (auto-polls /jobs/:id). */
249
+ generate(params: P, options?: RequestOptions & PollOptions): Promise<MediaJob>;
250
+ }
251
+
252
+ /** Video generation (async). `curvet.video.generate(...)` auto-polls to completion. */
253
+ declare class Video extends MediaResource<VideoGenerateParams> {
254
+ constructor(client: HttpClient, defaults: JobDefaults);
255
+ }
256
+
257
+ /** Audio generation (async). `curvet.audio.generate(...)` auto-polls to completion. */
258
+ declare class Audio extends MediaResource<AudioGenerateParams> {
259
+ constructor(client: HttpClient, defaults: JobDefaults);
260
+ }
261
+
262
+ /** 3D model generation (async). `curvet.threeD.generate(...)` auto-polls to completion. */
263
+ declare class ThreeD extends MediaResource<ThreeDGenerateParams> {
264
+ constructor(client: HttpClient, defaults: JobDefaults);
240
265
  }
241
266
 
242
267
  interface ModelsListOptions extends RequestOptions {
@@ -285,11 +310,179 @@ declare class Balance {
285
310
  get(options?: RequestOptions): Promise<BalanceInfo>;
286
311
  }
287
312
 
313
+ interface AnalyticsParams extends RequestOptions {
314
+ /** ISO 8601 start date. */
315
+ startDate?: string;
316
+ /** ISO 8601 end date. */
317
+ endDate?: string;
318
+ }
319
+ interface AnalyticsResult {
320
+ totalRequests?: number;
321
+ totalCost?: number;
322
+ requestsByModel?: Record<string, number>;
323
+ requestsByCategory?: Record<string, number>;
324
+ [key: string]: unknown;
325
+ }
326
+ declare class Analytics {
327
+ private client;
328
+ constructor(client: HttpClient);
329
+ /** Usage analytics for the app, optionally bounded by a date range. */
330
+ get(params?: AnalyticsParams): Promise<AnalyticsResult>;
331
+ }
332
+
333
+ interface WorkflowRunParams {
334
+ /** Input values for the workflow. */
335
+ inputs?: Record<string, unknown>;
336
+ /** Optional file inputs, keyed by the workflow's file field name. */
337
+ files?: Record<string, Blob>;
338
+ /** Include the full execution state in the response (default true server-side). */
339
+ includeFullState?: boolean;
340
+ }
341
+ /** Result of a synchronous `run()` call. */
342
+ interface WorkflowRunResult {
343
+ success: boolean;
344
+ [key: string]: unknown;
345
+ }
346
+ type WorkflowRunStatus = "queued" | "running" | "completed" | "failed" | "stopped";
347
+ interface WorkflowRunNode {
348
+ nodeId: string;
349
+ nodeLabel?: string;
350
+ nodeType?: string;
351
+ status?: string;
352
+ executionTime?: number;
353
+ }
354
+ /** Normalized status of an async (pollable) workflow run. */
355
+ interface WorkflowRun {
356
+ runId: string;
357
+ status: WorkflowRunStatus;
358
+ progress?: number;
359
+ totalNodes?: number;
360
+ completedNodeCount?: number;
361
+ /** The node currently executing (null when finished/queued). */
362
+ currentNode?: {
363
+ id: string;
364
+ label?: string;
365
+ type?: string;
366
+ } | null;
367
+ nodesExecuted?: WorkflowRunNode[];
368
+ /** Final outputs (present once completed). */
369
+ result?: unknown;
370
+ error?: string | null;
371
+ startTime?: string;
372
+ endTime?: string;
373
+ /** Raw, unnormalized response body. */
374
+ raw: unknown;
375
+ }
376
+ interface WorkflowSubmitResult {
377
+ runId: string;
378
+ status: WorkflowRunStatus;
379
+ raw: unknown;
380
+ }
381
+ interface WorkflowPollOptions {
382
+ /** Poll interval in ms (default 2500). */
383
+ pollIntervalMs?: number;
384
+ /** Total poll timeout in ms before throwing WorkflowRunTimeoutError (default 300000). */
385
+ pollTimeoutMs?: number;
386
+ signal?: AbortSignal;
387
+ /** Called on each poll tick with the latest run status (current node, progress). */
388
+ onProgress?: (run: WorkflowRun) => void;
389
+ }
390
+ /** Retrieve async workflow-run status. */
391
+ declare class WorkflowRuns {
392
+ private client;
393
+ constructor(client: HttpClient);
394
+ /** Fetch the current status of an async run once (no polling). */
395
+ retrieve(runId: string, options?: RequestOptions): Promise<WorkflowRun>;
396
+ }
397
+ declare class Workflows {
398
+ private client;
399
+ readonly runs: WorkflowRuns;
400
+ constructor(client: HttpClient);
401
+ /**
402
+ * Execute a workflow synchronously (blocks until it finishes). Best for short
403
+ * workflows; for long ones (video/audio/3D nodes) prefer `runAndPoll`.
404
+ * Sends JSON, or multipart/form-data when file inputs are provided.
405
+ */
406
+ run(id: string, params?: WorkflowRunParams, options?: RequestOptions): Promise<WorkflowRunResult>;
407
+ /**
408
+ * Submit a workflow in async (pollable) mode — returns immediately with a
409
+ * runId. Poll `runs.retrieve(runId)` for status, or use `runAndPoll`.
410
+ */
411
+ submit(id: string, params?: WorkflowRunParams, options?: RequestOptions): Promise<WorkflowSubmitResult>;
412
+ /**
413
+ * Submit and poll to completion. Resolves with the completed run (including
414
+ * `result`), reporting progress via `onProgress`. Throws WorkflowRunFailedError
415
+ * on failure, WorkflowRunTimeoutError on timeout.
416
+ */
417
+ runAndPoll(id: string, params?: WorkflowRunParams, opts?: RequestOptions & WorkflowPollOptions): Promise<WorkflowRun>;
418
+ }
419
+
420
+ interface FoodItem {
421
+ [key: string]: unknown;
422
+ }
423
+ /**
424
+ * Indian Food Dataset API. Mounted as a sibling of the playground under
425
+ * `/api/v1/food`, so it uses the v1-root HTTP client. Requires the app to have
426
+ * Food API access enabled.
427
+ */
428
+ declare class Food {
429
+ private client;
430
+ constructor(client: HttpClient);
431
+ /** List dishes (default limit 20). */
432
+ list(opts?: {
433
+ limit?: number;
434
+ } & RequestOptions): Promise<FoodItem[]>;
435
+ /** Full-text search for dishes. */
436
+ search(query: string, opts?: {
437
+ limit?: number;
438
+ } & RequestOptions): Promise<FoodItem[]>;
439
+ /** Natural-language dish recommendations. */
440
+ recommendations(prompt: string, options?: RequestOptions): Promise<FoodItem[]>;
441
+ }
442
+
443
+ interface SttParams {
444
+ /** The audio to transcribe. */
445
+ audio: Blob | Uint8Array | ArrayBuffer;
446
+ /** File name for the upload (default "audio"). */
447
+ filename?: string;
448
+ provider?: "elevenlabs" | "deepinfra" | (string & {});
449
+ /** ASR model id (provider-specific; optional). */
450
+ model?: string;
451
+ prompt?: string;
452
+ /** ISO 639-1 language hint. */
453
+ languageCode?: string;
454
+ allowFallback?: boolean;
455
+ }
456
+ interface SttResult {
457
+ success: boolean;
458
+ text: string;
459
+ languageCode?: string;
460
+ segments?: Array<{
461
+ start: number;
462
+ end: number;
463
+ text: string;
464
+ }>;
465
+ provider?: string;
466
+ creditsCharged?: number;
467
+ creditsRemaining?: number;
468
+ [key: string]: unknown;
469
+ }
470
+ /**
471
+ * Public speech-to-text. Mounted as a sibling of the playground under
472
+ * `/api/v1/voice`, so it uses the v1-root HTTP client. Multipart upload; not
473
+ * auto-retried (it consumes credits).
474
+ */
475
+ declare class Voice {
476
+ private client;
477
+ constructor(client: HttpClient);
478
+ stt(params: SttParams, options?: RequestOptions): Promise<SttResult>;
479
+ }
480
+
288
481
  declare const DEFAULT_BASE_URL = "https://curvet.ai/api/v1/playground";
289
482
  interface CurvetOptions {
290
483
  /** Your app key. Falls back to the CURVET_APP_KEY env var. */
291
484
  appKey?: string;
292
- /** Override the gateway base URL (defaults to production). */
485
+ /** Override the playground base URL (defaults to production). */
293
486
  baseURL?: string;
294
487
  /** Per-request timeout in ms (default 60000). */
295
488
  timeout?: number;
@@ -317,9 +510,15 @@ declare class Curvet {
317
510
  readonly chat: Chat;
318
511
  readonly image: Images;
319
512
  readonly video: Video;
513
+ readonly audio: Audio;
514
+ readonly threeD: ThreeD;
320
515
  readonly jobs: Jobs;
321
516
  readonly models: Models;
322
517
  readonly balance: Balance;
518
+ readonly analytics: Analytics;
519
+ readonly workflows: Workflows;
520
+ readonly food: Food;
521
+ readonly voice: Voice;
323
522
  constructor(options?: CurvetOptions);
324
523
  }
325
524
 
@@ -376,5 +575,15 @@ declare class JobTimeoutError extends CurvetError {
376
575
  readonly jobId: string;
377
576
  constructor(message: string, jobId: string, opts?: CurvetErrorOptions);
378
577
  }
578
+ /** An async workflow run finished with status "failed" (or "stopped"). */
579
+ declare class WorkflowRunFailedError extends CurvetError {
580
+ readonly runId: string;
581
+ constructor(message: string, runId: string, opts?: CurvetErrorOptions);
582
+ }
583
+ /** An async workflow run did not finish within the poll timeout. */
584
+ declare class WorkflowRunTimeoutError extends CurvetError {
585
+ readonly runId: string;
586
+ constructor(message: string, runId: string, opts?: CurvetErrorOptions);
587
+ }
379
588
 
380
- export { APIError, AuthError, BadRequestError, Balance, type BalanceInfo, Chat, type ChatCreateParams, type ChatMessage, type ChatResponse, type ChatRole, ConnectionError, Curvet, CurvetError, type CurvetErrorOptions, type CurvetOptions, DEFAULT_BASE_URL, type FetchLike, type ImageGenerateParams, type ImageResponse, Images, InsufficientBalanceError, Job, type JobDefaults, JobFailedError, type JobStatus, JobTimeoutError, Jobs, type KnownModelId, type MediaJob, type MediaKind, type ModelId, type ModelInfo, type ModelType, Models, type ModelsListOptions, NotFoundError, PermissionError, type PollOptions, RateLimitError, type RateLimits, type RequestOptions, type Usage, Video, type VideoGenerateParams };
589
+ export { APIError, Analytics, type AnalyticsParams, type AnalyticsResult, Audio, type AudioGenerateParams, AuthError, BadRequestError, Balance, type BalanceInfo, Chat, type ChatCreateParams, type ChatMessage, type ChatResponse, type ChatRole, ConnectionError, Curvet, CurvetError, type CurvetErrorOptions, type CurvetOptions, DEFAULT_BASE_URL, type FetchLike, Food, type FoodItem, type ImageGenerateParams, type ImageResponse, Images, InsufficientBalanceError, Job, type JobDefaults, JobFailedError, type JobStatus, JobTimeoutError, Jobs, type KnownModelId, type MediaJob, type MediaKind, type MediaParamsBase, MediaResource, type ModelId, type ModelInfo, type ModelType, Models, type ModelsListOptions, NotFoundError, PermissionError, type PollOptions, RateLimitError, type RateLimits, type RequestOptions, type SttParams, type SttResult, ThreeD, type ThreeDGenerateParams, type Usage, Video, type VideoGenerateParams, Voice, type WorkflowPollOptions, type WorkflowRun, WorkflowRunFailedError, type WorkflowRunNode, type WorkflowRunParams, type WorkflowRunResult, type WorkflowRunStatus, WorkflowRunTimeoutError, WorkflowRuns, type WorkflowSubmitResult, Workflows };
package/dist/index.js CHANGED
@@ -37,6 +37,18 @@ var JobTimeoutError = class extends CurvetError {
37
37
  this.jobId = jobId;
38
38
  }
39
39
  };
40
+ var WorkflowRunFailedError = class extends CurvetError {
41
+ constructor(message, runId, opts = {}) {
42
+ super(message, opts);
43
+ this.runId = runId;
44
+ }
45
+ };
46
+ var WorkflowRunTimeoutError = class extends CurvetError {
47
+ constructor(message, runId, opts = {}) {
48
+ super(message, opts);
49
+ this.runId = runId;
50
+ }
51
+ };
40
52
  function errorFromResponse(status, body, requestId, headers) {
41
53
  const b = body ?? {};
42
54
  const message = typeof b.error === "string" ? b.error : `HTTP ${status}`;
@@ -125,8 +137,12 @@ var HttpClient = class {
125
137
  };
126
138
  let payload;
127
139
  if (body !== void 0) {
128
- headers["content-type"] = "application/json";
129
- payload = JSON.stringify(body);
140
+ if (typeof FormData !== "undefined" && body instanceof FormData) {
141
+ payload = body;
142
+ } else {
143
+ headers["content-type"] = "application/json";
144
+ payload = JSON.stringify(body);
145
+ }
130
146
  }
131
147
  let attempt = 0;
132
148
  for (; ; ) {
@@ -360,20 +376,17 @@ var Job = class {
360
376
  }
361
377
  };
362
378
 
363
- // src/resources/video.ts
364
- var Video = class {
365
- constructor(client, defaults, path = "/video") {
379
+ // src/resources/media.ts
380
+ var MediaResource = class {
381
+ constructor(client, defaults, path) {
366
382
  this.client = client;
367
383
  this.defaults = defaults;
368
384
  this.path = path;
369
385
  }
370
386
  /**
371
- * Submit a job WITHOUT polling. Returns once the server responds either the
372
- * 200 fast-path (already done) or a 202 with a jobId.
373
- *
374
- * The media POST long-polls server-side and can block well past a normal
375
- * request timeout, so we default its timeout to the poll budget and disable
376
- * auto-retry (a retried POST would enqueue a duplicate, double-charged job).
387
+ * Submit WITHOUT polling. The media POST long-polls server-side and can block
388
+ * well past a normal request timeout, so we default its timeout to the poll
389
+ * budget and disable auto-retry (a retried POST would enqueue a duplicate job).
377
390
  */
378
391
  async submit(params, options) {
379
392
  const reqOptions = {
@@ -389,10 +402,7 @@ var Video = class {
389
402
  });
390
403
  return normalizeMediaPost(body);
391
404
  }
392
- /**
393
- * Submit and resolve to the finished media. Handles the 200-vs-202 split and
394
- * polls `/jobs/:id` internally. Throws JobFailedError / JobTimeoutError.
395
- */
405
+ /** Submit and resolve to the finished media (auto-polls /jobs/:id). */
396
406
  async generate(params, options) {
397
407
  const submitted = await this.submit(params, options);
398
408
  if (submitted.status === "completed" || submitted.status === "failed") {
@@ -413,6 +423,27 @@ var Video = class {
413
423
  }
414
424
  };
415
425
 
426
+ // src/resources/video.ts
427
+ var Video = class extends MediaResource {
428
+ constructor(client, defaults) {
429
+ super(client, defaults, "/video");
430
+ }
431
+ };
432
+
433
+ // src/resources/audio.ts
434
+ var Audio = class extends MediaResource {
435
+ constructor(client, defaults) {
436
+ super(client, defaults, "/audio");
437
+ }
438
+ };
439
+
440
+ // src/resources/threeD.ts
441
+ var ThreeD = class extends MediaResource {
442
+ constructor(client, defaults) {
443
+ super(client, defaults, "/3d");
444
+ }
445
+ };
446
+
416
447
  // src/resources/models.ts
417
448
  var Models = class {
418
449
  constructor(client, cacheTtlMs = 6e4) {
@@ -465,6 +496,227 @@ var Balance = class {
465
496
  }
466
497
  };
467
498
 
499
+ // src/resources/analytics.ts
500
+ var Analytics = class {
501
+ constructor(client) {
502
+ this.client = client;
503
+ }
504
+ /** Usage analytics for the app, optionally bounded by a date range. */
505
+ async get(params = {}) {
506
+ const { startDate, endDate, ...options } = params;
507
+ const body = await this.client.request({
508
+ method: "GET",
509
+ path: "/analytics",
510
+ query: { startDate, endDate },
511
+ options
512
+ });
513
+ return body.analytics;
514
+ }
515
+ };
516
+
517
+ // src/resources/workflows.ts
518
+ function normalizeRun(body) {
519
+ const b = body ?? {};
520
+ return {
521
+ runId: b.runId,
522
+ status: b.status,
523
+ progress: b.progress,
524
+ totalNodes: b.totalNodes,
525
+ completedNodeCount: b.completedNodeCount,
526
+ currentNode: b.currentNode ?? null,
527
+ nodesExecuted: b.nodesExecuted,
528
+ result: b.result,
529
+ error: b.error ?? null,
530
+ startTime: b.startTime,
531
+ endTime: b.endTime,
532
+ raw: body
533
+ };
534
+ }
535
+ function buildBody(params, extra = {}) {
536
+ const hasFiles = params.files && Object.keys(params.files).length > 0;
537
+ if (hasFiles) {
538
+ const form = new FormData();
539
+ form.append("inputs", JSON.stringify(params.inputs ?? {}));
540
+ if (params.includeFullState !== void 0) {
541
+ form.append("includeFullState", String(params.includeFullState));
542
+ }
543
+ for (const [k, v] of Object.entries(extra)) form.append(k, String(v));
544
+ for (const [field, file] of Object.entries(params.files)) {
545
+ form.append(field, file);
546
+ }
547
+ return form;
548
+ }
549
+ return {
550
+ inputs: params.inputs ?? {},
551
+ includeFullState: params.includeFullState,
552
+ ...extra
553
+ };
554
+ }
555
+ var WorkflowRuns = class {
556
+ constructor(client) {
557
+ this.client = client;
558
+ }
559
+ /** Fetch the current status of an async run once (no polling). */
560
+ async retrieve(runId, options) {
561
+ const body = await this.client.request({
562
+ method: "GET",
563
+ path: `/workflows/runs/${encodeURIComponent(runId)}`,
564
+ options
565
+ });
566
+ return normalizeRun(body);
567
+ }
568
+ };
569
+ var Workflows = class {
570
+ constructor(client) {
571
+ this.client = client;
572
+ this.runs = new WorkflowRuns(client);
573
+ }
574
+ /**
575
+ * Execute a workflow synchronously (blocks until it finishes). Best for short
576
+ * workflows; for long ones (video/audio/3D nodes) prefer `runAndPoll`.
577
+ * Sends JSON, or multipart/form-data when file inputs are provided.
578
+ */
579
+ async run(id, params = {}, options) {
580
+ const reqOptions = { ...options, maxRetries: options?.maxRetries ?? 0 };
581
+ return this.client.request({
582
+ method: "POST",
583
+ path: `/workflows/${encodeURIComponent(id)}/run`,
584
+ body: buildBody(params),
585
+ options: reqOptions
586
+ });
587
+ }
588
+ /**
589
+ * Submit a workflow in async (pollable) mode — returns immediately with a
590
+ * runId. Poll `runs.retrieve(runId)` for status, or use `runAndPoll`.
591
+ */
592
+ async submit(id, params = {}, options) {
593
+ const reqOptions = { ...options, maxRetries: options?.maxRetries ?? 0 };
594
+ const res = await this.client.request({
595
+ method: "POST",
596
+ path: `/workflows/${encodeURIComponent(id)}/run`,
597
+ body: buildBody(params, { async: true }),
598
+ options: reqOptions
599
+ });
600
+ return { runId: res?.runId, status: res?.status ?? "running", raw: res };
601
+ }
602
+ /**
603
+ * Submit and poll to completion. Resolves with the completed run (including
604
+ * `result`), reporting progress via `onProgress`. Throws WorkflowRunFailedError
605
+ * on failure, WorkflowRunTimeoutError on timeout.
606
+ */
607
+ async runAndPoll(id, params = {}, opts = {}) {
608
+ const submitted = await this.submit(id, params, opts);
609
+ if (!submitted.runId) {
610
+ throw new CurvetError("Workflow submit did not return a runId", {
611
+ raw: submitted.raw
612
+ });
613
+ }
614
+ const intervalMs = opts.pollIntervalMs ?? 2500;
615
+ const timeoutMs = opts.pollTimeoutMs ?? 3e5;
616
+ let run;
617
+ try {
618
+ run = await pollUntil(
619
+ () => this.runs.retrieve(submitted.runId, { signal: opts.signal }),
620
+ {
621
+ intervalMs,
622
+ timeoutMs,
623
+ signal: opts.signal,
624
+ isTerminal: (r) => r.status === "completed" || r.status === "failed" || r.status === "stopped",
625
+ onTick: (r) => opts.onProgress?.(r)
626
+ }
627
+ );
628
+ } catch (e) {
629
+ if (e instanceof PollTimeoutError) {
630
+ throw new WorkflowRunTimeoutError(
631
+ `Workflow run ${submitted.runId} did not finish within ${timeoutMs}ms`,
632
+ submitted.runId
633
+ );
634
+ }
635
+ throw e;
636
+ }
637
+ if (run.status === "failed" || run.status === "stopped") {
638
+ throw new WorkflowRunFailedError(
639
+ run.error || `Workflow run ${submitted.runId} ${run.status}`,
640
+ submitted.runId,
641
+ { raw: run.raw }
642
+ );
643
+ }
644
+ return run;
645
+ }
646
+ };
647
+
648
+ // src/resources/food.ts
649
+ var Food = class {
650
+ constructor(client) {
651
+ this.client = client;
652
+ }
653
+ /** List dishes (default limit 20). */
654
+ async list(opts) {
655
+ const { limit, ...options } = opts ?? {};
656
+ const body = await this.client.request({
657
+ method: "GET",
658
+ path: "/food",
659
+ query: { limit },
660
+ options
661
+ });
662
+ return body.data;
663
+ }
664
+ /** Full-text search for dishes. */
665
+ async search(query, opts) {
666
+ const { limit, ...options } = opts ?? {};
667
+ const body = await this.client.request({
668
+ method: "GET",
669
+ path: "/food/search",
670
+ query: { q: query, limit },
671
+ options
672
+ });
673
+ return body.data;
674
+ }
675
+ /** Natural-language dish recommendations. */
676
+ async recommendations(prompt, options) {
677
+ const body = await this.client.request({
678
+ method: "POST",
679
+ path: "/food/recommendations",
680
+ body: { prompt },
681
+ options
682
+ });
683
+ return body.data;
684
+ }
685
+ };
686
+
687
+ // src/resources/voice.ts
688
+ var Voice = class {
689
+ constructor(client) {
690
+ this.client = client;
691
+ }
692
+ async stt(params, options) {
693
+ const form = new FormData();
694
+ form.append("audio", toBlob(params.audio), params.filename ?? "audio");
695
+ if (params.provider) form.append("provider", params.provider);
696
+ if (params.model) form.append("model", params.model);
697
+ if (params.prompt) form.append("prompt", params.prompt);
698
+ if (params.languageCode) form.append("languageCode", params.languageCode);
699
+ if (params.allowFallback !== void 0) {
700
+ form.append("allowFallback", String(params.allowFallback));
701
+ }
702
+ const reqOptions = {
703
+ ...options,
704
+ timeout: options?.timeout ?? 12e4,
705
+ maxRetries: options?.maxRetries ?? 0
706
+ };
707
+ return this.client.request({
708
+ method: "POST",
709
+ path: "/voice/stt/public",
710
+ body: form,
711
+ options: reqOptions
712
+ });
713
+ }
714
+ };
715
+ function toBlob(audio) {
716
+ if (typeof Blob !== "undefined" && audio instanceof Blob) return audio;
717
+ return new Blob([audio]);
718
+ }
719
+
468
720
  // src/client.ts
469
721
  var DEFAULT_BASE_URL = "https://curvet.ai/api/v1/playground";
470
722
  var Curvet = class {
@@ -481,13 +733,16 @@ var Curvet = class {
481
733
  "No fetch implementation available. Use Node 18+ or pass { fetch }."
482
734
  );
483
735
  }
484
- const client = new HttpClient({
736
+ const playgroundBase = options.baseURL ?? DEFAULT_BASE_URL;
737
+ const v1Base = playgroundBase.replace(/\/playground\/?$/, "");
738
+ const shared = {
485
739
  appKey,
486
- baseURL: options.baseURL ?? DEFAULT_BASE_URL,
487
740
  timeout: options.timeout ?? 6e4,
488
741
  maxRetries: options.maxRetries ?? 2,
489
742
  fetch: fetchImpl
490
- });
743
+ };
744
+ const client = new HttpClient({ ...shared, baseURL: playgroundBase });
745
+ const v1Client = new HttpClient({ ...shared, baseURL: v1Base });
491
746
  const jobDefaults = {
492
747
  pollIntervalMs: options.defaultPollIntervalMs ?? 2500,
493
748
  pollTimeoutMs: options.defaultPollTimeoutMs ?? 18e4
@@ -496,8 +751,14 @@ var Curvet = class {
496
751
  this.image = new Images(client);
497
752
  this.jobs = new Jobs(client, jobDefaults);
498
753
  this.video = new Video(client, jobDefaults);
754
+ this.audio = new Audio(client, jobDefaults);
755
+ this.threeD = new ThreeD(client, jobDefaults);
499
756
  this.models = new Models(client);
500
757
  this.balance = new Balance(client);
758
+ this.analytics = new Analytics(client);
759
+ this.workflows = new Workflows(client);
760
+ this.food = new Food(v1Client);
761
+ this.voice = new Voice(v1Client);
501
762
  }
502
763
  };
503
764
  function envKey() {
@@ -509,6 +770,8 @@ function defaultFetch() {
509
770
  }
510
771
  export {
511
772
  APIError,
773
+ Analytics,
774
+ Audio,
512
775
  AuthError,
513
776
  BadRequestError,
514
777
  Balance,
@@ -517,16 +780,24 @@ export {
517
780
  Curvet,
518
781
  CurvetError,
519
782
  DEFAULT_BASE_URL,
783
+ Food,
520
784
  Images,
521
785
  InsufficientBalanceError,
522
786
  Job,
523
787
  JobFailedError,
524
788
  JobTimeoutError,
525
789
  Jobs,
790
+ MediaResource,
526
791
  Models,
527
792
  NotFoundError,
528
793
  PermissionError,
529
794
  RateLimitError,
530
- Video
795
+ ThreeD,
796
+ Video,
797
+ Voice,
798
+ WorkflowRunFailedError,
799
+ WorkflowRunTimeoutError,
800
+ WorkflowRuns,
801
+ Workflows
531
802
  };
532
803
  //# sourceMappingURL=index.js.map