@acedatacloud/sdk 2026.718.1 → 2026.727.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.mts +22 -5
- package/dist/index.d.ts +22 -5
- package/dist/index.js +1017 -35
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +1017 -35
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/client.ts +3 -0
- package/src/resources/providers/digitalhuman.ts +123 -0
- package/src/resources/providers/dreamina.ts +68 -0
- package/src/resources/providers/fish.ts +146 -0
- package/src/resources/providers/flux.ts +71 -0
- package/src/resources/providers/hailuo.ts +65 -0
- package/src/resources/providers/happyhorse.ts +89 -0
- package/src/resources/providers/index.ts +53 -0
- package/src/resources/providers/localization.ts +45 -0
- package/src/resources/providers/luma.ts +83 -0
- package/src/resources/providers/maestro.ts +84 -0
- package/src/resources/providers/nano-banana.ts +74 -0
- package/src/resources/providers/producer.ts +182 -0
- package/src/resources/providers/seedance.ts +89 -0
- package/src/resources/providers/seedream.ts +95 -0
- package/src/resources/providers/suno.ts +437 -0
- package/src/resources/providers/wan.ts +92 -0
- package/src/runtime/payment.ts +5 -3
- package/src/runtime/tasks.ts +165 -11
- package/src/runtime/transport.ts +20 -12
package/src/runtime/tasks.ts
CHANGED
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/**
|
|
2
|
+
* Task polling for long-running operations.
|
|
3
|
+
*
|
|
4
|
+
* A generation call always returns a handle, never sometimes a handle and
|
|
5
|
+
* sometimes a plain object — the caller decides whether to wait. When the server
|
|
6
|
+
* answers synchronously (some endpoints do for fast or cached results) the
|
|
7
|
+
* handle is born already complete, so `wait()` returns immediately rather than
|
|
8
|
+
* polling for something that has already arrived.
|
|
9
|
+
*
|
|
10
|
+
* Kept behaviourally identical to the Python and Go implementations; a
|
|
11
|
+
* divergence here is a cross-language bug that only shows up in production.
|
|
12
|
+
*/
|
|
2
13
|
|
|
3
14
|
import { Transport } from './transport';
|
|
4
15
|
|
|
@@ -7,17 +18,135 @@ export interface TaskHandleOptions {
|
|
|
7
18
|
maxWait?: number;
|
|
8
19
|
}
|
|
9
20
|
|
|
10
|
-
|
|
21
|
+
const DONE = new Set(['succeed', 'succeeded', 'success', 'completed', 'complete', 'finished']);
|
|
22
|
+
const FAILED = new Set(['failed', 'failure', 'error', 'cancelled', 'canceled', 'rejected']);
|
|
23
|
+
|
|
24
|
+
function statusWords(node: unknown, depth = 0): string[] {
|
|
25
|
+
if (depth > 6) return [];
|
|
26
|
+
const out: string[] = [];
|
|
27
|
+
if (Array.isArray(node)) {
|
|
28
|
+
for (const item of node) out.push(...statusWords(item, depth + 1));
|
|
29
|
+
} else if (node && typeof node === 'object') {
|
|
30
|
+
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
|
|
31
|
+
if ((key === 'state' || key === 'status') && typeof value === 'string') {
|
|
32
|
+
out.push(value.toLowerCase());
|
|
33
|
+
} else {
|
|
34
|
+
out.push(...statusWords(value, depth + 1));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function collectUrls(node: unknown, out: string[], depth = 0): void {
|
|
42
|
+
if (depth > 6) return;
|
|
43
|
+
if (Array.isArray(node)) {
|
|
44
|
+
for (const item of node) collectUrls(item, out, depth + 1);
|
|
45
|
+
} else if (node && typeof node === 'object') {
|
|
46
|
+
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
|
|
47
|
+
const looksLikeUrl = key.endsWith('_url') || key === 'url' || key.endsWith('_urls');
|
|
48
|
+
if (typeof value === 'string' && value.startsWith('http') && looksLikeUrl) {
|
|
49
|
+
out.push(value);
|
|
50
|
+
} else {
|
|
51
|
+
collectUrls(value, out, depth + 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Every artifact URL in a task response, outermost first.
|
|
59
|
+
*
|
|
60
|
+
* Where the artifact lives is not derivable from the OpenAPI spec — `response`
|
|
61
|
+
* is typed as a bare object and the key differs per service (`video_url`,
|
|
62
|
+
* `image_url`, `data[].image_url`). Rather than keep a per-service table that
|
|
63
|
+
* goes stale, collect anything URL-shaped.
|
|
64
|
+
*/
|
|
65
|
+
export function artifactUrls(state: Record<string, unknown> | null): string[] {
|
|
66
|
+
if (!state) return [];
|
|
67
|
+
const found: string[] = [];
|
|
68
|
+
collectUrls(state.response ?? state, found);
|
|
69
|
+
return [...new Set(found)];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** Percent complete when the service reports it, else null. */
|
|
73
|
+
export function taskProgress(state: Record<string, unknown> | null): number | null {
|
|
74
|
+
if (!state) return null;
|
|
75
|
+
for (const value of findKeys(state.response ?? state, ['progress', 'percent', 'percentage'])) {
|
|
76
|
+
if (typeof value === 'boolean') continue;
|
|
77
|
+
if (typeof value === 'number') {
|
|
78
|
+
const pct = value > 0 && value <= 1 ? Math.round(value * 100) : Math.round(value);
|
|
79
|
+
return Math.max(0, Math.min(100, pct));
|
|
80
|
+
}
|
|
81
|
+
if (typeof value === 'string') {
|
|
82
|
+
const parsed = Number.parseFloat(value.trim().replace(/%$/, ''));
|
|
83
|
+
if (!Number.isNaN(parsed)) return Math.max(0, Math.min(100, Math.round(parsed)));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function* findKeys(node: unknown, names: string[], depth = 0): Generator<unknown> {
|
|
90
|
+
if (depth > 6) return;
|
|
91
|
+
if (Array.isArray(node)) {
|
|
92
|
+
for (const item of node) yield* findKeys(item, names, depth + 1);
|
|
93
|
+
} else if (node && typeof node === 'object') {
|
|
94
|
+
for (const [key, value] of Object.entries(node as Record<string, unknown>)) {
|
|
95
|
+
if (names.includes(key)) yield value;
|
|
96
|
+
else yield* findKeys(value, names, depth + 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** The upstream's own words for why a task failed. */
|
|
102
|
+
export function failureReason(state: Record<string, unknown> | null): string {
|
|
103
|
+
if (!state) return 'Task failed.';
|
|
11
104
|
const response = (state.response ?? state) as Record<string, unknown>;
|
|
12
|
-
|
|
13
|
-
if (
|
|
105
|
+
const error = response.error;
|
|
106
|
+
if (error && typeof error === 'object') {
|
|
107
|
+
const message = (error as Record<string, unknown>).message ?? (error as Record<string, unknown>).detail;
|
|
108
|
+
if (typeof message === 'string' && message) return message;
|
|
109
|
+
} else if (typeof error === 'string' && error) {
|
|
110
|
+
return error;
|
|
111
|
+
}
|
|
112
|
+
for (const key of ['message', 'failure_reason', 'fail_reason']) {
|
|
113
|
+
const value = response[key];
|
|
114
|
+
if (typeof value === 'string' && value) return value;
|
|
115
|
+
}
|
|
116
|
+
return 'Task failed.';
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Reduce a poll response to succeeded | failed | '' (still running).
|
|
121
|
+
*
|
|
122
|
+
* Services report completion inconsistently. A status word is the strongest
|
|
123
|
+
* signal and outranks the `success` flag, since a response can carry
|
|
124
|
+
* `success: false` for a retryable hiccup while the task is still running.
|
|
125
|
+
* This normaliser is deliberately broad; narrowing it silently hangs whichever
|
|
126
|
+
* service it drops.
|
|
127
|
+
*/
|
|
128
|
+
export function taskStatus(state: Record<string, unknown>): 'succeeded' | 'failed' | '' {
|
|
129
|
+
const response = (state.response ?? state) as Record<string, unknown> | null;
|
|
130
|
+
if (!response || typeof response !== 'object') return '';
|
|
131
|
+
|
|
132
|
+
const words = statusWords(response);
|
|
133
|
+
if (words.some((w) => FAILED.has(w))) return 'failed';
|
|
134
|
+
if (words.some((w) => DONE.has(w))) {
|
|
135
|
+
// A terminal word with no artifact means the job ended without output.
|
|
136
|
+
return artifactUrls(state).length > 0 ? 'succeeded' : 'failed';
|
|
137
|
+
}
|
|
138
|
+
if (words.length > 0) return '';
|
|
139
|
+
|
|
14
140
|
const finished =
|
|
15
141
|
(response.finished_at !== undefined && response.finished_at !== null) ||
|
|
16
142
|
(state.finished_at !== undefined && state.finished_at !== null);
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
143
|
+
if (finished) {
|
|
144
|
+
if (response.success === true) return 'succeeded';
|
|
145
|
+
if (response.success === false) return 'failed';
|
|
146
|
+
if (artifactUrls(state).length > 0) return 'succeeded';
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return artifactUrls(state).length > 0 ? 'succeeded' : '';
|
|
21
150
|
}
|
|
22
151
|
|
|
23
152
|
export class TaskHandle {
|
|
@@ -26,10 +155,33 @@ export class TaskHandle {
|
|
|
26
155
|
private transport: Transport;
|
|
27
156
|
private _result: Record<string, unknown> | null = null;
|
|
28
157
|
|
|
29
|
-
constructor(
|
|
158
|
+
constructor(
|
|
159
|
+
taskId: string,
|
|
160
|
+
pollEndpoint: string,
|
|
161
|
+
transport: Transport,
|
|
162
|
+
submitted?: Record<string, unknown>,
|
|
163
|
+
) {
|
|
30
164
|
this.id = taskId;
|
|
31
165
|
this.pollEndpoint = pollEndpoint;
|
|
32
166
|
this.transport = transport;
|
|
167
|
+
// A submission that already carried the artifact is a finished task. The
|
|
168
|
+
// caller should not have to detect that and skip wait() themselves.
|
|
169
|
+
if (submitted && artifactUrls({ response: submitted }).length > 0) {
|
|
170
|
+
this._result = { response: submitted };
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
get done(): boolean {
|
|
175
|
+
return this._result !== null;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Artifact URLs, once completed. */
|
|
179
|
+
urls(): string[] {
|
|
180
|
+
return artifactUrls(this._result);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
progress(): number | null {
|
|
184
|
+
return taskProgress(this._result);
|
|
33
185
|
}
|
|
34
186
|
|
|
35
187
|
async get(): Promise<Record<string, unknown>> {
|
|
@@ -39,12 +191,14 @@ export class TaskHandle {
|
|
|
39
191
|
}
|
|
40
192
|
|
|
41
193
|
async isCompleted(): Promise<boolean> {
|
|
42
|
-
|
|
43
|
-
const status = taskStatus(
|
|
194
|
+
if (this.done) return true;
|
|
195
|
+
const status = taskStatus(await this.get());
|
|
44
196
|
return status === 'succeeded' || status === 'failed';
|
|
45
197
|
}
|
|
46
198
|
|
|
47
199
|
async wait(opts: TaskHandleOptions = {}): Promise<Record<string, unknown>> {
|
|
200
|
+
if (this._result !== null) return this._result;
|
|
201
|
+
|
|
48
202
|
const pollInterval = opts.pollInterval ?? 3000;
|
|
49
203
|
const maxWait = opts.maxWait ?? 600_000;
|
|
50
204
|
const start = Date.now();
|
package/src/runtime/transport.ts
CHANGED
|
@@ -17,6 +17,24 @@ import type {
|
|
|
17
17
|
PaymentRequiredBody,
|
|
18
18
|
} from './payment';
|
|
19
19
|
|
|
20
|
+
function parsePaymentRequired(resp: Response, text: string): PaymentRequiredBody {
|
|
21
|
+
const encoded = resp.headers.get('PAYMENT-REQUIRED');
|
|
22
|
+
if (encoded) {
|
|
23
|
+
try {
|
|
24
|
+
return JSON.parse(atob(encoded)) as PaymentRequiredBody;
|
|
25
|
+
} catch {
|
|
26
|
+
throw mapError(402, {
|
|
27
|
+
error: { code: 'invalid_402', message: 'Invalid PAYMENT-REQUIRED header' },
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
return JSON.parse(text) as PaymentRequiredBody;
|
|
33
|
+
} catch {
|
|
34
|
+
throw mapError(402, { error: { code: 'invalid_402', message: text } });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
20
38
|
const ERROR_CODE_MAP: Record<string, typeof APIError> = {
|
|
21
39
|
invalid_token: AuthenticationError,
|
|
22
40
|
token_expired: AuthenticationError,
|
|
@@ -171,12 +189,7 @@ export class Transport {
|
|
|
171
189
|
|
|
172
190
|
if (resp.status === 402 && this.paymentHandler && !paymentAttempted) {
|
|
173
191
|
const text = await resp.text();
|
|
174
|
-
|
|
175
|
-
try {
|
|
176
|
-
body = JSON.parse(text);
|
|
177
|
-
} catch {
|
|
178
|
-
throw mapError(402, { error: { code: 'invalid_402', message: text } });
|
|
179
|
-
}
|
|
192
|
+
const body = parsePaymentRequired(resp, text);
|
|
180
193
|
if (
|
|
181
194
|
!body ||
|
|
182
195
|
typeof body !== 'object' ||
|
|
@@ -267,12 +280,7 @@ export class Transport {
|
|
|
267
280
|
|
|
268
281
|
if (resp.status === 402 && this.paymentHandler && !paymentAttempted) {
|
|
269
282
|
const text = await resp.text();
|
|
270
|
-
|
|
271
|
-
try {
|
|
272
|
-
body = JSON.parse(text);
|
|
273
|
-
} catch {
|
|
274
|
-
throw mapError(402, { error: { code: 'invalid_402', message: text } });
|
|
275
|
-
}
|
|
283
|
+
const body = parsePaymentRequired(resp, text);
|
|
276
284
|
if (
|
|
277
285
|
!body ||
|
|
278
286
|
typeof body !== 'object' ||
|