@@ -503,6 +503,191 @@ func (c *Client) Run(model string, input map[string]any, opts ...RunOption) (map
503503 return nil , fmt .Errorf ("all %d attempts failed" , taskRetries + 1 )
504504}
505505
506+ // RunDetail contains detailed information about a task execution.
507+ type RunDetail struct {
508+ TaskID string `json:"taskId"`
509+ Status string `json:"status"`
510+ Model string `json:"model"`
511+ Error string `json:"error,omitempty"`
512+ CreatedAt string `json:"createdAt,omitempty"`
513+ }
514+
515+ // RunNoThrowResult is the result of RunNoThrow method.
516+ type RunNoThrowResult struct {
517+ Outputs []any `json:"outputs"`
518+ Detail RunDetail `json:"detail"`
519+ }
520+
521+ // RunNoThrow executes a model and waits for the output (no-throw version).
522+ //
523+ // This method is similar to Run() but does not return errors for task failures.
524+ // Instead, it returns a result object with outputs (nil on failure) and detail information.
525+ // The detail object always contains the taskId, which is useful for debugging and tracking.
526+ //
527+ // Example:
528+ //
529+ // result := client.RunNoThrow("wavespeed-ai/z-image/turbo", map[string]any{"prompt": "Cat"})
530+ //
531+ // if result.Outputs != nil {
532+ // fmt.Println("Success:", result.Outputs)
533+ // fmt.Println("Task ID:", result.Detail.TaskID)
534+ // } else {
535+ // fmt.Println("Failed:", result.Detail.Error)
536+ // fmt.Println("Task ID:", result.Detail.TaskID)
537+ // }
538+ func (c * Client ) RunNoThrow (model string , input map [string ]any , opts ... RunOption ) * RunNoThrowResult {
539+ // Apply default options
540+ options := & RunOptions {
541+ Timeout : 36000.0 ,
542+ PollInterval : 1.0 ,
543+ EnableSyncMode : false ,
544+ MaxRetries : c .maxRetries ,
545+ }
546+
547+ // Apply user-provided options
548+ for _ , opt := range opts {
549+ opt (options )
550+ }
551+
552+ timeout := options .Timeout
553+ pollInterval := options .PollInterval
554+ enableSyncMode := options .EnableSyncMode
555+ taskRetries := options .MaxRetries
556+
557+ for attempt := 0 ; attempt <= taskRetries ; attempt ++ {
558+ requestID , syncResult , err := c .submit (model , input , enableSyncMode , timeout )
559+ if err == nil {
560+ if enableSyncMode {
561+ // In sync mode, extract outputs from the result
562+ data , ok := syncResult ["data" ].(map [string ]any )
563+ if ! ok {
564+ return & RunNoThrowResult {
565+ Outputs : nil ,
566+ Detail : RunDetail {
567+ TaskID : "unknown" ,
568+ Status : "failed" ,
569+ Model : model ,
570+ Error : "Invalid response format" ,
571+ },
572+ }
573+ }
574+
575+ status , _ := data ["status" ].(string )
576+ taskID , _ := data ["id" ].(string )
577+ if taskID == "" {
578+ taskID = "unknown"
579+ }
580+
581+ if status != "completed" {
582+ errorMsg := "Unknown error"
583+ if e , ok := data ["error" ].(string ); ok && e != "" {
584+ errorMsg = e
585+ }
586+ createdAt , _ := data ["created_at" ].(string )
587+ return & RunNoThrowResult {
588+ Outputs : nil ,
589+ Detail : RunDetail {
590+ TaskID : taskID ,
591+ Status : "failed" ,
592+ Model : model ,
593+ Error : errorMsg ,
594+ CreatedAt : createdAt ,
595+ },
596+ }
597+ }
598+
599+ outputs , ok := data ["outputs" ].([]any )
600+ if ! ok {
601+ outputs = []any {}
602+ }
603+ createdAt , _ := data ["created_at" ].(string )
604+ return & RunNoThrowResult {
605+ Outputs : outputs ,
606+ Detail : RunDetail {
607+ TaskID : taskID ,
608+ Status : "completed" ,
609+ Model : model ,
610+ CreatedAt : createdAt ,
611+ },
612+ }
613+ }
614+
615+ // Async mode
616+ result , err := c .wait (requestID , timeout , pollInterval )
617+ if err == nil {
618+ outputs , ok := result ["outputs" ].([]any )
619+ if ! ok {
620+ outputs = []any {}
621+ }
622+ return & RunNoThrowResult {
623+ Outputs : outputs ,
624+ Detail : RunDetail {
625+ TaskID : requestID ,
626+ Status : "completed" ,
627+ Model : model ,
628+ },
629+ }
630+ }
631+
632+ // Wait failed, but we have taskID
633+ return & RunNoThrowResult {
634+ Outputs : nil ,
635+ Detail : RunDetail {
636+ TaskID : requestID ,
637+ Status : "failed" ,
638+ Model : model ,
639+ Error : err .Error (),
640+ },
641+ }
642+ }
643+
644+ // Submit failed
645+ isRetryable := c .isRetryableError (err )
646+
647+ if ! isRetryable || attempt >= taskRetries {
648+ // Try to extract taskID from error message
649+ taskID := "unknown"
650+ errStr := err .Error ()
651+ if idx := strings .Index (errStr , "task_id: " ); idx != - 1 {
652+ start := idx + 9
653+ end := start
654+ for end < len (errStr ) && (errStr [end ] != ')' && errStr [end ] != ' ' && errStr [end ] != '\n' ) {
655+ end ++
656+ }
657+ if end > start {
658+ taskID = errStr [start :end ]
659+ }
660+ }
661+
662+ return & RunNoThrowResult {
663+ Outputs : nil ,
664+ Detail : RunDetail {
665+ TaskID : taskID ,
666+ Status : "failed" ,
667+ Model : model ,
668+ Error : err .Error (),
669+ },
670+ }
671+ }
672+
673+ delay := c .retryInterval * float64 (attempt + 1 )
674+ fmt .Printf ("Task attempt %d/%d failed: %v\n " , attempt + 1 , taskRetries + 1 , err )
675+ fmt .Printf ("Retrying in %.1f seconds...\n " , delay )
676+ time .Sleep (time .Duration (delay * float64 (time .Second )))
677+ }
678+
679+ // Should not reach here
680+ return & RunNoThrowResult {
681+ Outputs : nil ,
682+ Detail : RunDetail {
683+ TaskID : "unknown" ,
684+ Status : "failed" ,
685+ Model : model ,
686+ Error : fmt .Sprintf ("All %d attempts failed" , taskRetries + 1 ),
687+ },
688+ }
689+ }
690+
506691// Upload uploads a file to WaveSpeed.
507692func (c * Client ) Upload (file string , opts ... UploadOption ) (string , error ) {
508693 if c .apiKey == "" {
0 commit comments