@getanyapi/sdk 0.9.8 → 0.9.10
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/README.md +31 -8
- package/dist/index.cjs +47 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +19 -2
- package/dist/index.d.ts +19 -2
- package/dist/index.js +47 -82
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,7 +73,11 @@ console.log(api.pricing.from, api.pricing.failoverMaxUsd, api.inputSchema);
|
|
|
73
73
|
|
|
74
74
|
`catalog` is category-only browsing. Ranked queries always use `search`, which returns
|
|
75
75
|
`results`, `total`, and `ranking`; `describe` includes schemas. Discovery prices are nested
|
|
76
|
-
USD flat/linear offers, lanes are anonymous, and provider is always `"AnyAPI"`.
|
|
76
|
+
USD flat/linear offers, lanes are anonymous, and provider is always `"AnyAPI"`. The gateway
|
|
77
|
+
owns validation, routing, lane order, failover, pricing relationships, health semantics, and
|
|
78
|
+
billing. This handwritten discovery client safety-scans the response, projects known fields,
|
|
79
|
+
preserves schemas as opaque JSON, and ignores safe additions. It does not recompute gateway
|
|
80
|
+
business rules. Generated per-SKU methods remain a separate OpenAPI-driven surface.
|
|
77
81
|
|
|
78
82
|
## Pagination
|
|
79
83
|
|
|
@@ -113,7 +117,8 @@ await client.google.search(
|
|
|
113
117
|
);
|
|
114
118
|
```
|
|
115
119
|
|
|
116
|
-
Per-call transport overrides: `timeoutMs`, `maxRetries`, and an
|
|
120
|
+
Per-call transport overrides: `timeoutMs`, `maxRetries`, `maxInProgressWaitMs`, and an
|
|
121
|
+
`AbortSignal` via `signal`.
|
|
117
122
|
|
|
118
123
|
## Errors and retries
|
|
119
124
|
|
|
@@ -129,12 +134,30 @@ Per-call transport overrides: `timeoutMs`, `maxRetries`, and an `AbortSignal` vi
|
|
|
129
134
|
| `ConnectionError` | 0 | Network or transport failure |
|
|
130
135
|
| `TimeoutError` | 0 | Request exceeded its timeout (not retried) |
|
|
131
136
|
|
|
132
|
-
All extend `AnyAPIError` (with `status` and `requestId`). Retries cover
|
|
133
|
-
failures proven to happen before a request was sent, with jittered
|
|
134
|
-
`Retry-After`. Default `maxRetries` is 2 (up to 3 attempts); set it
|
|
135
|
-
Timeouts are never retried. Connection failures during or after a
|
|
136
|
-
retried because the call may already have been charged. When the
|
|
137
|
-
does not retry. Configure with
|
|
137
|
+
All extend `AnyAPIError` (with `status` and `requestId`). Retries cover 429, one specific 409
|
|
138
|
+
(below), and network failures proven to happen before a request was sent, with jittered
|
|
139
|
+
exponential backoff honoring `Retry-After`. Default `maxRetries` is 2 (up to 3 attempts); set it
|
|
140
|
+
on the client or per request. Timeouts are never retried. Connection failures during or after a
|
|
141
|
+
billed `POST /v1/run` are not retried because the call may already have been charged. When the
|
|
142
|
+
send phase is unknown, the SDK does not retry. Configure with
|
|
143
|
+
`new AnyAPI({ timeoutMs, maxRetries })`.
|
|
144
|
+
|
|
145
|
+
### Waiting out a run that is still in flight
|
|
146
|
+
|
|
147
|
+
Settlement is detached from your connection, so a run keeps going after a connection drops. When
|
|
148
|
+
you re-issue a call whose `Idempotency-Key` is still executing, the gateway answers `409` with
|
|
149
|
+
`code: "idempotency_in_progress"` and `Retry-After: 30`. The SDK waits that full delay and
|
|
150
|
+
retries, so you get the original run's replayed result (`replayed: true`, no second charge)
|
|
151
|
+
instead of an error.
|
|
152
|
+
|
|
153
|
+
The 8s ordinary-backoff ceiling does not apply here; a separate whole-call budget does.
|
|
154
|
+
`maxInProgressWaitMs` (default `60000`) caps the TOTAL time one `run()` may block on these
|
|
155
|
+
waits, across every retry. A wait that does not fit the remaining budget is refused and the
|
|
156
|
+
`409` is thrown rather than truncated into an attempt that would fail anyway. Set
|
|
157
|
+
`maxInProgressWaitMs: 0` to surface the `409` immediately and handle it yourself.
|
|
158
|
+
|
|
159
|
+
No other `409` retries: `idempotency_conflict` (the same key with different input) and
|
|
160
|
+
`idempotency_needs_review` are caller-side problems a retry cannot fix.
|
|
138
161
|
|
|
139
162
|
Automatic network retry of a billed `run()` requires structured runtime evidence that the
|
|
140
163
|
request body was not sent:
|
package/dist/index.cjs
CHANGED
|
@@ -218,17 +218,20 @@ var DEFAULT_BASE_URL = "https://api.getanyapi.com";
|
|
|
218
218
|
function malformed(path) {
|
|
219
219
|
throw new AnyAPIError(`malformed discovery response: ${path}`, 0);
|
|
220
220
|
}
|
|
221
|
-
function
|
|
221
|
+
function rejectUnsafeDiscoveryFields(value, path) {
|
|
222
222
|
if (Array.isArray(value)) {
|
|
223
223
|
value.forEach(
|
|
224
|
-
(item, index) =>
|
|
224
|
+
(item, index) => rejectUnsafeDiscoveryFields(item, `${path}[${index}]`)
|
|
225
225
|
);
|
|
226
226
|
return;
|
|
227
227
|
}
|
|
228
228
|
if (typeof value !== "object" || value === null) return;
|
|
229
229
|
for (const [key, item] of Object.entries(value)) {
|
|
230
230
|
if (key.toLowerCase().includes("credit")) malformed(`${path}.${key}`);
|
|
231
|
-
|
|
231
|
+
if (key === "provider" && item !== "AnyAPI") {
|
|
232
|
+
malformed(`${path}.${key}`);
|
|
233
|
+
}
|
|
234
|
+
rejectUnsafeDiscoveryFields(item, `${path}.${key}`);
|
|
232
235
|
}
|
|
233
236
|
}
|
|
234
237
|
function record(value, path) {
|
|
@@ -237,12 +240,6 @@ function record(value, path) {
|
|
|
237
240
|
}
|
|
238
241
|
return value;
|
|
239
242
|
}
|
|
240
|
-
function exactKeys(raw, allowed, path) {
|
|
241
|
-
const keys = new Set(allowed);
|
|
242
|
-
for (const key of Object.keys(raw)) {
|
|
243
|
-
if (!keys.has(key)) malformed(`${path}.${key}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
243
|
function stringField(raw, key, path) {
|
|
247
244
|
const value = raw[key];
|
|
248
245
|
if (typeof value !== "string") return malformed(`${path}.${key}`);
|
|
@@ -273,14 +270,12 @@ function parseOffer(value, path) {
|
|
|
273
270
|
const unit = stringField(raw, "unit", path);
|
|
274
271
|
const maxUsd = numberField(raw, "maxUsd", path);
|
|
275
272
|
if (model === "flat") {
|
|
276
|
-
|
|
277
|
-
if (unit !== "request" || "baseUsd" in raw || "perUnitUsd" in raw) {
|
|
273
|
+
if (unit !== "request") {
|
|
278
274
|
return malformed(path);
|
|
279
275
|
}
|
|
280
276
|
return { model, unit, maxUsd };
|
|
281
277
|
}
|
|
282
278
|
if (model === "linear") {
|
|
283
|
-
exactKeys(raw, ["model", "unit", "baseUsd", "perUnitUsd", "maxUsd"], path);
|
|
284
279
|
if (unit.length === 0) return malformed(`${path}.unit`);
|
|
285
280
|
return {
|
|
286
281
|
model,
|
|
@@ -294,7 +289,6 @@ function parseOffer(value, path) {
|
|
|
294
289
|
}
|
|
295
290
|
function parsePricing(value, path) {
|
|
296
291
|
const raw = record(value, path);
|
|
297
|
-
exactKeys(raw, ["from", "failoverMaxUsd"], path);
|
|
298
292
|
return {
|
|
299
293
|
from: parseOffer(raw["from"], `${path}.from`),
|
|
300
294
|
failoverMaxUsd: numberField(raw, "failoverMaxUsd", path)
|
|
@@ -302,10 +296,8 @@ function parsePricing(value, path) {
|
|
|
302
296
|
}
|
|
303
297
|
function parseHealth(value, path) {
|
|
304
298
|
const raw = record(value, path);
|
|
305
|
-
exactKeys(raw, ["window", "uptimePct", "latencyP50Ms", "requests"], path);
|
|
306
|
-
if (raw["window"] !== "30d") return malformed(`${path}.window`);
|
|
307
299
|
return {
|
|
308
|
-
window: "
|
|
300
|
+
window: stringField(raw, "window", path),
|
|
309
301
|
uptimePct: boundedNumberField(raw, "uptimePct", path, void 0, 100),
|
|
310
302
|
latencyP50Ms: integerField(raw, "latencyP50Ms", path),
|
|
311
303
|
requests: integerField(raw, "requests", path)
|
|
@@ -313,7 +305,6 @@ function parseHealth(value, path) {
|
|
|
313
305
|
}
|
|
314
306
|
function parseLane(value, path) {
|
|
315
307
|
const raw = record(value, path);
|
|
316
|
-
exactKeys(raw, ["pricing", "health"], path);
|
|
317
308
|
const lane = {
|
|
318
309
|
pricing: parseOffer(raw["pricing"], `${path}.pricing`)
|
|
319
310
|
};
|
|
@@ -331,7 +322,6 @@ function parseSchema(value, path) {
|
|
|
331
322
|
}
|
|
332
323
|
function parseHighlight(value, path) {
|
|
333
324
|
const raw = record(value, path);
|
|
334
|
-
exactKeys(raw, ["path", "type", "why"], path);
|
|
335
325
|
const field = {
|
|
336
326
|
path: stringField(raw, "path", path),
|
|
337
327
|
type: stringField(raw, "type", path)
|
|
@@ -352,28 +342,10 @@ function mapProfile(raw) {
|
|
|
352
342
|
return profile;
|
|
353
343
|
}
|
|
354
344
|
function mapCatalogEntry(raw) {
|
|
355
|
-
|
|
345
|
+
rejectUnsafeDiscoveryFields(raw, "api");
|
|
356
346
|
const value = record(raw, "api");
|
|
357
|
-
exactKeys(
|
|
358
|
-
value,
|
|
359
|
-
[
|
|
360
|
-
"id",
|
|
361
|
-
"slug",
|
|
362
|
-
"category",
|
|
363
|
-
"name",
|
|
364
|
-
"description",
|
|
365
|
-
"provider",
|
|
366
|
-
"pricing",
|
|
367
|
-
"lanes",
|
|
368
|
-
"heavy",
|
|
369
|
-
"tryEligible",
|
|
370
|
-
"inputSchema",
|
|
371
|
-
"outputSchema"
|
|
372
|
-
],
|
|
373
|
-
"api"
|
|
374
|
-
);
|
|
375
347
|
const lanesRaw = value["lanes"];
|
|
376
|
-
if (!Array.isArray(lanesRaw)
|
|
348
|
+
if (!Array.isArray(lanesRaw)) {
|
|
377
349
|
return malformed("api.lanes");
|
|
378
350
|
}
|
|
379
351
|
const entry = {
|
|
@@ -395,32 +367,24 @@ function mapCatalogEntry(raw) {
|
|
|
395
367
|
}
|
|
396
368
|
if (typeof value["tryEligible"] !== "boolean")
|
|
397
369
|
return malformed("api.tryEligible");
|
|
370
|
+
if (value["failover"] !== void 0) {
|
|
371
|
+
if (typeof value["failover"] !== "boolean")
|
|
372
|
+
return malformed("api.failover");
|
|
373
|
+
entry.failover = value["failover"];
|
|
374
|
+
}
|
|
375
|
+
if (value["excludesCallerDelay"] !== void 0) {
|
|
376
|
+
if (typeof value["excludesCallerDelay"] !== "boolean")
|
|
377
|
+
return malformed("api.excludesCallerDelay");
|
|
378
|
+
entry.excludesCallerDelay = value["excludesCallerDelay"];
|
|
379
|
+
}
|
|
398
380
|
if (value["inputSchema"] !== void 0) {
|
|
399
381
|
entry.inputSchema = parseSchema(value["inputSchema"], "api.inputSchema");
|
|
400
382
|
}
|
|
401
383
|
if (value["outputSchema"] !== void 0) {
|
|
402
384
|
entry.outputSchema = parseSchema(value["outputSchema"], "api.outputSchema");
|
|
403
385
|
}
|
|
404
|
-
if (!offersEqual(entry.pricing.from, entry.lanes[0].pricing)) {
|
|
405
|
-
return malformed("api.pricing.from");
|
|
406
|
-
}
|
|
407
|
-
const failoverMaxUsd = Math.max(
|
|
408
|
-
...entry.lanes.map((lane) => lane.pricing.maxUsd)
|
|
409
|
-
);
|
|
410
|
-
if (entry.pricing.failoverMaxUsd !== failoverMaxUsd) {
|
|
411
|
-
return malformed("api.pricing.failoverMaxUsd");
|
|
412
|
-
}
|
|
413
386
|
return entry;
|
|
414
387
|
}
|
|
415
|
-
function offersEqual(left, right) {
|
|
416
|
-
if (left.model !== right.model || left.unit !== right.unit || left.maxUsd !== right.maxUsd) {
|
|
417
|
-
return false;
|
|
418
|
-
}
|
|
419
|
-
if (left.model === "flat" || right.model === "flat") {
|
|
420
|
-
return left.model === right.model;
|
|
421
|
-
}
|
|
422
|
-
return left.baseUsd === right.baseUsd && left.perUnitUsd === right.perUnitUsd;
|
|
423
|
-
}
|
|
424
388
|
function mapCatalogDetail(raw) {
|
|
425
389
|
const entry = mapCatalogEntry(raw);
|
|
426
390
|
if (entry.inputSchema === void 0) return malformed("api.inputSchema");
|
|
@@ -428,28 +392,13 @@ function mapCatalogDetail(raw) {
|
|
|
428
392
|
return entry;
|
|
429
393
|
}
|
|
430
394
|
function mapCatalogList(raw) {
|
|
395
|
+
rejectUnsafeDiscoveryFields(raw, "catalog");
|
|
431
396
|
const envelope = record(raw, "catalog");
|
|
432
|
-
exactKeys(envelope, ["apis"], "catalog");
|
|
433
397
|
if (!Array.isArray(envelope["apis"])) return malformed("catalog.apis");
|
|
434
398
|
return envelope["apis"].map(mapCatalogEntry);
|
|
435
399
|
}
|
|
436
400
|
function mapSearchResult(value, path) {
|
|
437
401
|
const raw = record(value, path);
|
|
438
|
-
exactKeys(
|
|
439
|
-
raw,
|
|
440
|
-
[
|
|
441
|
-
"slug",
|
|
442
|
-
"platformId",
|
|
443
|
-
"name",
|
|
444
|
-
"description",
|
|
445
|
-
"category",
|
|
446
|
-
"provider",
|
|
447
|
-
"pricing",
|
|
448
|
-
"relevance",
|
|
449
|
-
"highlightFields"
|
|
450
|
-
],
|
|
451
|
-
path
|
|
452
|
-
);
|
|
453
402
|
const result = {
|
|
454
403
|
slug: stringField(raw, "slug", path),
|
|
455
404
|
platformId: stringField(raw, "platformId", path),
|
|
@@ -470,9 +419,8 @@ function mapSearchResult(value, path) {
|
|
|
470
419
|
return result;
|
|
471
420
|
}
|
|
472
421
|
function mapCatalogSearch(raw) {
|
|
473
|
-
|
|
422
|
+
rejectUnsafeDiscoveryFields(raw, "search");
|
|
474
423
|
const envelope = record(raw, "search");
|
|
475
|
-
exactKeys(envelope, ["results", "total", "ranking"], "search");
|
|
476
424
|
if (!Array.isArray(envelope["results"])) return malformed("search.results");
|
|
477
425
|
const ranking = envelope["ranking"];
|
|
478
426
|
if (ranking !== "semantic" && ranking !== "keyword")
|
|
@@ -545,10 +493,12 @@ async function agentSignup(options = {}) {
|
|
|
545
493
|
|
|
546
494
|
// src/core/client.ts
|
|
547
495
|
var DEFAULT_BASE_URL2 = "https://api.getanyapi.com";
|
|
548
|
-
var DEFAULT_TIMEOUT_MS =
|
|
496
|
+
var DEFAULT_TIMEOUT_MS = 3e5;
|
|
549
497
|
var DEFAULT_MAX_RETRIES = 2;
|
|
550
498
|
var RETRY_BASE_DELAY_MS = 500;
|
|
551
499
|
var RETRY_MAX_DELAY_MS = 8e3;
|
|
500
|
+
var DEFAULT_MAX_IN_PROGRESS_WAIT_MS = 6e4;
|
|
501
|
+
var IDEMPOTENCY_IN_PROGRESS_CODE = "idempotency_in_progress";
|
|
552
502
|
var PRE_SEND_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
|
|
553
503
|
"EADDRNOTAVAIL",
|
|
554
504
|
"EAI_AGAIN",
|
|
@@ -592,7 +542,7 @@ function backoffDelay(attempt) {
|
|
|
592
542
|
const factor = 0.5 + Math.random();
|
|
593
543
|
return base * factor;
|
|
594
544
|
}
|
|
595
|
-
function
|
|
545
|
+
function parseRetryAfterMs(header) {
|
|
596
546
|
if (header === null) {
|
|
597
547
|
return void 0;
|
|
598
548
|
}
|
|
@@ -602,13 +552,11 @@ function parseRetryAfter(header) {
|
|
|
602
552
|
}
|
|
603
553
|
const seconds = Number(trimmed);
|
|
604
554
|
if (Number.isFinite(seconds)) {
|
|
605
|
-
|
|
606
|
-
return Math.min(Math.max(ms, 0), RETRY_MAX_DELAY_MS);
|
|
555
|
+
return Math.max(seconds * 1e3, 0);
|
|
607
556
|
}
|
|
608
557
|
const dateMs = Date.parse(trimmed);
|
|
609
558
|
if (Number.isFinite(dateMs)) {
|
|
610
|
-
|
|
611
|
-
return Math.min(Math.max(delta, 0), RETRY_MAX_DELAY_MS);
|
|
559
|
+
return Math.max(dateMs - Date.now(), 0);
|
|
612
560
|
}
|
|
613
561
|
return void 0;
|
|
614
562
|
}
|
|
@@ -713,6 +661,7 @@ var AnyAPI = class {
|
|
|
713
661
|
maxRetries;
|
|
714
662
|
timeoutMs;
|
|
715
663
|
idempotency;
|
|
664
|
+
maxInProgressWaitMs;
|
|
716
665
|
/**
|
|
717
666
|
* The network seam the generated per-platform namespaces target. The base client IS a
|
|
718
667
|
* ClientCore (it implements `run`), so the generated subclass hands `this._core` to each
|
|
@@ -735,6 +684,7 @@ var AnyAPI = class {
|
|
|
735
684
|
this.fetchImpl = resolvedFetch;
|
|
736
685
|
this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
|
|
737
686
|
this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
687
|
+
this.maxInProgressWaitMs = options.maxInProgressWaitMs ?? DEFAULT_MAX_IN_PROGRESS_WAIT_MS;
|
|
738
688
|
const idempotency = options.idempotency ?? "auto";
|
|
739
689
|
if (idempotency !== "auto" && idempotency !== "off") {
|
|
740
690
|
throw new TypeError('idempotency must be "auto" or "off"');
|
|
@@ -755,6 +705,7 @@ var AnyAPI = class {
|
|
|
755
705
|
body,
|
|
756
706
|
timeoutMs: options?.timeoutMs ?? this.timeoutMs,
|
|
757
707
|
maxRetries: options?.maxRetries ?? this.maxRetries,
|
|
708
|
+
maxInProgressWaitMs: options?.maxInProgressWaitMs ?? this.maxInProgressWaitMs,
|
|
758
709
|
...options?.idempotencyKey !== void 0 ? { idempotencyKey: options.idempotencyKey } : {},
|
|
759
710
|
...options?.signal ? { signal: options.signal } : {}
|
|
760
711
|
}
|
|
@@ -827,6 +778,11 @@ var AnyAPI = class {
|
|
|
827
778
|
headers["Idempotency-Key"] = key;
|
|
828
779
|
}
|
|
829
780
|
let attempt = 0;
|
|
781
|
+
let inProgressWaitedMs = 0;
|
|
782
|
+
const inProgressBudgetMs = Math.max(
|
|
783
|
+
opts.maxInProgressWaitMs ?? DEFAULT_MAX_IN_PROGRESS_WAIT_MS,
|
|
784
|
+
0
|
|
785
|
+
);
|
|
830
786
|
for (; ; ) {
|
|
831
787
|
const { signal, timeoutSignal } = composeSignal(
|
|
832
788
|
opts.timeoutMs,
|
|
@@ -875,13 +831,22 @@ var AnyAPI = class {
|
|
|
875
831
|
}
|
|
876
832
|
const body = await response.text().catch(() => "");
|
|
877
833
|
const { message, code } = messageFromBody(body, response.status);
|
|
834
|
+
const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
|
|
878
835
|
if (response.status === 429 && attempt < opts.maxRetries) {
|
|
879
|
-
const
|
|
880
|
-
const delay = retryAfter ?? backoffDelay(attempt);
|
|
836
|
+
const delay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, RETRY_MAX_DELAY_MS) : backoffDelay(attempt);
|
|
881
837
|
await sleep(delay, opts.signal);
|
|
882
838
|
attempt += 1;
|
|
883
839
|
continue;
|
|
884
840
|
}
|
|
841
|
+
if (billedPost && response.status === 409 && code === IDEMPOTENCY_IN_PROGRESS_CODE && attempt < opts.maxRetries) {
|
|
842
|
+
const delay = retryAfterMs ?? backoffDelay(attempt);
|
|
843
|
+
if (inProgressWaitedMs + delay <= inProgressBudgetMs) {
|
|
844
|
+
await sleep(delay, opts.signal);
|
|
845
|
+
inProgressWaitedMs += delay;
|
|
846
|
+
attempt += 1;
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
885
850
|
throw errorFromStatus(response.status, message, requestId, code);
|
|
886
851
|
}
|
|
887
852
|
}
|