@@ -14,6 +14,25 @@ export interface RunOptions {
1414 maxRetries ?: number ; // Maximum task-level retries (overrides client setting)
1515}
1616
17+ /**
18+ * Detail information for runNoThrow result.
19+ */
20+ export interface RunDetail {
21+ taskId : string ; // Task ID for tracking and debugging
22+ status : 'completed' | 'failed' ; // Task status
23+ model : string ; // Model identifier
24+ error ?: string ; // Error message if failed
25+ createdAt ?: string ; // Task creation timestamp
26+ }
27+
28+ /**
29+ * Result interface for runNoThrow method.
30+ */
31+ export interface RunNoThrowResult {
32+ outputs : any [ ] | null ; // Model outputs (null if failed)
33+ detail : RunDetail ; // Detailed information including taskId
34+ }
35+
1736/**
1837 * Upload file response interface
1938 */
@@ -431,6 +450,150 @@ export class Client {
431450 throw new Error ( `All ${ taskRetries + 1 } attempts failed` ) ;
432451 }
433452
453+ /**
454+ * Run a model and wait for the output (no-throw version).
455+ *
456+ * This method is similar to run() but does not throw exceptions.
457+ * Instead, it returns a result object with outputs (null on failure) and detail information.
458+ * The detail object always contains the taskId, which is useful for debugging and tracking.
459+ *
460+ * Args:
461+ * model: Model identifier (e.g., "wavespeed-ai/flux-dev").
462+ * input: Input parameters for the model.
463+ * options.timeout: Maximum time to wait for completion (undefined = no timeout).
464+ * options.pollInterval: Interval between status checks in seconds.
465+ * options.enableSyncMode: If true, use synchronous mode (single request).
466+ * options.maxRetries: Maximum task-level retries (overrides client setting).
467+ *
468+ * Returns:
469+ * Object containing:
470+ * - outputs: Array of model outputs (null if failed)
471+ * - detail: Object with taskId, status, error (if any), and other metadata
472+ *
473+ * Example:
474+ * const result = await client.runNoThrow("wavespeed-ai/z-image/turbo", { prompt: "Cat" });
475+ *
476+ * if (result.outputs) {
477+ * console.log("Success:", result.outputs);
478+ * console.log("Task ID:", result.detail.taskId);
479+ * } else {
480+ * console.log("Failed:", result.detail.error);
481+ * console.log("Task ID:", result.detail.taskId);
482+ * }
483+ */
484+ async runNoThrow (
485+ model : string ,
486+ input ?: Record < string , any > ,
487+ options ?: RunOptions
488+ ) : Promise < RunNoThrowResult > {
489+ const taskRetries = options ?. maxRetries ?? this . maxRetries ;
490+ const timeout = options ?. timeout ?? this . timeout ;
491+ const pollInterval = options ?. pollInterval ?? 1.0 ;
492+ const enableSyncMode = options ?. enableSyncMode ?? false ;
493+
494+ for ( let attempt = 0 ; attempt <= taskRetries ; attempt ++ ) {
495+ try {
496+ const [ requestId , syncResult ] = await this . _submit (
497+ model ,
498+ input ,
499+ enableSyncMode ,
500+ timeout
501+ ) ;
502+
503+ if ( enableSyncMode ) {
504+ // In sync mode, extract outputs from the result
505+ const data = syncResult ?. data || { } ;
506+ const status = data . status ;
507+ const taskId = data . id || 'unknown' ;
508+
509+ if ( status !== 'completed' ) {
510+ const error = data . error || 'Unknown error' ;
511+ return {
512+ outputs : null ,
513+ detail : {
514+ taskId,
515+ status : 'failed' ,
516+ model,
517+ error,
518+ createdAt : data . created_at
519+ }
520+ } ;
521+ }
522+
523+ return {
524+ outputs : data . outputs || [ ] ,
525+ detail : {
526+ taskId,
527+ status : 'completed' ,
528+ model,
529+ createdAt : data . created_at
530+ }
531+ } ;
532+ }
533+
534+ if ( requestId ) {
535+ const result = await this . _wait ( requestId , timeout , pollInterval ) ;
536+ return {
537+ outputs : result . outputs ,
538+ detail : {
539+ taskId : requestId ,
540+ status : 'completed' ,
541+ model
542+ }
543+ } ;
544+ }
545+
546+ // Should not reach here
547+ return {
548+ outputs : null ,
549+ detail : {
550+ taskId : 'unknown' ,
551+ status : 'failed' ,
552+ model,
553+ error : 'Invalid response from _submit'
554+ }
555+ } ;
556+
557+ } catch ( error : any ) {
558+ const isRetryable = this . _isRetryableError ( error ) ;
559+
560+ // If not retryable or last attempt, return error result
561+ if ( ! isRetryable || attempt >= taskRetries ) {
562+ // Try to extract taskId from error message
563+ const taskIdMatch = error . message ?. match ( / t a s k _ i d : ( [ a - f 0 - 9 - ] + ) / ) ;
564+ const taskId = taskIdMatch ? taskIdMatch [ 1 ] : 'unknown' ;
565+
566+ return {
567+ outputs : null ,
568+ detail : {
569+ taskId,
570+ status : 'failed' ,
571+ model,
572+ error : error . message || String ( error )
573+ }
574+ } ;
575+ }
576+
577+ // Retry
578+ console . log ( `Task attempt ${ attempt + 1 } /${ taskRetries + 1 } failed: ${ error } ` ) ;
579+ const delay = this . retryInterval * ( attempt + 1 ) * 1000 ;
580+ console . log ( `Retrying in ${ delay } ms...` ) ;
581+ await new Promise ( ( resolve ) => setTimeout ( resolve , delay ) ) ;
582+ }
583+ }
584+
585+ // Should not reach here, but just in case
586+ return {
587+ outputs : null ,
588+ detail : {
589+ taskId : 'unknown' ,
590+ status : 'failed' ,
591+ model,
592+ error : `All ${ taskRetries + 1 } attempts failed`
593+ }
594+ } ;
595+ }
596+
434597 /**
435598 * Upload a file to WaveSpeed.
436599 *
0 commit comments