@noosphere/agent-core 0.1.0-alpha.10 → 0.1.0-alpha.12
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.cjs +171 -46
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +47 -2
- package/dist/index.d.ts +47 -2
- package/dist/index.js +171 -46
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/dist/index.cjs
CHANGED
|
@@ -304,54 +304,69 @@ var ContainerManager = class {
|
|
|
304
304
|
this.containerPorts = /* @__PURE__ */ new Map();
|
|
305
305
|
this.docker = new import_dockerode.default();
|
|
306
306
|
}
|
|
307
|
-
async runContainer(container, input, timeout = 3e5) {
|
|
307
|
+
async runContainer(container, input, timeout = 3e5, connectionRetries = 5, connectionRetryDelayMs = 3e3) {
|
|
308
308
|
const startTime = Date.now();
|
|
309
|
+
const port = container.port ? parseInt(container.port) : 8081;
|
|
310
|
+
const containerHost = process.env.DOCKER_NETWORK ? `noosphere-${container.name}` : "localhost";
|
|
311
|
+
const url = `http://${containerHost}:${port}/computation`;
|
|
312
|
+
let requestBody;
|
|
309
313
|
try {
|
|
310
|
-
const
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
+
const parsedInput = JSON.parse(input);
|
|
315
|
+
requestBody = { input, ...parsedInput };
|
|
316
|
+
} catch {
|
|
317
|
+
requestBody = { input };
|
|
318
|
+
}
|
|
319
|
+
let lastError;
|
|
320
|
+
for (let attempt = 1; attempt <= connectionRetries; attempt++) {
|
|
314
321
|
try {
|
|
315
|
-
const
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
322
|
+
const response = await import_axios.default.post(url, requestBody, {
|
|
323
|
+
timeout,
|
|
324
|
+
headers: {
|
|
325
|
+
"Content-Type": "application/json"
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
const executionTime2 = Date.now() - startTime;
|
|
329
|
+
let output;
|
|
330
|
+
if (typeof response.data === "string") {
|
|
331
|
+
output = response.data;
|
|
332
|
+
} else if (response.data.output !== void 0) {
|
|
333
|
+
output = typeof response.data.output === "string" ? response.data.output : JSON.stringify(response.data.output);
|
|
334
|
+
} else {
|
|
335
|
+
output = JSON.stringify(response.data);
|
|
324
336
|
}
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
} catch (error) {
|
|
341
|
-
const executionTime = Date.now() - startTime;
|
|
342
|
-
if (error.response) {
|
|
343
|
-
throw new Error(
|
|
344
|
-
`Container HTTP error ${error.response.status}: ${JSON.stringify(error.response.data)}`
|
|
345
|
-
);
|
|
346
|
-
} else if (error.code === "ECONNREFUSED") {
|
|
347
|
-
throw new Error(
|
|
348
|
-
`Cannot connect to container (port ${container.port || 8081}). Is it running?`
|
|
349
|
-
);
|
|
350
|
-
} else if (error.code === "ETIMEDOUT" || error.code === "ECONNABORTED") {
|
|
351
|
-
throw new Error(`Container execution timeout after ${timeout}ms`);
|
|
337
|
+
return {
|
|
338
|
+
output,
|
|
339
|
+
exitCode: 0,
|
|
340
|
+
executionTime: executionTime2
|
|
341
|
+
};
|
|
342
|
+
} catch (error) {
|
|
343
|
+
lastError = error;
|
|
344
|
+
if (error.code === "ECONNREFUSED") {
|
|
345
|
+
if (attempt < connectionRetries) {
|
|
346
|
+
console.log(` \u23F3 Container not ready (attempt ${attempt}/${connectionRetries}), retrying in ${connectionRetryDelayMs / 1e3}s...`);
|
|
347
|
+
await this.sleep(connectionRetryDelayMs);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
break;
|
|
352
352
|
}
|
|
353
|
-
throw error;
|
|
354
353
|
}
|
|
354
|
+
const executionTime = Date.now() - startTime;
|
|
355
|
+
if (lastError.response) {
|
|
356
|
+
throw new Error(
|
|
357
|
+
`Container HTTP error ${lastError.response.status}: ${JSON.stringify(lastError.response.data)}`
|
|
358
|
+
);
|
|
359
|
+
} else if (lastError.code === "ECONNREFUSED") {
|
|
360
|
+
throw new Error(
|
|
361
|
+
`Cannot connect to container (port ${container.port || 8081}) after ${connectionRetries} attempts. Is it running?`
|
|
362
|
+
);
|
|
363
|
+
} else if (lastError.code === "ETIMEDOUT" || lastError.code === "ECONNABORTED") {
|
|
364
|
+
throw new Error(`Container execution timeout after ${timeout}ms`);
|
|
365
|
+
}
|
|
366
|
+
throw lastError;
|
|
367
|
+
}
|
|
368
|
+
sleep(ms) {
|
|
369
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
355
370
|
}
|
|
356
371
|
async collectContainerResult(dockerContainer, workDir, startTime) {
|
|
357
372
|
try {
|
|
@@ -797,7 +812,13 @@ var SchedulerService = class extends import_events2.EventEmitter {
|
|
|
797
812
|
try {
|
|
798
813
|
currentInterval = BigInt(await this.router.getComputeSubscriptionInterval(sub.subscriptionId));
|
|
799
814
|
} catch (error) {
|
|
800
|
-
|
|
815
|
+
const errorMessage = error.message || "";
|
|
816
|
+
console.warn(` Could not get interval from router for subscription ${subId}:`, errorMessage);
|
|
817
|
+
if (errorMessage.includes("SubscriptionNotFound")) {
|
|
818
|
+
console.log(` Subscription ${subId} not found (cancelled), untracking...`);
|
|
819
|
+
this.untrackSubscription(sub.subscriptionId);
|
|
820
|
+
continue;
|
|
821
|
+
}
|
|
801
822
|
const intervalsSinceActive = BigInt(currentBlockTime) - sub.activeAt;
|
|
802
823
|
currentInterval = intervalsSinceActive / sub.intervalSeconds + 1n;
|
|
803
824
|
}
|
|
@@ -1406,7 +1427,6 @@ var ConfigLoader = class {
|
|
|
1406
1427
|
|
|
1407
1428
|
// src/NoosphereAgent.ts
|
|
1408
1429
|
var NoosphereAgent = class _NoosphereAgent {
|
|
1409
|
-
// Deduplication: track requests being processed
|
|
1410
1430
|
constructor(options) {
|
|
1411
1431
|
this.options = options;
|
|
1412
1432
|
this.isRunning = false;
|
|
@@ -1464,6 +1484,12 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1464
1484
|
if (!this.getContainer && (!this.containers || this.containers.size === 0)) {
|
|
1465
1485
|
console.warn("\u26A0\uFE0F No container source provided. Agent will not be able to execute requests.");
|
|
1466
1486
|
}
|
|
1487
|
+
this.maxRetries = options.maxRetries ?? 3;
|
|
1488
|
+
this.retryIntervalMs = options.retryIntervalMs ?? 3e4;
|
|
1489
|
+
this.containerTimeout = options.containerConfig?.timeout ?? 3e5;
|
|
1490
|
+
this.containerConnectionRetries = options.containerConfig?.connectionRetries ?? 5;
|
|
1491
|
+
this.containerConnectionRetryDelayMs = options.containerConfig?.connectionRetryDelayMs ?? 3e3;
|
|
1492
|
+
this.healthCheckIntervalMs = options.healthCheckIntervalMs ?? 3e5;
|
|
1467
1493
|
}
|
|
1468
1494
|
/**
|
|
1469
1495
|
* Initialize NoosphereAgent from config.json (RECOMMENDED)
|
|
@@ -1602,10 +1628,100 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1602
1628
|
await this.handleRequest(data.requestStartedEvent);
|
|
1603
1629
|
}
|
|
1604
1630
|
});
|
|
1631
|
+
if (this.options.getRetryableEvents && this.options.resetEventForRetry) {
|
|
1632
|
+
this.startRetryTimer();
|
|
1633
|
+
}
|
|
1634
|
+
this.startHealthCheck();
|
|
1605
1635
|
this.isRunning = true;
|
|
1606
1636
|
console.log("\u2713 Noosphere Agent is running");
|
|
1607
1637
|
console.log("Listening for requests...");
|
|
1608
1638
|
}
|
|
1639
|
+
/**
|
|
1640
|
+
* Start the retry timer for failed requests
|
|
1641
|
+
*/
|
|
1642
|
+
startRetryTimer() {
|
|
1643
|
+
if (this.retryTimer) {
|
|
1644
|
+
clearInterval(this.retryTimer);
|
|
1645
|
+
}
|
|
1646
|
+
console.log(`\u{1F504} Retry mechanism enabled: max ${this.maxRetries} retries, check every ${this.retryIntervalMs / 1e3}s`);
|
|
1647
|
+
this.retryTimer = setInterval(async () => {
|
|
1648
|
+
await this.processRetries();
|
|
1649
|
+
}, this.retryIntervalMs);
|
|
1650
|
+
}
|
|
1651
|
+
/**
|
|
1652
|
+
* Start the health check timer for registry validation
|
|
1653
|
+
*/
|
|
1654
|
+
startHealthCheck() {
|
|
1655
|
+
if (this.healthCheckTimer) {
|
|
1656
|
+
clearInterval(this.healthCheckTimer);
|
|
1657
|
+
}
|
|
1658
|
+
console.log(`\u{1F3E5} Health check enabled: check every ${this.healthCheckIntervalMs / 1e3}s`);
|
|
1659
|
+
this.healthCheckTimer = setInterval(async () => {
|
|
1660
|
+
await this.performHealthCheck();
|
|
1661
|
+
}, this.healthCheckIntervalMs);
|
|
1662
|
+
}
|
|
1663
|
+
/**
|
|
1664
|
+
* Perform health check - verify registry has containers and reload if necessary
|
|
1665
|
+
*/
|
|
1666
|
+
async performHealthCheck() {
|
|
1667
|
+
const stats = this.registryManager.getStats();
|
|
1668
|
+
if (stats.totalContainers === 0) {
|
|
1669
|
+
console.warn("\u26A0\uFE0F Health check: 0 containers detected, attempting registry reload...");
|
|
1670
|
+
try {
|
|
1671
|
+
await this.registryManager.reload();
|
|
1672
|
+
const newStats = this.registryManager.getStats();
|
|
1673
|
+
if (newStats.totalContainers > 0) {
|
|
1674
|
+
console.log(`\u2713 Health check: Registry recovered - ${newStats.totalContainers} containers loaded`);
|
|
1675
|
+
} else {
|
|
1676
|
+
console.error("\u274C Health check: Registry reload failed - still 0 containers");
|
|
1677
|
+
}
|
|
1678
|
+
} catch (error) {
|
|
1679
|
+
console.error("\u274C Health check: Registry reload error:", error.message);
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
/**
|
|
1684
|
+
* Process retryable failed events (with throttling to avoid rate limits)
|
|
1685
|
+
*/
|
|
1686
|
+
async processRetries() {
|
|
1687
|
+
if (!this.options.getRetryableEvents || !this.options.resetEventForRetry) {
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
const retryableEvents = this.options.getRetryableEvents(this.maxRetries);
|
|
1691
|
+
if (retryableEvents.length === 0) {
|
|
1692
|
+
return;
|
|
1693
|
+
}
|
|
1694
|
+
const event = retryableEvents[0];
|
|
1695
|
+
if (this.processingRequests.has(event.requestId)) {
|
|
1696
|
+
return;
|
|
1697
|
+
}
|
|
1698
|
+
console.log(`\u{1F504} Retrying request ${event.requestId.slice(0, 10)}... (attempt ${event.retryCount + 1}/${this.maxRetries}, ${retryableEvents.length} remaining)`);
|
|
1699
|
+
this.options.resetEventForRetry(event.requestId);
|
|
1700
|
+
const container = this.getContainerMetadata(event.containerId);
|
|
1701
|
+
if (!container) {
|
|
1702
|
+
console.log(` \u26A0\uFE0F Container ${event.containerId.slice(0, 10)}... no longer supported, skipping retry`);
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1705
|
+
const retryEvent = {
|
|
1706
|
+
requestId: event.requestId,
|
|
1707
|
+
subscriptionId: BigInt(event.subscriptionId),
|
|
1708
|
+
interval: event.interval,
|
|
1709
|
+
containerId: event.containerId,
|
|
1710
|
+
redundancy: 1,
|
|
1711
|
+
useDeliveryInbox: false,
|
|
1712
|
+
feeAmount: BigInt(0),
|
|
1713
|
+
feeToken: "0x0000000000000000000000000000000000000000",
|
|
1714
|
+
walletAddress: "0x0000000000000000000000000000000000000000",
|
|
1715
|
+
verifier: "0x0000000000000000000000000000000000000000",
|
|
1716
|
+
coordinator: this.config.coordinatorAddress,
|
|
1717
|
+
blockNumber: 0
|
|
1718
|
+
};
|
|
1719
|
+
try {
|
|
1720
|
+
await this.handleRequest(retryEvent);
|
|
1721
|
+
} catch (error) {
|
|
1722
|
+
console.log(` \u274C Retry failed for ${event.requestId.slice(0, 10)}...: ${error.message}`);
|
|
1723
|
+
}
|
|
1724
|
+
}
|
|
1609
1725
|
/**
|
|
1610
1726
|
* Convert registry ContainerMetadata to agent-core ContainerMetadata
|
|
1611
1727
|
*/
|
|
@@ -1750,8 +1866,9 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1750
1866
|
const result = await this.containerManager.runContainer(
|
|
1751
1867
|
container,
|
|
1752
1868
|
inputData,
|
|
1753
|
-
|
|
1754
|
-
|
|
1869
|
+
this.containerTimeout,
|
|
1870
|
+
this.containerConnectionRetries,
|
|
1871
|
+
this.containerConnectionRetryDelayMs
|
|
1755
1872
|
);
|
|
1756
1873
|
if (result.exitCode !== 0) {
|
|
1757
1874
|
console.error(` \u274C Container execution failed with exit code ${result.exitCode}`);
|
|
@@ -1842,6 +1959,14 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1842
1959
|
}
|
|
1843
1960
|
async stop() {
|
|
1844
1961
|
console.log("Stopping Noosphere Agent...");
|
|
1962
|
+
if (this.retryTimer) {
|
|
1963
|
+
clearInterval(this.retryTimer);
|
|
1964
|
+
this.retryTimer = void 0;
|
|
1965
|
+
}
|
|
1966
|
+
if (this.healthCheckTimer) {
|
|
1967
|
+
clearInterval(this.healthCheckTimer);
|
|
1968
|
+
this.healthCheckTimer = void 0;
|
|
1969
|
+
}
|
|
1845
1970
|
await this.eventMonitor.stop();
|
|
1846
1971
|
this.scheduler.stop();
|
|
1847
1972
|
await this.containerManager.cleanup();
|