Skip to content

Commit e5b3d50

Browse files
feat(client): send retry count header (#1087)
1 parent 17ef084 commit e5b3d50

File tree

2 files changed

+37
-4
lines changed

2 files changed

+37
-4
lines changed

Diff for: src/core.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -308,7 +308,10 @@ export abstract class APIClient {
308308
return null;
309309
}
310310

311-
buildRequest<Req>(options: FinalRequestOptions<Req>): { req: RequestInit; url: string; timeout: number } {
311+
buildRequest<Req>(
312+
options: FinalRequestOptions<Req>,
313+
{ retryCount = 0 }: { retryCount?: number } = {},
314+
): { req: RequestInit; url: string; timeout: number } {
312315
const { method, path, query, headers: headers = {} } = options;
313316

314317
const body =
@@ -340,7 +343,7 @@ export abstract class APIClient {
340343
headers[this.idempotencyHeader] = options.idempotencyKey;
341344
}
342345

343-
const reqHeaders = this.buildHeaders({ options, headers, contentLength });
346+
const reqHeaders = this.buildHeaders({ options, headers, contentLength, retryCount });
344347

345348
const req: RequestInit = {
346349
method,
@@ -359,10 +362,12 @@ export abstract class APIClient {
359362
options,
360363
headers,
361364
contentLength,
365+
retryCount,
362366
}: {
363367
options: FinalRequestOptions;
364368
headers: Record<string, string | null | undefined>;
365369
contentLength: string | null | undefined;
370+
retryCount: number;
366371
}): Record<string, string> {
367372
const reqHeaders: Record<string, string> = {};
368373
if (contentLength) {
@@ -378,6 +383,8 @@ export abstract class APIClient {
378383
delete reqHeaders['content-type'];
379384
}
380385

386+
reqHeaders['x-stainless-retry-count'] = String(retryCount);
387+
381388
this.validateHeaders(reqHeaders, headers);
382389

383390
return reqHeaders;
@@ -429,13 +436,14 @@ export abstract class APIClient {
429436
retriesRemaining: number | null,
430437
): Promise<APIResponseProps> {
431438
const options = await optionsInput;
439+
const maxRetries = options.maxRetries ?? this.maxRetries;
432440
if (retriesRemaining == null) {
433-
retriesRemaining = options.maxRetries ?? this.maxRetries;
441+
retriesRemaining = maxRetries;
434442
}
435443

436444
await this.prepareOptions(options);
437445

438-
const { req, url, timeout } = this.buildRequest(options);
446+
const { req, url, timeout } = this.buildRequest(options, { retryCount: maxRetries - retriesRemaining });
439447

440448
await this.prepareRequest(req, { url, options });
441449

Diff for: tests/index.test.ts

+25
Original file line numberDiff line numberDiff line change
@@ -241,6 +241,31 @@ describe('retries', () => {
241241
expect(count).toEqual(3);
242242
});
243243

244+
test('retry count header', async () => {
245+
let count = 0;
246+
let capturedRequest: RequestInit | undefined;
247+
const testFetch = async (url: RequestInfo, init: RequestInit = {}): Promise<Response> => {
248+
count++;
249+
if (count <= 2) {
250+
return new Response(undefined, {
251+
status: 429,
252+
headers: {
253+
'Retry-After': '0.1',
254+
},
255+
});
256+
}
257+
capturedRequest = init;
258+
return new Response(JSON.stringify({ a: 1 }), { headers: { 'Content-Type': 'application/json' } });
259+
};
260+
261+
const client = new OpenAI({ apiKey: 'My API Key', fetch: testFetch, maxRetries: 4 });
262+
263+
expect(await client.request({ path: '/foo', method: 'get' })).toEqual({ a: 1 });
264+
265+
expect((capturedRequest!.headers as Headers)['x-stainless-retry-count']).toEqual('2');
266+
expect(count).toEqual(3);
267+
});
268+
244269
test('retry on 429 with retry-after', async () => {
245270
let count = 0;
246271
const testFetch = async (url: RequestInfo, { signal }: RequestInit = {}): Promise<Response> => {

0 commit comments

Comments
 (0)