@apifuse/provider-sdk 2.2.0-beta.1 → 2.2.0-beta.2

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.
@@ -1,17 +1,11 @@
1
1
  import type { ProxyResolutionOptions } from "../config/loader";
2
2
  import { resolveProxyConfigAsync } from "../config/loader";
3
3
  import { ProviderError, TransportError } from "../errors";
4
- import {
5
- parseSseStream,
6
- readableBytes,
7
- readableLines,
8
- readableTextChunks,
9
- } from "../stream";
4
+ import { parseSseStream, readableBytes, readableLines, readableTextChunks } from "../stream";
10
5
  import type {
11
6
  HttpClient,
12
7
  HttpMethod,
13
8
  HttpResponse,
14
- HttpRetryOptions,
15
9
  HttpRetrySummary,
16
10
  HttpStreamResponse,
17
11
  RequestOptions,
@@ -19,12 +13,16 @@ import type {
19
13
  SseMessage,
20
14
  } from "../types";
21
15
  import {
22
- HttpRetryAfterPolicy,
23
- HttpRetryDelayStrategy,
24
- HttpRetryJitter,
25
- HttpRetryPreset,
26
- HttpRetryUnsafeMethodPolicy,
27
- } from "../types";
16
+ computeProxyAttemptIndex,
17
+ computeProxyTransportRetryDelayMs,
18
+ createDefaultProxyTransportRetryOptions,
19
+ isProxyTransportRetryMethod,
20
+ normalizeProxyTransportRetryOptions,
21
+ proxyTransportRetryErrorCode,
22
+ proxyTransportRetryErrorStatus,
23
+ shouldRetryProxyTransportAttempt,
24
+ validateUnsafeProxyTransportRetryMethods,
25
+ } from "./proxy-retry-policy";
28
26
  import { appendQueryParams, normalizeHttpRequestBody } from "./request-options";
29
27
 
30
28
  const DEFAULT_HTTP_BASE_URL = "http://localhost";
@@ -35,24 +33,6 @@ export type HttpClientOptions = ProxyResolutionOptions & {
35
33
  onRetrySummary?: (summary: HttpRetrySummary) => void;
36
34
  };
37
35
 
38
- type NormalizedRetryOptions = Required<
39
- Pick<
40
- HttpRetryOptions,
41
- | "attempts"
42
- | "delayStrategy"
43
- | "baseDelayMs"
44
- | "maxDelayMs"
45
- | "jitter"
46
- | "retryAfter"
47
- | "unsafeMethodPolicy"
48
- >
49
- > & {
50
- preset?: HttpRetryPreset;
51
- methods: readonly string[];
52
- statusCodes: readonly number[];
53
- errorCodes: readonly string[];
54
- };
55
-
56
36
  type HttpStatusOutcome = {
57
37
  kind: "http-status";
58
38
  status: number;
@@ -71,416 +51,6 @@ function isHttpStatusOutcome(
71
51
  return "kind" in outcome && outcome.kind === "http-status";
72
52
  }
73
53
 
74
- const DEFAULT_RETRY_METHODS = ["GET", "HEAD", "OPTIONS"] as const;
75
- const DEFAULT_RETRY_ERROR_CODES = [
76
- "transport_network_error",
77
- "transport_timeout",
78
- ] as const;
79
- const SAFE_RETRY_STATUS_CODES = [408, 429, 500, 502, 503, 504] as const;
80
- const RATE_LIMIT_RETRY_STATUS_CODES = [429, 503] as const;
81
- const KNOWN_RETRY_METHODS = new Set([
82
- "GET",
83
- "HEAD",
84
- "POST",
85
- "PUT",
86
- "DELETE",
87
- "OPTIONS",
88
- "TRACE",
89
- "PATCH",
90
- ]);
91
- const UNSAFE_RETRY_METHODS = new Set([
92
- "POST",
93
- "PUT",
94
- "PATCH",
95
- "DELETE",
96
- "TRACE",
97
- ]);
98
- const MAX_RETRY_ATTEMPTS = 8;
99
- const MAX_RETRY_DELAY_MS = 30_000;
100
-
101
- function hasOwnValue<T extends string>(
102
- values: Record<string, T>,
103
- value: unknown,
104
- ): value is T {
105
- if (typeof value !== "string") return false;
106
- return Object.values(values).some((candidate) => candidate === value);
107
- }
108
-
109
- function createInvalidRetryPolicyError(message: string): ProviderError {
110
- return new ProviderError(message, { code: "retry_invalid_policy" });
111
- }
112
-
113
- function createRetryOptions(preset: HttpRetryPreset): NormalizedRetryOptions {
114
- switch (preset) {
115
- case HttpRetryPreset.Off:
116
- return {
117
- preset,
118
- attempts: 1,
119
- methods: DEFAULT_RETRY_METHODS,
120
- statusCodes: [],
121
- errorCodes: DEFAULT_RETRY_ERROR_CODES,
122
- delayStrategy: HttpRetryDelayStrategy.Exponential,
123
- baseDelayMs: 100,
124
- maxDelayMs: 1_000,
125
- jitter: HttpRetryJitter.Full,
126
- retryAfter: HttpRetryAfterPolicy.Ignore,
127
- unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
128
- };
129
- case HttpRetryPreset.SafeRead:
130
- return {
131
- preset,
132
- attempts: 3,
133
- methods: DEFAULT_RETRY_METHODS,
134
- statusCodes: SAFE_RETRY_STATUS_CODES,
135
- errorCodes: DEFAULT_RETRY_ERROR_CODES,
136
- delayStrategy: HttpRetryDelayStrategy.Exponential,
137
- baseDelayMs: 100,
138
- maxDelayMs: 2_000,
139
- jitter: HttpRetryJitter.Full,
140
- retryAfter: HttpRetryAfterPolicy.Cap,
141
- unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
142
- };
143
- case HttpRetryPreset.AggressiveRead:
144
- return {
145
- preset,
146
- attempts: 4,
147
- methods: DEFAULT_RETRY_METHODS,
148
- statusCodes: SAFE_RETRY_STATUS_CODES,
149
- errorCodes: DEFAULT_RETRY_ERROR_CODES,
150
- delayStrategy: HttpRetryDelayStrategy.Exponential,
151
- baseDelayMs: 150,
152
- maxDelayMs: 5_000,
153
- jitter: HttpRetryJitter.Full,
154
- retryAfter: HttpRetryAfterPolicy.Cap,
155
- unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
156
- };
157
- case HttpRetryPreset.RateLimitAware:
158
- return {
159
- preset,
160
- attempts: 3,
161
- methods: DEFAULT_RETRY_METHODS,
162
- statusCodes: RATE_LIMIT_RETRY_STATUS_CODES,
163
- errorCodes: ["transport_timeout"],
164
- delayStrategy: HttpRetryDelayStrategy.Exponential,
165
- baseDelayMs: 250,
166
- maxDelayMs: 5_000,
167
- jitter: HttpRetryJitter.Equal,
168
- retryAfter: HttpRetryAfterPolicy.Respect,
169
- unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
170
- };
171
- case HttpRetryPreset.TransportTransient:
172
- return {
173
- preset,
174
- attempts: 3,
175
- methods: DEFAULT_RETRY_METHODS,
176
- statusCodes: [],
177
- errorCodes: DEFAULT_RETRY_ERROR_CODES,
178
- delayStrategy: HttpRetryDelayStrategy.Exponential,
179
- baseDelayMs: 100,
180
- maxDelayMs: 1_000,
181
- jitter: HttpRetryJitter.Full,
182
- retryAfter: HttpRetryAfterPolicy.Ignore,
183
- unsafeMethodPolicy: HttpRetryUnsafeMethodPolicy.Reject,
184
- };
185
- }
186
- throw createInvalidRetryPolicyError(`Unknown HTTP retry preset: ${preset}`);
187
- }
188
-
189
- function clampPositiveInteger(
190
- value: number | undefined,
191
- fallback: number,
192
- max: number,
193
- ): number {
194
- if (value === undefined) return fallback;
195
- if (!Number.isFinite(value) || value < 1) return fallback;
196
- return Math.min(Math.floor(value), max);
197
- }
198
-
199
- function clampDelay(value: number | undefined, fallback: number): number {
200
- if (value === undefined) return fallback;
201
- if (!Number.isFinite(value) || value < 0) return fallback;
202
- return Math.min(Math.floor(value), MAX_RETRY_DELAY_MS);
203
- }
204
-
205
- function normalizeRetryOptions(
206
- retry: RequestOptions["retry"],
207
- ): NormalizedRetryOptions | undefined {
208
- if (retry === undefined || retry === false) {
209
- return undefined;
210
- }
211
- if (retry === true) {
212
- return createRetryOptions(HttpRetryPreset.TransportTransient);
213
- }
214
- if (typeof retry === "string") {
215
- if (!hasOwnValue(HttpRetryPreset, retry)) {
216
- throw createInvalidRetryPolicyError(
217
- `Unknown HTTP retry preset: ${retry}`,
218
- );
219
- }
220
- return createRetryOptions(retry);
221
- }
222
- if (typeof retry !== "object" || retry === null) {
223
- throw createInvalidRetryPolicyError("HTTP retry policy must be an object");
224
- }
225
- if (Array.isArray(retry)) {
226
- throw createInvalidRetryPolicyError(
227
- "HTTP retry policy must be a plain object",
228
- );
229
- }
230
-
231
- validateRetryOptionsShape(retry);
232
- const base = createRetryOptions(
233
- retry.preset ?? HttpRetryPreset.TransportTransient,
234
- );
235
- const maxDelayMs = clampDelay(retry.maxDelayMs, base.maxDelayMs);
236
- const normalized: NormalizedRetryOptions = {
237
- preset: retry.preset ?? base.preset,
238
- attempts: clampPositiveInteger(
239
- retry.attempts,
240
- base.attempts,
241
- MAX_RETRY_ATTEMPTS,
242
- ),
243
- methods:
244
- retry.methods?.map((method) => method.toUpperCase()) ?? base.methods,
245
- statusCodes:
246
- retry.statusCodes?.filter((status) => Number.isInteger(status)) ??
247
- base.statusCodes,
248
- errorCodes: retry.errorCodes ?? base.errorCodes,
249
- delayStrategy: retry.delayStrategy ?? base.delayStrategy,
250
- baseDelayMs: clampDelay(retry.baseDelayMs, base.baseDelayMs),
251
- maxDelayMs,
252
- jitter: retry.jitter ?? base.jitter,
253
- retryAfter: retry.retryAfter ?? base.retryAfter,
254
- unsafeMethodPolicy: retry.unsafeMethodPolicy ?? base.unsafeMethodPolicy,
255
- };
256
-
257
- return normalized;
258
- }
259
-
260
- function validateRetryOptionsShape(retry: HttpRetryOptions): void {
261
- if (
262
- retry.preset !== undefined &&
263
- !hasOwnValue(HttpRetryPreset, retry.preset)
264
- ) {
265
- throw createInvalidRetryPolicyError(
266
- `Unknown HTTP retry preset: ${String(retry.preset)}`,
267
- );
268
- }
269
- if (
270
- retry.delayStrategy !== undefined &&
271
- !hasOwnValue(HttpRetryDelayStrategy, retry.delayStrategy)
272
- ) {
273
- throw createInvalidRetryPolicyError(
274
- `Unknown HTTP retry delay strategy: ${String(retry.delayStrategy)}`,
275
- );
276
- }
277
- if (
278
- retry.jitter !== undefined &&
279
- !hasOwnValue(HttpRetryJitter, retry.jitter)
280
- ) {
281
- throw createInvalidRetryPolicyError(
282
- `Unknown HTTP retry jitter policy: ${String(retry.jitter)}`,
283
- );
284
- }
285
- if (
286
- retry.retryAfter !== undefined &&
287
- !hasOwnValue(HttpRetryAfterPolicy, retry.retryAfter)
288
- ) {
289
- throw createInvalidRetryPolicyError(
290
- `Unknown HTTP retry-after policy: ${String(retry.retryAfter)}`,
291
- );
292
- }
293
- if (
294
- retry.unsafeMethodPolicy !== undefined &&
295
- !hasOwnValue(HttpRetryUnsafeMethodPolicy, retry.unsafeMethodPolicy)
296
- ) {
297
- throw createInvalidRetryPolicyError(
298
- `Unknown HTTP retry unsafe method policy: ${String(retry.unsafeMethodPolicy)}`,
299
- );
300
- }
301
- if (retry.methods !== undefined) {
302
- if (!Array.isArray(retry.methods)) {
303
- throw createInvalidRetryPolicyError(
304
- "HTTP retry methods must be an array",
305
- );
306
- }
307
- const nonStringMethods = retry.methods.filter(
308
- (method) => typeof method !== "string",
309
- );
310
- if (nonStringMethods.length > 0) {
311
- throw createInvalidRetryPolicyError(
312
- "HTTP retry methods must contain only strings",
313
- );
314
- }
315
- const unknownMethods = retry.methods
316
- .map((method) => method.toUpperCase())
317
- .filter((method) => !KNOWN_RETRY_METHODS.has(method));
318
- if (unknownMethods.length > 0) {
319
- throw createInvalidRetryPolicyError(
320
- `Unknown HTTP retry method(s): ${unknownMethods.join(", ")}`,
321
- );
322
- }
323
- }
324
- if (retry.statusCodes !== undefined) {
325
- if (!Array.isArray(retry.statusCodes)) {
326
- throw createInvalidRetryPolicyError(
327
- "HTTP retry statusCodes must be an array",
328
- );
329
- }
330
- const invalidStatusCodes = retry.statusCodes.filter(
331
- (status) =>
332
- !Number.isInteger(status) ||
333
- Number(status) < 100 ||
334
- Number(status) > 599,
335
- );
336
- if (invalidStatusCodes.length > 0) {
337
- throw createInvalidRetryPolicyError(
338
- "HTTP retry statusCodes must contain HTTP status integers in [100, 599]",
339
- );
340
- }
341
- }
342
- if (retry.errorCodes !== undefined) {
343
- if (!Array.isArray(retry.errorCodes)) {
344
- throw createInvalidRetryPolicyError(
345
- "HTTP retry errorCodes must be an array",
346
- );
347
- }
348
- const nonStringErrorCodes = retry.errorCodes.filter(
349
- (errorCode) => typeof errorCode !== "string",
350
- );
351
- if (nonStringErrorCodes.length > 0) {
352
- throw createInvalidRetryPolicyError(
353
- "HTTP retry errorCodes must contain only strings",
354
- );
355
- }
356
- }
357
- if (
358
- retry.preset === HttpRetryPreset.Off &&
359
- ((retry.attempts !== undefined && retry.attempts > 1) ||
360
- (retry.statusCodes !== undefined && retry.statusCodes.length > 0))
361
- ) {
362
- throw createInvalidRetryPolicyError(
363
- "HTTP retry preset off cannot be combined with retry-enabling overrides",
364
- );
365
- }
366
- }
367
-
368
- function validateUnsafeRetryMethods(options: NormalizedRetryOptions): void {
369
- if (
370
- options.unsafeMethodPolicy ===
371
- HttpRetryUnsafeMethodPolicy.AllowExplicitUnsafe
372
- ) {
373
- return;
374
- }
375
- const unsafeMethods = options.methods.filter((method) =>
376
- UNSAFE_RETRY_METHODS.has(method.toUpperCase()),
377
- );
378
- if (unsafeMethods.length === 0) return;
379
-
380
- throw new ProviderError(
381
- `HTTP retry methods include unsafe method(s): ${unsafeMethods.join(", ")}`,
382
- { code: "retry_unsafe_method" },
383
- );
384
- }
385
-
386
- function isMethodRetryable(
387
- method: HttpMethod,
388
- options: NormalizedRetryOptions,
389
- ): boolean {
390
- return options.methods
391
- .map((allowedMethod) => allowedMethod.toUpperCase())
392
- .includes(method.toUpperCase());
393
- }
394
-
395
- function retryErrorCode(error: unknown): string | undefined {
396
- if (error instanceof TransportError) {
397
- return error.code;
398
- }
399
- if (error && typeof error === "object" && "code" in error) {
400
- const code = Reflect.get(error, "code");
401
- return typeof code === "string" ? code : undefined;
402
- }
403
- return undefined;
404
- }
405
-
406
- function retryErrorStatus(error: unknown): number | undefined {
407
- if (error instanceof TransportError) {
408
- return error.status ?? error.upstreamStatus;
409
- }
410
- return undefined;
411
- }
412
-
413
- function shouldRetryTransportError(
414
- error: unknown,
415
- options: NormalizedRetryOptions,
416
- ): boolean {
417
- const code = retryErrorCode(error);
418
- return Boolean(code && options.errorCodes.includes(code));
419
- }
420
-
421
- function retryAfterHeader(headers: Record<string, string>): string | undefined {
422
- for (const [name, value] of Object.entries(headers)) {
423
- if (name.toLowerCase() === "retry-after") return value;
424
- }
425
- return undefined;
426
- }
427
-
428
- function parseRetryAfterMs(
429
- headers: Record<string, string>,
430
- now: number = Date.now(),
431
- ): number | undefined {
432
- const value = retryAfterHeader(headers);
433
- if (!value) return undefined;
434
- const seconds = Number(value);
435
- if (Number.isFinite(seconds)) {
436
- return Math.max(0, Math.floor(seconds * 1_000));
437
- }
438
- const dateMs = Date.parse(value);
439
- if (!Number.isNaN(dateMs)) {
440
- return Math.max(0, dateMs - now);
441
- }
442
- return undefined;
443
- }
444
-
445
- function computeRetryDelayMs(
446
- options: NormalizedRetryOptions,
447
- attemptIndex: number,
448
- headers?: Record<string, string>,
449
- ): number {
450
- const multiplier =
451
- options.delayStrategy === HttpRetryDelayStrategy.Exponential
452
- ? 2 ** Math.max(0, attemptIndex - 1)
453
- : 1;
454
- const configuredDelay = Math.min(
455
- options.baseDelayMs * multiplier,
456
- options.maxDelayMs,
457
- );
458
- const retryAfterMs =
459
- options.retryAfter === HttpRetryAfterPolicy.Ignore
460
- ? undefined
461
- : headers
462
- ? parseRetryAfterMs(headers)
463
- : undefined;
464
- if (retryAfterMs !== undefined) {
465
- const boundedRetryAfterMs = Math.min(retryAfterMs, options.maxDelayMs);
466
- if (options.retryAfter === HttpRetryAfterPolicy.Cap) {
467
- return Math.min(boundedRetryAfterMs, configuredDelay);
468
- }
469
- return boundedRetryAfterMs;
470
- }
471
-
472
- switch (options.jitter) {
473
- case HttpRetryJitter.None:
474
- return configuredDelay;
475
- case HttpRetryJitter.Equal:
476
- return Math.floor(
477
- configuredDelay / 2 + Math.random() * (configuredDelay / 2),
478
- );
479
- case HttpRetryJitter.Full:
480
- return Math.floor(Math.random() * configuredDelay);
481
- }
482
- }
483
-
484
54
  async function sleep(ms: number): Promise<void> {
485
55
  if (ms <= 0) return;
486
56
  await new Promise((resolve) => setTimeout(resolve, ms));
@@ -505,9 +75,7 @@ function withClientHeaders(
505
75
  body: unknown,
506
76
  ): RequestOptions {
507
77
  const headers: Record<string, string> = {
508
- ...(clientOptions.userAgent
509
- ? { "User-Agent": clientOptions.userAgent }
510
- : {}),
78
+ ...(clientOptions.userAgent ? { "User-Agent": clientOptions.userAgent } : {}),
511
79
  ...options?.headers,
512
80
  };
513
81
 
@@ -522,10 +90,7 @@ function withClientHeaders(
522
90
  }
523
91
 
524
92
  function parseHttpData(body: string, headers: Record<string, string>): unknown {
525
- const contentType =
526
- headers["content-type"] ??
527
- headers["Content-Type"] ??
528
- headers["CONTENT-TYPE"];
93
+ const contentType = headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
529
94
 
530
95
  if (contentType?.includes("application/json")) {
531
96
  return body ? JSON.parse(body) : null;
@@ -596,23 +161,14 @@ async function toNativeHttpResponse(response: Response): Promise<HttpResponse> {
596
161
  headers,
597
162
  json: async <T = unknown>() => {
598
163
  const contentType =
599
- headers["content-type"] ??
600
- headers["Content-Type"] ??
601
- headers["CONTENT-TYPE"];
602
- return parseJson<T>(
603
- contentType?.includes("application/json") && !rawText
604
- ? "null"
605
- : rawText,
606
- );
164
+ headers["content-type"] ?? headers["Content-Type"] ?? headers["CONTENT-TYPE"];
165
+ return parseJson<T>(contentType?.includes("application/json") && !rawText ? "null" : rawText);
607
166
  },
608
167
  ok: response.status >= 200 && response.status < 300,
609
168
  status: response.status,
610
169
  text: async () => rawText,
611
170
  arrayBuffer: async () =>
612
- bodyBytes.buffer.slice(
613
- bodyBytes.byteOffset,
614
- bodyBytes.byteOffset + bodyBytes.byteLength,
615
- ),
171
+ bodyBytes.buffer.slice(bodyBytes.byteOffset, bodyBytes.byteOffset + bodyBytes.byteLength),
616
172
  bytes: async () => bodyBytes.slice(0),
617
173
  };
618
174
  }
@@ -625,9 +181,7 @@ async function drainNativeResponseBody(response: Response): Promise<void> {
625
181
  }
626
182
  }
627
183
 
628
- function requireNativeResponseBody(
629
- response: Response,
630
- ): ReadableStream<Uint8Array> {
184
+ function requireNativeResponseBody(response: Response): ReadableStream<Uint8Array> {
631
185
  if (!response.body) {
632
186
  throw new TransportError("Response body stream is unavailable", {
633
187
  code: "transport_stream_unavailable",
@@ -692,18 +246,16 @@ async function resolveNativeProxy(
692
246
  warn: (message: string) => void,
693
247
  proxyAttemptOffset = 0,
694
248
  ): Promise<string | undefined> {
695
- const baseProxyAttempt =
696
- clientOptions.proxyAttempt === undefined ||
697
- !Number.isFinite(clientOptions.proxyAttempt)
698
- ? 0
699
- : Math.max(0, Math.floor(clientOptions.proxyAttempt));
700
249
  const resolvedProxy = await resolveProxyConfigAsync({
701
250
  proxy: options.proxy ?? clientOptions.proxy,
702
251
  upstream: clientOptions.upstream,
703
252
  apifuseConfig: clientOptions.apifuseConfig,
704
253
  proxyPolicy: clientOptions.proxyPolicy,
705
254
  affinityKey: clientOptions.affinityKey,
706
- proxyAttempt: baseProxyAttempt + proxyAttemptOffset,
255
+ proxyAttempt: computeProxyAttemptIndex({
256
+ baseProxyAttempt: clientOptions.proxyAttempt,
257
+ retryAttemptOffset: proxyAttemptOffset,
258
+ }),
707
259
  telemetry: clientOptions.telemetry,
708
260
  });
709
261
  if (resolvedProxy.shouldWarn) {
@@ -723,9 +275,7 @@ function assertNoHttpTransportOverrides(options: RequestOptions): void {
723
275
  }
724
276
  }
725
277
 
726
- function normalizeNativeFetchBody(
727
- body: unknown,
728
- ): string | ArrayBuffer | undefined {
278
+ function normalizeNativeFetchBody(body: unknown): string | ArrayBuffer | undefined {
729
279
  const normalized = normalizeHttpRequestBody(body);
730
280
  if (!Buffer.isBuffer(normalized)) {
731
281
  return normalized;
@@ -745,10 +295,7 @@ async function fetchNativeHttp(
745
295
  statusRetryCodes?: readonly number[],
746
296
  proxyAttemptOffset = 0,
747
297
  ): Promise<NativeHttpAttemptOutcome> {
748
- const requestUrl = appendQueryParams(
749
- resolveHttpUrl(baseUrl, url),
750
- options.params,
751
- );
298
+ const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
752
299
  const controller = options.timeout ? new AbortController() : undefined;
753
300
  const timeoutHandle = options.timeout
754
301
  ? setTimeout(() => controller?.abort(), options.timeout)
@@ -756,12 +303,7 @@ async function fetchNativeHttp(
756
303
 
757
304
  let proxy: string | undefined;
758
305
  try {
759
- proxy = await resolveNativeProxy(
760
- options,
761
- clientOptions,
762
- warn,
763
- proxyAttemptOffset,
764
- );
306
+ proxy = await resolveNativeProxy(options, clientOptions, warn, proxyAttemptOffset);
765
307
  const requestInit: NativeFetchInit = {
766
308
  headers: options.headers,
767
309
  method,
@@ -789,13 +331,10 @@ async function fetchNativeHttp(
789
331
 
790
332
  if (response.status >= 400 && options.throwOnHttpError !== false) {
791
333
  await drainNativeResponseBody(response);
792
- throw new TransportError(
793
- `Upstream request failed with status ${response.status}`,
794
- {
795
- code: "upstream_http_error",
796
- status: response.status,
797
- },
798
- );
334
+ throw new TransportError(`Upstream request failed with status ${response.status}`, {
335
+ code: "upstream_http_error",
336
+ status: response.status,
337
+ });
799
338
  }
800
339
 
801
340
  return toNativeHttpResponse(response);
@@ -819,10 +358,7 @@ async function fetchNativeHttpStream(
819
358
  clientOptions: HttpClientOptions,
820
359
  warn: (message: string) => void,
821
360
  ): Promise<HttpStreamResponse> {
822
- const requestUrl = appendQueryParams(
823
- resolveHttpUrl(baseUrl, url),
824
- options.params,
825
- );
361
+ const requestUrl = appendQueryParams(resolveHttpUrl(baseUrl, url), options.params);
826
362
  const controller = options.timeout ? new AbortController() : undefined;
827
363
  const timeoutHandle = options.timeout
828
364
  ? setTimeout(() => controller?.abort(), options.timeout)
@@ -845,13 +381,10 @@ async function fetchNativeHttpStream(
845
381
 
846
382
  if (response.status >= 400 && options.throwOnHttpError !== false) {
847
383
  await drainNativeResponseBody(response);
848
- throw new TransportError(
849
- `Upstream request failed with status ${response.status}`,
850
- {
851
- code: "upstream_http_error",
852
- status: response.status,
853
- },
854
- );
384
+ throw new TransportError(`Upstream request failed with status ${response.status}`, {
385
+ code: "upstream_http_error",
386
+ status: response.status,
387
+ });
855
388
  }
856
389
 
857
390
  return toNativeHttpStreamResponse(response);
@@ -891,23 +424,19 @@ export function createHttpClient(
891
424
  );
892
425
  }
893
426
  assertNoHttpTransportOverrides(options);
894
- const headersOptions = withClientHeaders(
895
- options,
896
- clientOptions,
897
- options.body,
898
- );
427
+ const headersOptions = withClientHeaders(options, clientOptions, options.body);
899
428
  const methodName = normalizeHttpMethod(method);
900
429
  const explicitRetry = headersOptions.retry !== undefined;
901
430
  const retryOptions =
902
- normalizeRetryOptions(headersOptions.retry) ??
903
- (explicitRetry
904
- ? undefined
905
- : createRetryOptions(HttpRetryPreset.TransportTransient));
906
- if (retryOptions) validateUnsafeRetryMethods(retryOptions);
431
+ normalizeProxyTransportRetryOptions(headersOptions.retry, {
432
+ label: "HTTP",
433
+ }) ??
434
+ (explicitRetry ? undefined : createDefaultProxyTransportRetryOptions({ label: "HTTP" }));
435
+ if (retryOptions) validateUnsafeProxyTransportRetryMethods(retryOptions, "HTTP");
907
436
  const retryEnabled = Boolean(
908
437
  retryOptions &&
909
438
  retryOptions.attempts > 1 &&
910
- isMethodRetryable(methodName, retryOptions),
439
+ isProxyTransportRetryMethod(methodName, retryOptions),
911
440
  );
912
441
  const statusRetryEnabled = Boolean(
913
442
  retryEnabled &&
@@ -916,14 +445,11 @@ export function createHttpClient(
916
445
  retryOptions.statusCodes.length > 0 &&
917
446
  headersOptions.throwOnHttpError !== false,
918
447
  );
919
- const attemptOptions: RequestOptions & { body?: unknown } =
920
- statusRetryEnabled
921
- ? { ...headersOptions, throwOnHttpError: false }
922
- : headersOptions;
448
+ const attemptOptions: RequestOptions & { body?: unknown } = statusRetryEnabled
449
+ ? { ...headersOptions, throwOnHttpError: false }
450
+ : headersOptions;
923
451
 
924
- const executeOnce = (
925
- proxyAttemptOffset = 0,
926
- ): Promise<NativeHttpAttemptOutcome> =>
452
+ const executeOnce = (proxyAttemptOffset = 0): Promise<NativeHttpAttemptOutcome> =>
927
453
  fetchNativeHttp(
928
454
  baseUrl,
929
455
  url,
@@ -951,19 +477,14 @@ export function createHttpClient(
951
477
  if (isHttpStatusOutcome(outcome)) {
952
478
  lastStatus = outcome.status;
953
479
  if (outcome.retryable && attempt < retryOptions.attempts) {
954
- await sleep(
955
- computeRetryDelayMs(retryOptions, attempt, outcome.headers),
956
- );
480
+ await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt, outcome.headers));
957
481
  continue;
958
482
  }
959
483
  throw toUpstreamHttpError(outcome.status);
960
484
  }
961
485
 
962
486
  const response = outcome;
963
- if (
964
- response.status >= 400 &&
965
- headersOptions.throwOnHttpError !== false
966
- ) {
487
+ if (response.status >= 400 && headersOptions.throwOnHttpError !== false) {
967
488
  throw toUpstreamHttpError(response.status);
968
489
  }
969
490
 
@@ -980,15 +501,20 @@ export function createHttpClient(
980
501
  }
981
502
  return response;
982
503
  } catch (error) {
983
- lastErrorCode = retryErrorCode(error);
984
- lastStatus = retryErrorStatus(error);
504
+ lastErrorCode = proxyTransportRetryErrorCode(error);
505
+ lastStatus = proxyTransportRetryErrorStatus(error);
985
506
  const proxyUsed = Boolean((error as NativeHttpAttemptError).proxyUsed);
986
507
  if (
987
508
  attempt < retryOptions.attempts &&
988
- (explicitRetry || proxyUsed) &&
989
- shouldRetryTransportError(error, retryOptions)
509
+ shouldRetryProxyTransportAttempt({
510
+ error,
511
+ explicitRetry,
512
+ method: methodName,
513
+ options: retryOptions,
514
+ proxyUsed,
515
+ })
990
516
  ) {
991
- await sleep(computeRetryDelayMs(retryOptions, attempt));
517
+ await sleep(computeProxyTransportRetryDelayMs(retryOptions, attempt));
992
518
  continue;
993
519
  }
994
520
  throw error;
@@ -1012,30 +538,17 @@ export function createHttpClient(
1012
538
  );
1013
539
  }
1014
540
  assertNoHttpTransportOverrides(options);
1015
- const headersOptions = withClientHeaders(
1016
- options,
1017
- clientOptions,
1018
- options.body,
1019
- );
541
+ const headersOptions = withClientHeaders(options, clientOptions, options.body);
1020
542
  const methodName = normalizeHttpMethod(method);
1021
- return fetchNativeHttpStream(
1022
- baseUrl,
1023
- url,
1024
- methodName,
1025
- headersOptions,
1026
- clientOptions,
1027
- warnOnce,
1028
- );
543
+ return fetchNativeHttpStream(baseUrl, url, methodName, headersOptions, clientOptions, warnOnce);
1029
544
  }
1030
545
 
1031
546
  return {
1032
547
  request: async (url, options: RequestWithMethodOptions = {}) =>
1033
548
  request(url, options.method ?? "GET", options),
1034
549
  get: async (url, options) => request(url, "GET", options),
1035
- post: async (url, body, options) =>
1036
- request(url, "POST", { ...options, body }),
1037
- put: async (url, body, options) =>
1038
- request(url, "PUT", { ...options, body }),
550
+ post: async (url, body, options) => request(url, "POST", { ...options, body }),
551
+ put: async (url, body, options) => request(url, "PUT", { ...options, body }),
1039
552
  delete: async (url, options) => request(url, "DELETE", options),
1040
553
  stream: async (url, options: RequestWithMethodOptions = {}) =>
1041
554
  streamRequest(url, options.method ?? "GET", options),