From 0b13b3afb3212315028ba83b18bda7804387d9bd Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Wed, 8 Apr 2026 15:19:23 +0200 Subject: [PATCH 1/3] fix(http-client): enable got retry for transient server errors on POST requests (#SOLENG-333) Remove `throwHttpErrors: false` from the POST method so that got's built-in retry mechanism (5 retries, exponential backoff) can trigger for transient server errors (500, 502, 503, 504). Previously, got silently returned error responses without retrying because it did not see them as errors. After retries are exhausted, HTTPError is caught and converted to our custom error hierarchy for consistent handling. This fixes intermittent CUDNN_STATUS_INTERNAL_ERROR (500) responses from the Azure GPU backend that cannot be fixed server-side. Refs: SOLENG-333 Co-Authored-By: Claude Opus 4.6 (1M context) --- .../src/utils/http/http-client-got.ts | 55 +++++++++++++++---- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/packages/askui-nodejs/src/utils/http/http-client-got.ts b/packages/askui-nodejs/src/utils/http/http-client-got.ts index 5537439990..eb38d841e2 100644 --- a/packages/askui-nodejs/src/utils/http/http-client-got.ts +++ b/packages/askui-nodejs/src/utils/http/http-client-got.ts @@ -1,6 +1,7 @@ import got, { ExtendOptions, Got, + HTTPError, OptionsOfJSONResponseBody, RequestError, TimeoutError, @@ -57,6 +58,16 @@ export class HttpClientGot { this.askuiGot = got.extend(gotExtendOptions); } + /** + * Configures got with retry behavior for transient server errors. + * + * Got retries requests that fail with retryable status codes + * (500, 502, 503, 504, etc.) up to `limit` times using exponential backoff. + * + * POST requests are only retried if their URL is registered in `urlsToRetry` + * (see `shouldRetryOnError`), to avoid retrying non-idempotent calls + * to unknown endpoints. + */ private buildGotExtendOptions( proxyAgents?: { http: http.Agent; https: https.Agent } | undefined, ): ExtendOptions { @@ -142,6 +153,11 @@ export class HttpClientGot { return gotExtendOptions; } + /** + * Only retry POST requests if the URL is explicitly registered in `urlsToRetry` + * (populated by InferenceClient with inference endpoint URLs). + * Non-POST requests (GET, PUT, etc.) are always retried. + */ private shouldRetryOnError(error: TimeoutError | RequestError): boolean { return ( error.request?.options.method !== 'POST' @@ -187,22 +203,39 @@ export class HttpClientGot { url: string, data: Record, ): Promise<{ headers: http.IncomingHttpHeaders; body: T }> { + // Note: We intentionally do NOT set `throwHttpErrors: false` here. + // Got must throw on non-2xx responses so that its built-in retry mechanism + // (configured in buildGotExtendOptions) can kick in for transient server errors + // (500, 502, 503, 504). After all retries are exhausted, got throws an HTTPError + // which we catch below and convert into our custom error hierarchy. const options = this.injectHeadersAndCookies(url, { json: data, responseType: 'json', - throwHttpErrors: false, }); - const { body, statusCode, headers } = await this.askuiGot.post( - url, - options, - ); - if (headers['deprecation'] !== undefined) { - logger.warn(headers['deprecation']); - } - if (statusCode !== 200) { - throw httpClientErrorHandler(statusCode, JSON.stringify(body)); + try { + const { body, statusCode, headers } = await this.askuiGot.post( + url, + options, + ); + if (headers['deprecation'] !== undefined) { + logger.warn(headers['deprecation']); + } + if (statusCode !== 200) { + throw httpClientErrorHandler(statusCode, JSON.stringify(body)); + } + return { body, headers }; + } catch (error) { + // After got exhausts all retries, it throws HTTPError. + // Convert it to our custom error types (ServerHttpClientError, + // AuthenticationHttpClientError, etc.) for consistent error handling. + if (error instanceof HTTPError) { + throw httpClientErrorHandler( + error.response.statusCode, + error.response.body as string, + ); + } + throw error; } - return { body, headers }; } async get( From 8c7cb339914a1283d3bf1495ebc3f852c9e2c3f6 Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Fri, 10 Apr 2026 09:22:03 +0200 Subject: [PATCH 2/3] refactor(http-client): remove redundant statusCode check in post() (#SOLENG-333) Got now handles all non-2xx responses via HTTPError, making the manual statusCode !== 200 check unnecessary. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/askui-nodejs/src/utils/http/http-client-got.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/askui-nodejs/src/utils/http/http-client-got.ts b/packages/askui-nodejs/src/utils/http/http-client-got.ts index eb38d841e2..afc4a17b7e 100644 --- a/packages/askui-nodejs/src/utils/http/http-client-got.ts +++ b/packages/askui-nodejs/src/utils/http/http-client-got.ts @@ -213,16 +213,13 @@ export class HttpClientGot { responseType: 'json', }); try { - const { body, statusCode, headers } = await this.askuiGot.post( + const { body, headers } = await this.askuiGot.post( url, options, ); if (headers['deprecation'] !== undefined) { logger.warn(headers['deprecation']); } - if (statusCode !== 200) { - throw httpClientErrorHandler(statusCode, JSON.stringify(body)); - } return { body, headers }; } catch (error) { // After got exhausts all retries, it throws HTTPError. From 365be54bc890cd5a7c252197f7b841f0536cebe0 Mon Sep 17 00:00:00 2001 From: Dominik Klotz Date: Fri, 10 Apr 2026 09:29:39 +0200 Subject: [PATCH 3/3] fix(http-client): wrap unexpected errors in UnkownHttpClientError (#SOLENG-333) Wrap non-HTTPError exceptions (network errors, timeouts, etc.) in UnkownHttpClientError for consistent error handling across the HttpClientError hierarchy. Co-Authored-By: Claude Opus 4.6 (1M context) --- packages/askui-nodejs/src/utils/http/http-client-got.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/askui-nodejs/src/utils/http/http-client-got.ts b/packages/askui-nodejs/src/utils/http/http-client-got.ts index afc4a17b7e..23f2e41d02 100644 --- a/packages/askui-nodejs/src/utils/http/http-client-got.ts +++ b/packages/askui-nodejs/src/utils/http/http-client-got.ts @@ -12,6 +12,7 @@ import https from 'https'; import { logger } from '../../lib'; import { Credentials } from './credentials'; import { httpClientErrorHandler } from './custom-errors'; +import { UnkownHttpClientError } from './custom-errors/unkown-http-client-error'; function buildRetryLog( requestUrl: string | undefined, @@ -231,7 +232,9 @@ export class HttpClientGot { error.response.body as string, ); } - throw error; + throw new UnkownHttpClientError( + error instanceof Error ? error.message : String(error), + ); } }