@@ -10,7 +10,7 @@ import { api as apiConfig } from '../config';
1010export interface RunOptions {
1111 timeout ?: number ; // Maximum time to wait for completion
1212 pollInterval ?: number ; // Interval between status checks in seconds
13- enableSyncMode ?: boolean ; // If true, use synchronous mode (single request)
13+ enableSyncMode ?: boolean ; // If true, attempt synchronous mode in one request
1414 maxRetries ?: number ; // Maximum task-level retries (overrides client setting)
1515}
1616
@@ -38,6 +38,24 @@ export class WavespeedTimeoutException extends WavespeedException {
3838 }
3939}
4040
41+ /**
42+ * Sync-mode wait timed out, but the task is still processing asynchronously
43+ */
44+ export class WavespeedSyncTimeoutException extends WavespeedException {
45+ constructor (
46+ taskId : string ,
47+ model : string ,
48+ errorMessage : string ,
49+ public readonly resultUrl ?: string
50+ ) {
51+ const suffix = resultUrl && ! errorMessage . includes ( resultUrl )
52+ ? ` Query the result later at: ${ resultUrl } `
53+ : '' ;
54+ super ( `Sync mode timed out (task_id: ${ taskId } ): ${ errorMessage } ${ suffix } ` , taskId , model ) ;
55+ this . name = 'WavespeedSyncTimeoutException' ;
56+ }
57+ }
58+
4159/**
4260 * Connection exception
4361 */
@@ -73,10 +91,11 @@ export class WavespeedUnknownException extends WavespeedException {
7391 */
7492export interface RunDetail {
7593 taskId : string ; // Task ID for tracking and debugging
76- status : 'completed' | 'failed' ; // Task status
94+ status : 'completed' | 'failed' | 'processing' ; // Task status
7795 model : string ; // Model identifier
7896 error ?: WavespeedException ; // Exception instance if failed
7997 createdAt ?: string ; // Task creation timestamp
98+ resultUrl ?: string ; // URL for querying the task result later
8099}
81100
82101/**
@@ -108,7 +127,7 @@ interface UploadFileResp {
108127 * const client = new Client("your-api-key");
109128 * const output = await client.run("wavespeed-ai/z-image/turbo", { prompt: "Cat" });
110129 *
111- * // With sync mode (single request, waits for result)
130+ * // With sync mode (best-effort single request, waits for result)
112131 * const output2 = await client.run("wavespeed-ai/z-image/turbo", { prompt: "Cat" }, { enableSyncMode: true });
113132 *
114133 * // With retry
@@ -413,6 +432,9 @@ export class Client {
413432
414433 // Always retry timeout and connection errors
415434 const errorStr = error . toString ( ) . toLowerCase ( ) ;
435+ if ( errorStr . includes ( 'sync mode timed out' ) ) {
436+ return false ;
437+ }
416438 if ( errorStr . includes ( 'timeout' ) || errorStr . includes ( 'connection' ) ) {
417439 return true ;
418440 }
@@ -425,6 +447,35 @@ export class Client {
425447 return false ;
426448 }
427449
450+ private _resultUrlFromData ( data : Record < string , any > ) : string | undefined {
451+ const urls = data . urls ;
452+ return urls && typeof urls === 'object' && typeof urls . get === 'string'
453+ ? urls . get
454+ : undefined ;
455+ }
456+
457+ private _isSyncTimeoutData ( data : Record < string , any > ) : boolean {
458+ const error = data . error || '' ;
459+ return data . code === 5004 ||
460+ ( data . status === 'processing' && typeof error === 'string' && error . includes ( 'Sync mode timed out' ) ) ;
461+ }
462+
463+ private _syncModeError ( data : Record < string , any > , model : string ) : Error {
464+ const error = data . error || 'Unknown error' ;
465+ const taskId = data . id || 'unknown' ;
466+
467+ if ( this . _isSyncTimeoutData ( data ) ) {
468+ return new WavespeedSyncTimeoutException (
469+ taskId ,
470+ model ,
471+ error ,
472+ this . _resultUrlFromData ( data )
473+ ) ;
474+ }
475+
476+ return new Error ( `Prediction failed (task_id: ${ taskId } ): ${ error } ` ) ;
477+ }
478+
428479 /**
429480 * Run a model and wait for the output.
430481 *
@@ -433,7 +484,7 @@ export class Client {
433484 * input: Input parameters for the model.
434485 * options.timeout: Maximum time to wait for completion (undefined = no timeout).
435486 * options.pollInterval: Interval between status checks in seconds.
436- * options.enableSyncMode: If true, use synchronous mode (single request).
487+ * options.enableSyncMode: If true, use synchronous mode (best-effort single request).
437488 * options.maxRetries: Maximum task-level retries (overrides client setting).
438489 *
439490 * Returns:
@@ -469,9 +520,7 @@ export class Client {
469520 const data = syncResult ?. data || { } ;
470521 const status = data . status ;
471522 if ( status !== 'completed' ) {
472- const error = data . error || 'Unknown error' ;
473- const requestId = data . id || 'unknown' ;
474- throw new Error ( `Prediction failed (task_id: ${ requestId } ): ${ error } ` ) ;
523+ throw this . _syncModeError ( data , model ) ;
475524 }
476525 return { outputs : data . outputs || [ ] } ;
477526 }
@@ -516,7 +565,7 @@ export class Client {
516565 * input: Input parameters for the model.
517566 * options.timeout: Maximum time to wait for completion (undefined = no timeout).
518567 * options.pollInterval: Interval between status checks in seconds.
519- * options.enableSyncMode: If true, use synchronous mode (single request).
568+ * options.enableSyncMode: If true, use synchronous mode (best-effort single request).
520569 * options.maxRetries: Maximum task-level retries (overrides client setting).
521570 *
522571 * Returns:
@@ -562,14 +611,19 @@ export class Client {
562611
563612 if ( status !== 'completed' ) {
564613 const errorMsg = data . error || 'Unknown error' ;
614+ const resultUrl = this . _resultUrlFromData ( data ) ;
615+ const isSyncTimeout = this . _isSyncTimeoutData ( data ) ;
565616 return {
566617 outputs : null ,
567618 detail : {
568619 taskId,
569- status : 'failed' ,
620+ status : isSyncTimeout ? 'processing' : 'failed' ,
570621 model,
571- error : new WavespeedPredictionException ( taskId , model , errorMsg ) ,
572- createdAt : data . created_at
622+ error : isSyncTimeout
623+ ? new WavespeedSyncTimeoutException ( taskId , model , errorMsg , resultUrl )
624+ : new WavespeedPredictionException ( taskId , model , errorMsg ) ,
625+ createdAt : data . created_at ,
626+ resultUrl
573627 }
574628 } ;
575629 }
@@ -614,19 +668,28 @@ export class Client {
614668 // If not retryable or last attempt, return error result
615669 if ( ! isRetryable || attempt >= taskRetries ) {
616670 // Try to extract taskId from error message
617- const taskIdMatch = error . message ?. match ( / t a s k _ i d : ( [ a - f 0 - 9 - ] + ) / ) ;
671+ const taskIdMatch = error . message ?. match ( / t a s k _ i d : \s * ( [ ^ ) ] + ) / ) ;
618672 const taskId = taskIdMatch ? taskIdMatch [ 1 ] : 'unknown' ;
673+ const resultUrlMatch = error . message ?. match ( / Q u e r y t h e r e s u l t l a t e r a t : \s * ( \S + ) / ) ;
674+ const resultUrl = resultUrlMatch ? resultUrlMatch [ 1 ] : undefined ;
619675
620676 // Determine exception type based on error
621677 let exception : WavespeedException ;
622678 const errorStr = error . toString ( ) . toLowerCase ( ) ;
623679
624- if ( errorStr . includes ( 'timeout' ) || errorStr . includes ( 'timed out' ) ) {
680+ if ( errorStr . includes ( 'sync mode timed out' ) ) {
681+ exception = new WavespeedSyncTimeoutException (
682+ taskId ,
683+ model ,
684+ error . message ?. replace ( / S y n c m o d e t i m e d o u t \( t a s k _ i d : [ ^ ) ] + \) : \s * / , '' ) || String ( error ) ,
685+ resultUrl
686+ ) ;
687+ } else if ( errorStr . includes ( 'timeout' ) || errorStr . includes ( 'timed out' ) ) {
625688 exception = new WavespeedTimeoutException ( taskId , model , timeout || 0 ) ;
626689 } else if ( errorStr . includes ( 'connection' ) || errorStr . includes ( 'fetch' ) || error . name === 'AbortError' || error . name === 'TypeError' ) {
627690 exception = new WavespeedConnectionException ( taskId , model , error . message || String ( error ) ) ;
628691 } else if ( errorStr . includes ( 'prediction failed' ) ) {
629- const errorMsg = error . message ?. replace ( / P r e d i c t i o n f a i l e d \( t a s k _ i d : [ a - f 0 - 9 - ] + \) : / , '' ) || 'Unknown error' ;
692+ const errorMsg = error . message ?. replace ( / P r e d i c t i o n f a i l e d \( t a s k _ i d : [ ^ ) ] + \) : / , '' ) || 'Unknown error' ;
630693 exception = new WavespeedPredictionException ( taskId , model , errorMsg ) ;
631694 } else {
632695 exception = new WavespeedUnknownException ( taskId , model , error ) ;
@@ -636,9 +699,10 @@ export class Client {
636699 outputs : null ,
637700 detail : {
638701 taskId,
639- status : 'failed' ,
702+ status : errorStr . includes ( 'sync mode timed out' ) ? 'processing' : 'failed' ,
640703 model,
641- error : exception
704+ error : exception ,
705+ resultUrl
642706 }
643707 } ;
644708 }
0 commit comments