@nosana/node 1.1.35 → 1.1.37-rc

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosana/node",
3
- "version": "1.1.35",
3
+ "version": "1.1.37-rc",
4
4
  "description": "",
5
5
  "bin": {
6
6
  "nosana-node": "./dist/src/index.js"
@@ -1,6 +1,7 @@
1
1
  import createClient from 'openapi-fetch';
2
2
  import { getSDK } from '../../sdk/index.js';
3
3
  import { configs } from '../../configs/configs.js';
4
+ import { retryFetch } from '../retryFetch.js';
4
5
  import { maintenanceMiddleware } from '../maintenanceMiddleware.js';
5
6
  let instance = undefined;
6
7
  export const hostManagerClientSelector = () => {
@@ -9,6 +10,7 @@ export const hostManagerClientSelector = () => {
9
10
  const address = sdk.solana.provider.wallet.publicKey.toString();
10
11
  instance = createClient({
11
12
  baseUrl: configs().hostManagerBaseUrl,
13
+ fetch: retryFetch,
12
14
  headers: {
13
15
  'Content-Type': 'application/json',
14
16
  },
@@ -0,0 +1,7 @@
1
+ /**
2
+ * Custom fetch for openapi-fetch clients that retries network-level failures
3
+ * (errors thrown by fetch itself: DNS, connection resets, timeouts) with
4
+ * exponential backoff. HTTP error responses are returned untouched — those
5
+ * are handled by the callers and the maintenance middleware.
6
+ */
7
+ export declare const retryFetch: (input: Request) => Promise<Response>;
@@ -0,0 +1,29 @@
1
+ import chalk from 'chalk';
2
+ import { sleep } from '../utils/utils.js';
3
+ import { HostManagerUnreachableError, describeNetworkError, } from '../errors/HostManagerUnreachableError.js';
4
+ const MAX_ATTEMPTS = 6;
5
+ const BASE_DELAY_S = 1;
6
+ /**
7
+ * Custom fetch for openapi-fetch clients that retries network-level failures
8
+ * (errors thrown by fetch itself: DNS, connection resets, timeouts) with
9
+ * exponential backoff. HTTP error responses are returned untouched — those
10
+ * are handled by the callers and the maintenance middleware.
11
+ */
12
+ export const retryFetch = async (input) => {
13
+ let lastError;
14
+ for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) {
15
+ try {
16
+ // Fetch a clone so the body is still readable on the next attempt.
17
+ return await fetch(input.clone());
18
+ }
19
+ catch (error) {
20
+ lastError = error;
21
+ if (attempt < MAX_ATTEMPTS) {
22
+ const delayS = BASE_DELAY_S * 2 ** (attempt - 1);
23
+ console.warn(chalk.yellow(`Request to the host manager failed (${describeNetworkError(error)}), retrying in ${delayS}s (attempt ${attempt}/${MAX_ATTEMPTS})`));
24
+ await sleep(delayS);
25
+ }
26
+ }
27
+ }
28
+ throw new HostManagerUnreachableError(MAX_ATTEMPTS, lastError);
29
+ };
@@ -0,0 +1,10 @@
1
+ export declare class HostManagerUnreachableError extends Error {
2
+ readonly cause: unknown;
3
+ constructor(attempts: number, cause: unknown);
4
+ }
5
+ /**
6
+ * Undici reports every network failure as `TypeError: fetch failed` and hides
7
+ * the actual reason (ECONNRESET, EAI_AGAIN, ...) in the error's cause chain,
8
+ * so surface it in the message.
9
+ */
10
+ export declare function describeNetworkError(error: unknown): string;
@@ -0,0 +1,30 @@
1
+ export class HostManagerUnreachableError extends Error {
2
+ constructor(attempts, cause) {
3
+ super(`Could not reach the host manager after ${attempts} attempts: ${describeNetworkError(cause)}. Check your internet connection and firewall, then restart the node.`);
4
+ this.name = 'HostManagerUnreachableError';
5
+ this.cause = cause;
6
+ }
7
+ }
8
+ /**
9
+ * Undici reports every network failure as `TypeError: fetch failed` and hides
10
+ * the actual reason (ECONNRESET, EAI_AGAIN, ...) in the error's cause chain,
11
+ * so surface it in the message.
12
+ */
13
+ export function describeNetworkError(error) {
14
+ if (!(error instanceof Error))
15
+ return String(error);
16
+ const cause = error.cause;
17
+ if (cause instanceof AggregateError && cause.errors.length > 0) {
18
+ return `${error.message} (${cause.errors.map(describeSingle).join('; ')})`;
19
+ }
20
+ if (cause instanceof Error) {
21
+ return `${error.message} (${describeSingle(cause)})`;
22
+ }
23
+ return describeSingle(error);
24
+ }
25
+ function describeSingle(error) {
26
+ const code = error.code;
27
+ return code && !error.message.includes(code)
28
+ ? `${code}: ${error.message}`
29
+ : error.message;
30
+ }
@@ -57,7 +57,7 @@ export class Benchmark {
57
57
  const flowResults = this.repository.getFlow(this.benchmarkId);
58
58
  if (!flowResults)
59
59
  throw new Error(`Cannot find results for flow with id ${this.benchmarkId}`);
60
- this.repository.deleteflow(this.benchmarkId);
61
60
  await HostManager.submitBenchmarkResults(this.benchmarkId, flowResults.state);
61
+ this.repository.deleteflow(this.benchmarkId);
62
62
  }
63
63
  }
@@ -1,2 +1,2 @@
1
- export declare const s3HelperImage = "registry.hub.docker.com/nosana/resource-manager:v1.1.12";
1
+ export declare const s3HelperImage = "registry.hub.docker.com/nosana/resource-manager:v1.1.14";
2
2
  export declare const nosanaBucket = "https://models.nosana.io";
@@ -1,2 +1,2 @@
1
- export const s3HelperImage = 'registry.hub.docker.com/nosana/resource-manager:v1.1.12';
1
+ export const s3HelperImage = 'registry.hub.docker.com/nosana/resource-manager:v1.1.14';
2
2
  export const nosanaBucket = 'https://models.nosana.io';
@@ -28,10 +28,14 @@ export declare function findReportedErrors(logs: {
28
28
  log?: string;
29
29
  }[]): string[];
30
30
  /**
31
- * A resource rendered for an error message. Resources carry credentials (`IAM`,
32
- * HF `accessToken`), and errors reach the logs and the support channel, so
33
- * those fields are replaced rather than reported.
31
+ * The fallback reason for a container that died without reporting through the
32
+ * envelope — a crash before the handlers install, or output lost to the kill.
33
+ * The raw stderr tail is the only account left of what happened, so the last
34
+ * few lines ride along rather than being discarded.
34
35
  */
36
+ export declare function describeUnreportedExit(logs: {
37
+ log?: string;
38
+ }[], statusCode: number): string;
35
39
  /**
36
40
  * The op a download is running for. Passed only for job resources — a market
37
41
  * preload has no op state to write errors to.
@@ -65,6 +65,26 @@ export function findReportedErrors(logs) {
65
65
  }
66
66
  return messages;
67
67
  }
68
+ /**
69
+ * The fallback reason for a container that died without reporting through the
70
+ * envelope — a crash before the handlers install, or output lost to the kill.
71
+ * The raw stderr tail is the only account left of what happened, so the last
72
+ * few lines ride along rather than being discarded.
73
+ */
74
+ export function describeUnreportedExit(logs, statusCode) {
75
+ const tail = logs
76
+ .map((entry) => entry.log?.trim())
77
+ .filter((line) => !!line)
78
+ .slice(-3)
79
+ .join(' | ')
80
+ .slice(-400);
81
+ return `no reason reported (exit code ${statusCode})${tail ? `; stderr tail: ${tail}` : ''}`;
82
+ }
83
+ /**
84
+ * A resource rendered for an error message. Resources carry credentials (`IAM`,
85
+ * HF `accessToken`), and errors reach the logs and the support channel, so
86
+ * those fields are replaced rather than reported.
87
+ */
68
88
  function describeResource(resource) {
69
89
  return JSON.stringify(resource, (key, value) => key === 'IAM' || key === 'accessToken' ? '[hidden]' : value);
70
90
  }
@@ -242,7 +262,7 @@ export class VolumeManager {
242
262
  // managed to report a reason, so this must throw either way: returning
243
263
  // normally here reports success for an unfinished volume, and the run
244
264
  // then fails later with the cause lost.
245
- const reason = reported.at(-1) ?? `no reason reported (exit code ${StatusCode})`;
265
+ const reason = reported.at(-1) ?? describeUnreportedExit(logs, StatusCode);
246
266
  throw new Error(`resource download failed for ${name}: ${reason}${describeContainerState(state)}`);
247
267
  }
248
268
  }
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@nosana/node",
3
- "version": "1.1.35",
3
+ "version": "1.1.37-rc",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@nosana/node",
9
- "version": "1.1.35",
9
+ "version": "1.1.37-rc",
10
10
  "license": "ISC",
11
11
  "dependencies": {
12
12
  "@coral-xyz/anchor": "^0.28.1-beta.1",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nosana/node",
3
- "version": "1.1.35",
3
+ "version": "1.1.37-rc",
4
4
  "description": "",
5
5
  "bin": {
6
6
  "nosana-node": "./dist/src/index.js"