Skip to content

Commit b57170d

Browse files
committed
feat: add RunNoThrow() method to expose task ID and detailed information
1 parent 8ce5dc4 commit b57170d

2 files changed

Lines changed: 201 additions & 0 deletions

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,22 @@ fmt.Println(url)
128128
url, err := wavespeed.Upload("/path/to/image.png", wavespeed.WithUploadTimeout(30))
129129
```
130130

131+
### Getting Task ID and Debug Information
132+
133+
If you need access to the task ID for logging, tracking, or debugging, use `RunNoThrow()` instead of `Run()`. This method returns detailed information and does not return errors:
134+
135+
```go
136+
result := client.RunNoThrow(model, input)
137+
138+
if result.Outputs != nil {
139+
fmt.Println("Success:", result.Outputs)
140+
fmt.Println("Task ID:", result.Detail.TaskID) // For tracking/debugging
141+
} else {
142+
fmt.Println("Failed:", result.Detail.Error)
143+
fmt.Println("Task ID:", result.Detail.TaskID) // Still available on failure
144+
}
145+
```
146+
131147
## Running Tests
132148

133149
```bash

api/client.go

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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.
507692
func (c *Client) Upload(file string, opts ...UploadOption) (string, error) {
508693
if c.apiKey == "" {

0 commit comments

Comments
 (0)