@acedatacloud/sdk 2026.716.1 → 2026.718.1

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.
@@ -68,6 +68,18 @@ function sleep(ms: number): Promise<void> {
68
68
  return new Promise((resolve) => setTimeout(resolve, ms));
69
69
  }
70
70
 
71
+ function isAbortError(error: unknown): boolean {
72
+ return typeof error === 'object' && error !== null && 'name' in error && error.name === 'AbortError';
73
+ }
74
+
75
+ function timeoutError(error: unknown): TimeoutError {
76
+ return new TimeoutError({
77
+ message: `Request timed out${error instanceof Error && error.message ? `: ${error.message}` : ''}`,
78
+ statusCode: 0,
79
+ code: 'timeout',
80
+ });
81
+ }
82
+
71
83
  export interface TransportOptions {
72
84
  apiToken?: string;
73
85
  baseURL?: string;
@@ -104,7 +116,7 @@ export class Transport {
104
116
  code: 'no_token',
105
117
  });
106
118
  }
107
- this.baseURL = (opts.baseURL ?? 'https://api.acedata.cloud').replace(/\/+$/, '');
119
+ this.baseURL = (opts.baseURL ?? 'https://x402.acedata.cloud').replace(/\/+$/, '');
108
120
  this.platformBaseURL = (opts.platformBaseURL ?? 'https://platform.acedata.cloud').replace(/\/+$/, '');
109
121
  this.timeout = opts.timeout ?? 300_000;
110
122
  this.maxRetries = opts.maxRetries ?? 2;
@@ -144,7 +156,8 @@ export class Transport {
144
156
  let lastError: Error | null = null;
145
157
  let paymentAttempted = false;
146
158
  let extraHeaders: Record<string, string> = {};
147
- for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
159
+ let attempt = 0;
160
+ while (true) {
148
161
  const controller = new AbortController();
149
162
  const timer = setTimeout(() => controller.abort(), timeoutMs);
150
163
  try {
@@ -158,20 +171,29 @@ export class Transport {
158
171
 
159
172
  if (resp.status === 402 && this.paymentHandler && !paymentAttempted) {
160
173
  const text = await resp.text();
161
- let body: PaymentRequiredBody;
174
+ let body: unknown;
162
175
  try {
163
- body = JSON.parse(text) as PaymentRequiredBody;
176
+ body = JSON.parse(text);
164
177
  } catch {
165
178
  throw mapError(402, { error: { code: 'invalid_402', message: text } });
166
179
  }
167
- if (!body.accepts?.length) {
180
+ if (
181
+ !body ||
182
+ typeof body !== 'object' ||
183
+ !Array.isArray((body as PaymentRequiredBody).accepts) ||
184
+ !(body as PaymentRequiredBody).accepts.length ||
185
+ !(body as PaymentRequiredBody).accepts.every(
186
+ (requirement) => requirement !== null && typeof requirement === 'object'
187
+ )
188
+ ) {
168
189
  throw mapError(402, { error: { code: 'invalid_402', message: 'No payment requirements' } });
169
190
  }
191
+ const paymentRequired = body as PaymentRequiredBody;
170
192
  const result = await this.paymentHandler({
171
193
  url,
172
194
  method,
173
195
  body: opts.json,
174
- accepts: body.accepts,
196
+ accepts: paymentRequired.accepts,
175
197
  });
176
198
  extraHeaders = { ...extraHeaders, ...result.headers };
177
199
  paymentAttempted = true;
@@ -186,8 +208,9 @@ export class Transport {
186
208
  } catch {
187
209
  body = { error: { code: 'unknown', message: text } };
188
210
  }
189
- if (RETRY_STATUS_CODES.has(resp.status) && attempt < this.maxRetries) {
211
+ if (!paymentAttempted && RETRY_STATUS_CODES.has(resp.status) && attempt < this.maxRetries) {
190
212
  await sleep(backoffDelay(attempt) * 1000);
213
+ attempt += 1;
191
214
  continue;
192
215
  }
193
216
  throw mapError(resp.status, body);
@@ -197,14 +220,19 @@ export class Transport {
197
220
  } catch (err) {
198
221
  clearTimeout(timer);
199
222
  if (err instanceof APIError) throw err;
200
- lastError = err as Error;
201
- if (attempt < this.maxRetries) {
223
+ if (isAbortError(err)) {
224
+ lastError = timeoutError(err);
225
+ } else {
226
+ lastError = err as Error;
227
+ }
228
+ if (!paymentAttempted && attempt < this.maxRetries) {
202
229
  await sleep(backoffDelay(attempt) * 1000);
230
+ attempt += 1;
203
231
  continue;
204
232
  }
233
+ throw lastError;
205
234
  }
206
235
  }
207
- throw lastError ?? new TransportError('Request failed after retries');
208
236
  }
209
237
 
210
238
  async *requestStream(
@@ -214,52 +242,115 @@ export class Transport {
214
242
  ): AsyncGenerator<string, void, unknown> {
215
243
  const url = `${this.baseURL}${path}`;
216
244
  const headers = { ...this.headers, accept: 'text/event-stream' };
217
- const controller = new AbortController();
218
- const timer = setTimeout(() => controller.abort(), opts.timeout ?? this.timeout);
219
-
220
- try {
221
- const resp = await fetch(url, {
222
- method,
223
- headers,
224
- body: opts.json ? JSON.stringify(opts.json) : undefined,
225
- signal: controller.signal,
226
- });
245
+ let paymentAttempted = false;
246
+ let extraHeaders: Record<string, string> = {};
227
247
 
228
- if (resp.status >= 400) {
229
- const text = await resp.text();
230
- let body: Record<string, unknown>;
248
+ while (true) {
249
+ const controller = new AbortController();
250
+ const timeoutMs = opts.timeout ?? this.timeout;
251
+ const connectionTimer = setTimeout(() => controller.abort(), timeoutMs);
252
+ try {
253
+ let resp: Response;
231
254
  try {
232
- body = JSON.parse(text) as Record<string, unknown>;
233
- } catch {
234
- body = { error: { code: 'unknown', message: text } };
255
+ resp = await fetch(url, {
256
+ method,
257
+ headers: { ...headers, ...extraHeaders },
258
+ body: opts.json ? JSON.stringify(opts.json) : undefined,
259
+ signal: controller.signal,
260
+ });
261
+ } catch (error) {
262
+ if (isAbortError(error)) throw timeoutError(error);
263
+ throw error;
264
+ } finally {
265
+ clearTimeout(connectionTimer);
266
+ }
267
+
268
+ if (resp.status === 402 && this.paymentHandler && !paymentAttempted) {
269
+ const text = await resp.text();
270
+ let body: unknown;
271
+ try {
272
+ body = JSON.parse(text);
273
+ } catch {
274
+ throw mapError(402, { error: { code: 'invalid_402', message: text } });
275
+ }
276
+ if (
277
+ !body ||
278
+ typeof body !== 'object' ||
279
+ !Array.isArray((body as PaymentRequiredBody).accepts) ||
280
+ !(body as PaymentRequiredBody).accepts.length ||
281
+ !(body as PaymentRequiredBody).accepts.every(
282
+ (requirement) => requirement !== null && typeof requirement === 'object'
283
+ )
284
+ ) {
285
+ throw mapError(402, { error: { code: 'invalid_402', message: 'No payment requirements' } });
286
+ }
287
+ const paymentRequired = body as PaymentRequiredBody;
288
+ const result = await this.paymentHandler({
289
+ url,
290
+ method,
291
+ body: opts.json,
292
+ accepts: paymentRequired.accepts,
293
+ });
294
+ extraHeaders = { ...extraHeaders, ...result.headers };
295
+ paymentAttempted = true;
296
+ continue;
297
+ }
298
+
299
+ if (resp.status >= 400) {
300
+ const text = await resp.text();
301
+ let body: Record<string, unknown>;
302
+ try {
303
+ body = JSON.parse(text) as Record<string, unknown>;
304
+ } catch {
305
+ body = { error: { code: 'unknown', message: text } };
306
+ }
307
+ throw mapError(resp.status, body);
235
308
  }
236
- throw mapError(resp.status, body);
237
- }
238
309
 
239
- if (!resp.body) throw new TransportError('No response body for stream');
310
+ if (!resp.body) throw new TransportError('No response body for stream');
240
311
 
241
- const reader = resp.body.getReader();
242
- const decoder = new TextDecoder();
243
- let buffer = '';
312
+ const reader = resp.body.getReader();
313
+ const decoder = new TextDecoder();
314
+ let buffer = '';
244
315
 
245
- while (true) {
246
- const { done, value } = await reader.read();
247
- if (done) break;
248
- buffer += decoder.decode(value, { stream: true });
316
+ try {
317
+ while (true) {
318
+ const idleTimer = setTimeout(() => controller.abort(), timeoutMs);
319
+ let result: Awaited<ReturnType<typeof reader.read>>;
320
+ try {
321
+ result = await reader.read();
322
+ } catch (error) {
323
+ if (isAbortError(error)) throw timeoutError(error);
324
+ throw error;
325
+ } finally {
326
+ clearTimeout(idleTimer);
327
+ }
328
+ const { done, value } = result;
329
+ if (done) break;
330
+ buffer += decoder.decode(value, { stream: true });
249
331
 
250
- const lines = buffer.split('\n');
251
- buffer = lines.pop() ?? '';
332
+ const lines = buffer.split('\n');
333
+ buffer = lines.pop() ?? '';
252
334
 
253
- for (const line of lines) {
254
- if (line.startsWith('data: ')) {
255
- const data = line.slice(6);
256
- if (data === '[DONE]') return;
257
- yield data;
335
+ for (const line of lines) {
336
+ if (line.startsWith('data: ')) {
337
+ const data = line.slice(6);
338
+ if (data === '[DONE]') return;
339
+ yield data;
340
+ }
341
+ }
342
+ }
343
+ } finally {
344
+ try {
345
+ await reader.cancel();
346
+ } catch {
347
+ // Preserve the original stream error, especially SDK TimeoutError.
258
348
  }
259
349
  }
350
+ return;
351
+ } finally {
352
+ clearTimeout(connectionTimer);
260
353
  }
261
- } finally {
262
- clearTimeout(timer);
263
354
  }
264
355
  }
265
356
 
@@ -288,12 +379,18 @@ export class Transport {
288
379
  const controller = new AbortController();
289
380
  const timer = setTimeout(() => controller.abort(), opts.timeout ?? this.timeout);
290
381
  try {
291
- const resp = await fetch(url, {
292
- method: 'POST',
293
- headers: authHeaders,
294
- body,
295
- signal: controller.signal,
296
- });
382
+ let resp: Response;
383
+ try {
384
+ resp = await fetch(url, {
385
+ method: 'POST',
386
+ headers: authHeaders,
387
+ body,
388
+ signal: controller.signal,
389
+ });
390
+ } catch (error) {
391
+ if (isAbortError(error)) throw timeoutError(error);
392
+ throw error;
393
+ }
297
394
  clearTimeout(timer);
298
395
 
299
396
  if (resp.status >= 400) {