Skip to content

Commit 4ae9804

Browse files
committed
feat: add runNoThrow() method to expose task ID and detailed information
1 parent d02a48d commit 4ae9804

4 files changed

Lines changed: 183 additions & 4 deletions

File tree

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,22 @@ const url = await wavespeed.upload("/path/to/image.png");
113113
console.log(url);
114114
```
115115

116+
### Getting Task ID and Debug Information
117+
118+
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 throw exceptions:
119+
120+
```javascript
121+
const result = await client.runNoThrow(model, input);
122+
123+
if (result.outputs) {
124+
console.log("Success:", result.outputs);
125+
console.log("Task ID:", result.detail.taskId); // For tracking/debugging
126+
} else {
127+
console.log("Failed:", result.detail.error);
128+
console.log("Task ID:", result.detail.taskId); // Still available on failure
129+
}
130+
```
131+
116132
## Running Tests
117133

118134
```bash

src/api/client.ts

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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(/task_id: ([a-f0-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
*

src/api/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,10 @@
1919
*/
2020

2121
import { Client } from './client';
22-
import type { RunOptions } from './client';
22+
import type { RunOptions, RunDetail, RunNoThrowResult } from './client';
2323

2424
export { Client };
25-
export type { RunOptions };
25+
export type { RunOptions, RunDetail, RunNoThrowResult };
2626

2727
// Default client instance
2828
let _defaultClient: Client | null = null;

src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,10 +22,10 @@ import './config';
2222

2323
// Import API client
2424
import { Client, run, upload } from './api';
25-
import type { RunOptions } from './api/client';
25+
import type { RunOptions, RunDetail, RunNoThrowResult } from './api/client';
2626

2727
export { version, Client, run, upload };
28-
export type { RunOptions };
28+
export type { RunOptions, RunDetail, RunNoThrowResult };
2929

3030
// Default export (Client class)
3131
export default Client;

0 commit comments

Comments
 (0)