@getanyapi/sdk 0.9.8 → 0.9.9

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.cts CHANGED
@@ -114,6 +114,8 @@ interface RequestOptions {
114
114
  signal?: AbortSignal;
115
115
  /** Override the client maxRetries for this call. */
116
116
  maxRetries?: number;
117
+ /** Override the client maxInProgressWaitMs for this call. */
118
+ maxInProgressWaitMs?: number;
117
119
  /** Override the generated Idempotency-Key for this billed POST. */
118
120
  idempotencyKey?: string;
119
121
  }
@@ -133,6 +135,13 @@ interface ClientOptions {
133
135
  timeoutMs?: number;
134
136
  /** Send Idempotency-Key on billed POSTs. Default "auto"; use "off" as a kill switch. */
135
137
  idempotency?: "auto" | "off";
138
+ /**
139
+ * Total milliseconds one run() call may block waiting out a 409 `idempotency_in_progress`
140
+ * (the gateway is still executing the run this key claims). Default 60000. Set 0 to
141
+ * surface that 409 immediately. This is a whole-call budget across every in-progress
142
+ * wait, separate from the 8000ms ordinary-backoff ceiling.
143
+ */
144
+ maxInProgressWaitMs?: number;
136
145
  }
137
146
  /**
138
147
  * Account profile returned by /v1/me (internal-only fields dropped). See SPEC 2.7.
@@ -168,7 +177,7 @@ interface DiscoveryPricing {
168
177
  failoverMaxUsd: number;
169
178
  }
170
179
  interface LaneHealth {
171
- window: "30d";
180
+ window: string;
172
181
  uptimePct: number;
173
182
  latencyP50Ms: number;
174
183
  requests: number;
@@ -189,6 +198,8 @@ interface CatalogEntry {
189
198
  lanes: DiscoveryLane[];
190
199
  heavy: boolean;
191
200
  tryEligible: boolean;
201
+ failover?: boolean;
202
+ excludesCallerDelay?: boolean;
192
203
  inputSchema?: Record<string, unknown>;
193
204
  outputSchema?: Record<string, unknown>;
194
205
  }
@@ -263,6 +274,7 @@ declare class AnyAPI$1 implements ClientCore {
263
274
  private readonly maxRetries;
264
275
  private readonly timeoutMs;
265
276
  private readonly idempotency;
277
+ private readonly maxInProgressWaitMs;
266
278
  /**
267
279
  * The network seam the generated per-platform namespaces target. The base client IS a
268
280
  * ClientCore (it implements `run`), so the generated subclass hands `this._core` to each
@@ -298,6 +310,7 @@ declare class AnyAPI$1 implements ClientCore {
298
310
  body?: string;
299
311
  timeoutMs: number;
300
312
  maxRetries: number;
313
+ maxInProgressWaitMs?: number;
301
314
  signal?: AbortSignal;
302
315
  idempotencyKey?: string;
303
316
  }): Promise<T>;
package/dist/index.d.ts CHANGED
@@ -114,6 +114,8 @@ interface RequestOptions {
114
114
  signal?: AbortSignal;
115
115
  /** Override the client maxRetries for this call. */
116
116
  maxRetries?: number;
117
+ /** Override the client maxInProgressWaitMs for this call. */
118
+ maxInProgressWaitMs?: number;
117
119
  /** Override the generated Idempotency-Key for this billed POST. */
118
120
  idempotencyKey?: string;
119
121
  }
@@ -133,6 +135,13 @@ interface ClientOptions {
133
135
  timeoutMs?: number;
134
136
  /** Send Idempotency-Key on billed POSTs. Default "auto"; use "off" as a kill switch. */
135
137
  idempotency?: "auto" | "off";
138
+ /**
139
+ * Total milliseconds one run() call may block waiting out a 409 `idempotency_in_progress`
140
+ * (the gateway is still executing the run this key claims). Default 60000. Set 0 to
141
+ * surface that 409 immediately. This is a whole-call budget across every in-progress
142
+ * wait, separate from the 8000ms ordinary-backoff ceiling.
143
+ */
144
+ maxInProgressWaitMs?: number;
136
145
  }
137
146
  /**
138
147
  * Account profile returned by /v1/me (internal-only fields dropped). See SPEC 2.7.
@@ -168,7 +177,7 @@ interface DiscoveryPricing {
168
177
  failoverMaxUsd: number;
169
178
  }
170
179
  interface LaneHealth {
171
- window: "30d";
180
+ window: string;
172
181
  uptimePct: number;
173
182
  latencyP50Ms: number;
174
183
  requests: number;
@@ -189,6 +198,8 @@ interface CatalogEntry {
189
198
  lanes: DiscoveryLane[];
190
199
  heavy: boolean;
191
200
  tryEligible: boolean;
201
+ failover?: boolean;
202
+ excludesCallerDelay?: boolean;
192
203
  inputSchema?: Record<string, unknown>;
193
204
  outputSchema?: Record<string, unknown>;
194
205
  }
@@ -263,6 +274,7 @@ declare class AnyAPI$1 implements ClientCore {
263
274
  private readonly maxRetries;
264
275
  private readonly timeoutMs;
265
276
  private readonly idempotency;
277
+ private readonly maxInProgressWaitMs;
266
278
  /**
267
279
  * The network seam the generated per-platform namespaces target. The base client IS a
268
280
  * ClientCore (it implements `run`), so the generated subclass hands `this._core` to each
@@ -298,6 +310,7 @@ declare class AnyAPI$1 implements ClientCore {
298
310
  body?: string;
299
311
  timeoutMs: number;
300
312
  maxRetries: number;
313
+ maxInProgressWaitMs?: number;
301
314
  signal?: AbortSignal;
302
315
  idempotencyKey?: string;
303
316
  }): Promise<T>;
package/dist/index.js CHANGED
@@ -119,17 +119,20 @@ var DEFAULT_BASE_URL = "https://api.getanyapi.com";
119
119
  function malformed(path) {
120
120
  throw new AnyAPIError(`malformed discovery response: ${path}`, 0);
121
121
  }
122
- function rejectInternalKeys(value, path) {
122
+ function rejectUnsafeDiscoveryFields(value, path) {
123
123
  if (Array.isArray(value)) {
124
124
  value.forEach(
125
- (item, index) => rejectInternalKeys(item, `${path}[${index}]`)
125
+ (item, index) => rejectUnsafeDiscoveryFields(item, `${path}[${index}]`)
126
126
  );
127
127
  return;
128
128
  }
129
129
  if (typeof value !== "object" || value === null) return;
130
130
  for (const [key, item] of Object.entries(value)) {
131
131
  if (key.toLowerCase().includes("credit")) malformed(`${path}.${key}`);
132
- rejectInternalKeys(item, `${path}.${key}`);
132
+ if (key === "provider" && item !== "AnyAPI") {
133
+ malformed(`${path}.${key}`);
134
+ }
135
+ rejectUnsafeDiscoveryFields(item, `${path}.${key}`);
133
136
  }
134
137
  }
135
138
  function record(value, path) {
@@ -138,12 +141,6 @@ function record(value, path) {
138
141
  }
139
142
  return value;
140
143
  }
141
- function exactKeys(raw, allowed, path) {
142
- const keys = new Set(allowed);
143
- for (const key of Object.keys(raw)) {
144
- if (!keys.has(key)) malformed(`${path}.${key}`);
145
- }
146
- }
147
144
  function stringField(raw, key, path) {
148
145
  const value = raw[key];
149
146
  if (typeof value !== "string") return malformed(`${path}.${key}`);
@@ -174,14 +171,12 @@ function parseOffer(value, path) {
174
171
  const unit = stringField(raw, "unit", path);
175
172
  const maxUsd = numberField(raw, "maxUsd", path);
176
173
  if (model === "flat") {
177
- exactKeys(raw, ["model", "unit", "maxUsd"], path);
178
- if (unit !== "request" || "baseUsd" in raw || "perUnitUsd" in raw) {
174
+ if (unit !== "request") {
179
175
  return malformed(path);
180
176
  }
181
177
  return { model, unit, maxUsd };
182
178
  }
183
179
  if (model === "linear") {
184
- exactKeys(raw, ["model", "unit", "baseUsd", "perUnitUsd", "maxUsd"], path);
185
180
  if (unit.length === 0) return malformed(`${path}.unit`);
186
181
  return {
187
182
  model,
@@ -195,7 +190,6 @@ function parseOffer(value, path) {
195
190
  }
196
191
  function parsePricing(value, path) {
197
192
  const raw = record(value, path);
198
- exactKeys(raw, ["from", "failoverMaxUsd"], path);
199
193
  return {
200
194
  from: parseOffer(raw["from"], `${path}.from`),
201
195
  failoverMaxUsd: numberField(raw, "failoverMaxUsd", path)
@@ -203,10 +197,8 @@ function parsePricing(value, path) {
203
197
  }
204
198
  function parseHealth(value, path) {
205
199
  const raw = record(value, path);
206
- exactKeys(raw, ["window", "uptimePct", "latencyP50Ms", "requests"], path);
207
- if (raw["window"] !== "30d") return malformed(`${path}.window`);
208
200
  return {
209
- window: "30d",
201
+ window: stringField(raw, "window", path),
210
202
  uptimePct: boundedNumberField(raw, "uptimePct", path, void 0, 100),
211
203
  latencyP50Ms: integerField(raw, "latencyP50Ms", path),
212
204
  requests: integerField(raw, "requests", path)
@@ -214,7 +206,6 @@ function parseHealth(value, path) {
214
206
  }
215
207
  function parseLane(value, path) {
216
208
  const raw = record(value, path);
217
- exactKeys(raw, ["pricing", "health"], path);
218
209
  const lane = {
219
210
  pricing: parseOffer(raw["pricing"], `${path}.pricing`)
220
211
  };
@@ -232,7 +223,6 @@ function parseSchema(value, path) {
232
223
  }
233
224
  function parseHighlight(value, path) {
234
225
  const raw = record(value, path);
235
- exactKeys(raw, ["path", "type", "why"], path);
236
226
  const field = {
237
227
  path: stringField(raw, "path", path),
238
228
  type: stringField(raw, "type", path)
@@ -253,28 +243,10 @@ function mapProfile(raw) {
253
243
  return profile;
254
244
  }
255
245
  function mapCatalogEntry(raw) {
256
- rejectInternalKeys(raw, "api");
246
+ rejectUnsafeDiscoveryFields(raw, "api");
257
247
  const value = record(raw, "api");
258
- exactKeys(
259
- value,
260
- [
261
- "id",
262
- "slug",
263
- "category",
264
- "name",
265
- "description",
266
- "provider",
267
- "pricing",
268
- "lanes",
269
- "heavy",
270
- "tryEligible",
271
- "inputSchema",
272
- "outputSchema"
273
- ],
274
- "api"
275
- );
276
248
  const lanesRaw = value["lanes"];
277
- if (!Array.isArray(lanesRaw) || lanesRaw.length === 0) {
249
+ if (!Array.isArray(lanesRaw)) {
278
250
  return malformed("api.lanes");
279
251
  }
280
252
  const entry = {
@@ -296,32 +268,24 @@ function mapCatalogEntry(raw) {
296
268
  }
297
269
  if (typeof value["tryEligible"] !== "boolean")
298
270
  return malformed("api.tryEligible");
271
+ if (value["failover"] !== void 0) {
272
+ if (typeof value["failover"] !== "boolean")
273
+ return malformed("api.failover");
274
+ entry.failover = value["failover"];
275
+ }
276
+ if (value["excludesCallerDelay"] !== void 0) {
277
+ if (typeof value["excludesCallerDelay"] !== "boolean")
278
+ return malformed("api.excludesCallerDelay");
279
+ entry.excludesCallerDelay = value["excludesCallerDelay"];
280
+ }
299
281
  if (value["inputSchema"] !== void 0) {
300
282
  entry.inputSchema = parseSchema(value["inputSchema"], "api.inputSchema");
301
283
  }
302
284
  if (value["outputSchema"] !== void 0) {
303
285
  entry.outputSchema = parseSchema(value["outputSchema"], "api.outputSchema");
304
286
  }
305
- if (!offersEqual(entry.pricing.from, entry.lanes[0].pricing)) {
306
- return malformed("api.pricing.from");
307
- }
308
- const failoverMaxUsd = Math.max(
309
- ...entry.lanes.map((lane) => lane.pricing.maxUsd)
310
- );
311
- if (entry.pricing.failoverMaxUsd !== failoverMaxUsd) {
312
- return malformed("api.pricing.failoverMaxUsd");
313
- }
314
287
  return entry;
315
288
  }
316
- function offersEqual(left, right) {
317
- if (left.model !== right.model || left.unit !== right.unit || left.maxUsd !== right.maxUsd) {
318
- return false;
319
- }
320
- if (left.model === "flat" || right.model === "flat") {
321
- return left.model === right.model;
322
- }
323
- return left.baseUsd === right.baseUsd && left.perUnitUsd === right.perUnitUsd;
324
- }
325
289
  function mapCatalogDetail(raw) {
326
290
  const entry = mapCatalogEntry(raw);
327
291
  if (entry.inputSchema === void 0) return malformed("api.inputSchema");
@@ -329,28 +293,13 @@ function mapCatalogDetail(raw) {
329
293
  return entry;
330
294
  }
331
295
  function mapCatalogList(raw) {
296
+ rejectUnsafeDiscoveryFields(raw, "catalog");
332
297
  const envelope = record(raw, "catalog");
333
- exactKeys(envelope, ["apis"], "catalog");
334
298
  if (!Array.isArray(envelope["apis"])) return malformed("catalog.apis");
335
299
  return envelope["apis"].map(mapCatalogEntry);
336
300
  }
337
301
  function mapSearchResult(value, path) {
338
302
  const raw = record(value, path);
339
- exactKeys(
340
- raw,
341
- [
342
- "slug",
343
- "platformId",
344
- "name",
345
- "description",
346
- "category",
347
- "provider",
348
- "pricing",
349
- "relevance",
350
- "highlightFields"
351
- ],
352
- path
353
- );
354
303
  const result = {
355
304
  slug: stringField(raw, "slug", path),
356
305
  platformId: stringField(raw, "platformId", path),
@@ -371,9 +320,8 @@ function mapSearchResult(value, path) {
371
320
  return result;
372
321
  }
373
322
  function mapCatalogSearch(raw) {
374
- rejectInternalKeys(raw, "search");
323
+ rejectUnsafeDiscoveryFields(raw, "search");
375
324
  const envelope = record(raw, "search");
376
- exactKeys(envelope, ["results", "total", "ranking"], "search");
377
325
  if (!Array.isArray(envelope["results"])) return malformed("search.results");
378
326
  const ranking = envelope["ranking"];
379
327
  if (ranking !== "semantic" && ranking !== "keyword")
@@ -450,6 +398,8 @@ var DEFAULT_TIMEOUT_MS = 6e4;
450
398
  var DEFAULT_MAX_RETRIES = 2;
451
399
  var RETRY_BASE_DELAY_MS = 500;
452
400
  var RETRY_MAX_DELAY_MS = 8e3;
401
+ var DEFAULT_MAX_IN_PROGRESS_WAIT_MS = 6e4;
402
+ var IDEMPOTENCY_IN_PROGRESS_CODE = "idempotency_in_progress";
453
403
  var PRE_SEND_NETWORK_ERROR_CODES = /* @__PURE__ */ new Set([
454
404
  "EADDRNOTAVAIL",
455
405
  "EAI_AGAIN",
@@ -493,7 +443,7 @@ function backoffDelay(attempt) {
493
443
  const factor = 0.5 + Math.random();
494
444
  return base * factor;
495
445
  }
496
- function parseRetryAfter(header) {
446
+ function parseRetryAfterMs(header) {
497
447
  if (header === null) {
498
448
  return void 0;
499
449
  }
@@ -503,13 +453,11 @@ function parseRetryAfter(header) {
503
453
  }
504
454
  const seconds = Number(trimmed);
505
455
  if (Number.isFinite(seconds)) {
506
- const ms = seconds * 1e3;
507
- return Math.min(Math.max(ms, 0), RETRY_MAX_DELAY_MS);
456
+ return Math.max(seconds * 1e3, 0);
508
457
  }
509
458
  const dateMs = Date.parse(trimmed);
510
459
  if (Number.isFinite(dateMs)) {
511
- const delta = dateMs - Date.now();
512
- return Math.min(Math.max(delta, 0), RETRY_MAX_DELAY_MS);
460
+ return Math.max(dateMs - Date.now(), 0);
513
461
  }
514
462
  return void 0;
515
463
  }
@@ -614,6 +562,7 @@ var AnyAPI = class {
614
562
  maxRetries;
615
563
  timeoutMs;
616
564
  idempotency;
565
+ maxInProgressWaitMs;
617
566
  /**
618
567
  * The network seam the generated per-platform namespaces target. The base client IS a
619
568
  * ClientCore (it implements `run`), so the generated subclass hands `this._core` to each
@@ -636,6 +585,7 @@ var AnyAPI = class {
636
585
  this.fetchImpl = resolvedFetch;
637
586
  this.maxRetries = options.maxRetries ?? DEFAULT_MAX_RETRIES;
638
587
  this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
588
+ this.maxInProgressWaitMs = options.maxInProgressWaitMs ?? DEFAULT_MAX_IN_PROGRESS_WAIT_MS;
639
589
  const idempotency = options.idempotency ?? "auto";
640
590
  if (idempotency !== "auto" && idempotency !== "off") {
641
591
  throw new TypeError('idempotency must be "auto" or "off"');
@@ -656,6 +606,7 @@ var AnyAPI = class {
656
606
  body,
657
607
  timeoutMs: options?.timeoutMs ?? this.timeoutMs,
658
608
  maxRetries: options?.maxRetries ?? this.maxRetries,
609
+ maxInProgressWaitMs: options?.maxInProgressWaitMs ?? this.maxInProgressWaitMs,
659
610
  ...options?.idempotencyKey !== void 0 ? { idempotencyKey: options.idempotencyKey } : {},
660
611
  ...options?.signal ? { signal: options.signal } : {}
661
612
  }
@@ -728,6 +679,11 @@ var AnyAPI = class {
728
679
  headers["Idempotency-Key"] = key;
729
680
  }
730
681
  let attempt = 0;
682
+ let inProgressWaitedMs = 0;
683
+ const inProgressBudgetMs = Math.max(
684
+ opts.maxInProgressWaitMs ?? DEFAULT_MAX_IN_PROGRESS_WAIT_MS,
685
+ 0
686
+ );
731
687
  for (; ; ) {
732
688
  const { signal, timeoutSignal } = composeSignal(
733
689
  opts.timeoutMs,
@@ -776,13 +732,22 @@ var AnyAPI = class {
776
732
  }
777
733
  const body = await response.text().catch(() => "");
778
734
  const { message, code } = messageFromBody(body, response.status);
735
+ const retryAfterMs = parseRetryAfterMs(response.headers.get("retry-after"));
779
736
  if (response.status === 429 && attempt < opts.maxRetries) {
780
- const retryAfter = parseRetryAfter(response.headers.get("retry-after"));
781
- const delay = retryAfter ?? backoffDelay(attempt);
737
+ const delay = retryAfterMs !== void 0 ? Math.min(retryAfterMs, RETRY_MAX_DELAY_MS) : backoffDelay(attempt);
782
738
  await sleep(delay, opts.signal);
783
739
  attempt += 1;
784
740
  continue;
785
741
  }
742
+ if (billedPost && response.status === 409 && code === IDEMPOTENCY_IN_PROGRESS_CODE && attempt < opts.maxRetries) {
743
+ const delay = retryAfterMs ?? backoffDelay(attempt);
744
+ if (inProgressWaitedMs + delay <= inProgressBudgetMs) {
745
+ await sleep(delay, opts.signal);
746
+ inProgressWaitedMs += delay;
747
+ attempt += 1;
748
+ continue;
749
+ }
750
+ }
786
751
  throw errorFromStatus(response.status, message, requestId, code);
787
752
  }
788
753
  }