@noosphere/agent-core 0.1.0-alpha.0 → 0.1.0-alpha.10
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/README.md +143 -0
- package/dist/index.cjs +53 -47
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +53 -47
- package/dist/index.js.map +1 -1
- package/package.json +4 -4
package/README.md
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
# @noosphere/agent-core
|
|
2
|
+
|
|
3
|
+
Core modules for building Noosphere compute agents.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @noosphere/agent-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
For alpha version:
|
|
12
|
+
```bash
|
|
13
|
+
npm install @noosphere/agent-core@alpha
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Components
|
|
17
|
+
|
|
18
|
+
### NoosphereAgent
|
|
19
|
+
|
|
20
|
+
Main agent class that orchestrates compute request handling.
|
|
21
|
+
|
|
22
|
+
```typescript
|
|
23
|
+
import { NoosphereAgent } from '@noosphere/agent-core';
|
|
24
|
+
|
|
25
|
+
const agent = new NoosphereAgent({
|
|
26
|
+
rpcUrl: 'https://sepolia.hpp.io',
|
|
27
|
+
wsUrl: 'wss://sepolia.hpp.io',
|
|
28
|
+
routerAddress: '0x89c76ee71E9cC8D57BEE3d414478B630AE41fF43',
|
|
29
|
+
coordinatorAddress: '0x244D87a7CAe0D557C223C13a90Ae845e56430A50',
|
|
30
|
+
keystore,
|
|
31
|
+
containers: [
|
|
32
|
+
{
|
|
33
|
+
containerId: 'hello-world',
|
|
34
|
+
imageName: 'noosphere/hello-world:latest',
|
|
35
|
+
},
|
|
36
|
+
],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
// Event handlers
|
|
40
|
+
agent.on('requestStarted', (event) => {
|
|
41
|
+
console.log('Request started:', event.requestId);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
agent.on('computeDelivered', (event) => {
|
|
45
|
+
console.log('Compute delivered:', event.requestId);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Start agent
|
|
49
|
+
await agent.start();
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### EventMonitor
|
|
53
|
+
|
|
54
|
+
WebSocket-based blockchain event monitor with automatic reconnection.
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
import { EventMonitor } from '@noosphere/agent-core';
|
|
58
|
+
|
|
59
|
+
const monitor = new EventMonitor(
|
|
60
|
+
{
|
|
61
|
+
rpcUrl: 'https://sepolia.hpp.io',
|
|
62
|
+
wsUrl: 'wss://sepolia.hpp.io',
|
|
63
|
+
routerAddress: '0x89c76ee71E9cC8D57BEE3d414478B630AE41fF43',
|
|
64
|
+
coordinatorAddress: '0x244D87a7CAe0D557C223C13a90Ae845e56430A50',
|
|
65
|
+
},
|
|
66
|
+
routerAbi,
|
|
67
|
+
coordinatorAbi,
|
|
68
|
+
{ enableHeartbeat: true }
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
monitor.on('RequestStarted', (event) => {
|
|
72
|
+
console.log('New request:', event);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
await monitor.start();
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### ContainerManager
|
|
79
|
+
|
|
80
|
+
Docker container lifecycle management.
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
import { ContainerManager } from '@noosphere/agent-core';
|
|
84
|
+
|
|
85
|
+
const manager = new ContainerManager();
|
|
86
|
+
|
|
87
|
+
// Run container
|
|
88
|
+
const result = await manager.runContainer({
|
|
89
|
+
imageName: 'noosphere/hello-world:latest',
|
|
90
|
+
input: { message: 'Hello' },
|
|
91
|
+
timeout: 30000,
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
console.log('Output:', result.output);
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### SchedulerService
|
|
98
|
+
|
|
99
|
+
Subscription scheduling and interval management.
|
|
100
|
+
|
|
101
|
+
```typescript
|
|
102
|
+
import { SchedulerService } from '@noosphere/agent-core';
|
|
103
|
+
|
|
104
|
+
const scheduler = new SchedulerService(
|
|
105
|
+
coordinatorContract,
|
|
106
|
+
walletManager,
|
|
107
|
+
{ intervalCheckMs: 60000 }
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
scheduler.on('intervalPrepared', (event) => {
|
|
111
|
+
console.log('Interval prepared:', event);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
await scheduler.start(subscriptions);
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Re-exports
|
|
118
|
+
|
|
119
|
+
For convenience, this package re-exports from other Noosphere packages:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
// From @noosphere/crypto
|
|
123
|
+
import { KeystoreManager, WalletManager } from '@noosphere/agent-core';
|
|
124
|
+
|
|
125
|
+
// From @noosphere/registry
|
|
126
|
+
import { RegistryManager } from '@noosphere/agent-core';
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Types
|
|
130
|
+
|
|
131
|
+
```typescript
|
|
132
|
+
import type {
|
|
133
|
+
NoosphereAgentConfig,
|
|
134
|
+
ContainerConfig,
|
|
135
|
+
CheckpointData,
|
|
136
|
+
ComputeDeliveredEvent,
|
|
137
|
+
RequestStartedCallbackEvent,
|
|
138
|
+
} from '@noosphere/agent-core';
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## License
|
|
142
|
+
|
|
143
|
+
MIT
|
package/dist/index.cjs
CHANGED
|
@@ -308,7 +308,8 @@ var ContainerManager = class {
|
|
|
308
308
|
const startTime = Date.now();
|
|
309
309
|
try {
|
|
310
310
|
const port = container.port ? parseInt(container.port) : 8081;
|
|
311
|
-
const
|
|
311
|
+
const containerHost = process.env.DOCKER_NETWORK ? `noosphere-${container.name}` : "localhost";
|
|
312
|
+
const url = `http://${containerHost}:${port}/computation`;
|
|
312
313
|
let requestBody;
|
|
313
314
|
try {
|
|
314
315
|
const parsedInput = JSON.parse(input);
|
|
@@ -494,14 +495,19 @@ var ContainerManager = class {
|
|
|
494
495
|
console.log(`
|
|
495
496
|
\u{1F680} Preparing ${containers.size} containers...`);
|
|
496
497
|
const pullAndStartPromises = Array.from(containers.entries()).map(async ([id, container]) => {
|
|
498
|
+
const imageTag = `${container.image}:${container.tag || "latest"}`;
|
|
497
499
|
try {
|
|
498
|
-
const imageTag = `${container.image}:${container.tag || "latest"}`;
|
|
499
500
|
console.log(` Pulling ${imageTag}...`);
|
|
500
501
|
await this.pullImage(container.image, container.tag || "latest");
|
|
501
502
|
console.log(` \u2713 ${imageTag} ready`);
|
|
503
|
+
} catch (error) {
|
|
504
|
+
console.error(` \u274C Failed to pull ${imageTag}:`, error.message);
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
try {
|
|
502
508
|
await this.startPersistentContainer(id, container);
|
|
503
509
|
} catch (error) {
|
|
504
|
-
console.error(` \u274C Failed to
|
|
510
|
+
console.error(` \u274C Failed to start ${container.name || container.image}:`, error.message);
|
|
505
511
|
}
|
|
506
512
|
});
|
|
507
513
|
await Promise.all(pullAndStartPromises);
|
|
@@ -511,26 +517,29 @@ var ContainerManager = class {
|
|
|
511
517
|
* Start a persistent container that stays running
|
|
512
518
|
*/
|
|
513
519
|
async startPersistentContainer(containerId, metadata) {
|
|
514
|
-
const containerName = `noosphere-${
|
|
520
|
+
const containerName = `noosphere-${metadata.name}`;
|
|
515
521
|
const imageTag = `${metadata.image}:${metadata.tag || "latest"}`;
|
|
522
|
+
const existingContainer = this.docker.getContainer(containerName);
|
|
516
523
|
try {
|
|
517
|
-
const
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
} else {
|
|
524
|
+
const inspect = await existingContainer.inspect();
|
|
525
|
+
if (inspect.State.Running) {
|
|
526
|
+
console.log(` \u2713 Container ${containerName} already running`);
|
|
527
|
+
this.persistentContainers.set(containerId, existingContainer);
|
|
528
|
+
return;
|
|
529
|
+
} else {
|
|
530
|
+
try {
|
|
525
531
|
await existingContainer.start();
|
|
526
532
|
console.log(` \u2713 Started existing container ${containerName}`);
|
|
527
533
|
this.persistentContainers.set(containerId, existingContainer);
|
|
528
534
|
return;
|
|
535
|
+
} catch (startErr) {
|
|
536
|
+
console.log(` Removing stopped container ${containerName} to recreate...`);
|
|
537
|
+
await existingContainer.remove({ force: true });
|
|
529
538
|
}
|
|
530
|
-
} catch (err) {
|
|
531
539
|
}
|
|
532
540
|
} catch (err) {
|
|
533
541
|
}
|
|
542
|
+
const dockerNetwork = process.env.DOCKER_NETWORK;
|
|
534
543
|
const createOptions = {
|
|
535
544
|
name: containerName,
|
|
536
545
|
Image: imageTag,
|
|
@@ -541,9 +550,12 @@ var ContainerManager = class {
|
|
|
541
550
|
HostConfig: {
|
|
542
551
|
AutoRemove: false,
|
|
543
552
|
// Keep container for reuse
|
|
544
|
-
|
|
553
|
+
// Only bind ports to host when not using Docker network (local dev)
|
|
554
|
+
PortBindings: metadata.port && !dockerNetwork ? {
|
|
545
555
|
[`${metadata.port}/tcp`]: [{ HostPort: metadata.port }]
|
|
546
|
-
} : void 0
|
|
556
|
+
} : void 0,
|
|
557
|
+
// Join the specified Docker network for DinD communication
|
|
558
|
+
NetworkMode: dockerNetwork || void 0
|
|
547
559
|
},
|
|
548
560
|
Env: metadata.env ? Object.entries(metadata.env).map(([k, v]) => `${k}=${v}`) : void 0
|
|
549
561
|
};
|
|
@@ -1615,8 +1627,30 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1615
1627
|
verified: registryContainer.verified
|
|
1616
1628
|
};
|
|
1617
1629
|
}
|
|
1630
|
+
/**
|
|
1631
|
+
* Get container metadata from available sources
|
|
1632
|
+
* Returns undefined if container is not supported by this agent
|
|
1633
|
+
*
|
|
1634
|
+
* NOTE: Only checks config-defined sources (callback and containers map).
|
|
1635
|
+
* Registry is NOT used here - we only process containers explicitly configured.
|
|
1636
|
+
*/
|
|
1637
|
+
getContainerMetadata(containerId) {
|
|
1638
|
+
if (this.getContainer) {
|
|
1639
|
+
const container = this.getContainer(containerId);
|
|
1640
|
+
if (container) return container;
|
|
1641
|
+
}
|
|
1642
|
+
if (this.containers) {
|
|
1643
|
+
const container = this.containers.get(containerId);
|
|
1644
|
+
if (container) return container;
|
|
1645
|
+
}
|
|
1646
|
+
return void 0;
|
|
1647
|
+
}
|
|
1618
1648
|
async handleRequest(event) {
|
|
1619
1649
|
const requestIdShort = event.requestId.slice(0, 10);
|
|
1650
|
+
const container = this.getContainerMetadata(event.containerId);
|
|
1651
|
+
if (!container) {
|
|
1652
|
+
return;
|
|
1653
|
+
}
|
|
1620
1654
|
if (this.processingRequests.has(event.requestId)) {
|
|
1621
1655
|
console.log(` \u23ED\uFE0F Request ${requestIdShort}... already being processed, skipping duplicate`);
|
|
1622
1656
|
return;
|
|
@@ -1631,6 +1665,7 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1631
1665
|
console.log(` SubscriptionId: ${event.subscriptionId}`);
|
|
1632
1666
|
console.log(` Interval: ${event.interval}`);
|
|
1633
1667
|
console.log(` ContainerId: ${event.containerId.slice(0, 10)}...`);
|
|
1668
|
+
console.log(` \u{1F4E6} Container: ${container.name} (${container.image}:${container.tag || "latest"})`);
|
|
1634
1669
|
if (this.options.onRequestStarted) {
|
|
1635
1670
|
this.options.onRequestStarted({
|
|
1636
1671
|
requestId: event.requestId,
|
|
@@ -1661,6 +1696,7 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1661
1696
|
console.warn(` Could not verify interval currency:`, error.message);
|
|
1662
1697
|
}
|
|
1663
1698
|
this.scheduler.markIntervalCommitted(BigInt(event.subscriptionId), BigInt(event.interval));
|
|
1699
|
+
let sentTxHash;
|
|
1664
1700
|
try {
|
|
1665
1701
|
await this.waitForPriority(event);
|
|
1666
1702
|
if (this.options.onRequestProcessing) {
|
|
@@ -1675,37 +1711,6 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1675
1711
|
this.processingRequests.delete(event.requestId);
|
|
1676
1712
|
return;
|
|
1677
1713
|
}
|
|
1678
|
-
let container;
|
|
1679
|
-
if (this.getContainer) {
|
|
1680
|
-
container = this.getContainer(event.containerId);
|
|
1681
|
-
if (container) {
|
|
1682
|
-
console.log(` \u{1F4E6} Container found via callback: ${container.name}`);
|
|
1683
|
-
}
|
|
1684
|
-
}
|
|
1685
|
-
if (!container) {
|
|
1686
|
-
const registryContainer = this.registryManager.getContainer(event.containerId);
|
|
1687
|
-
if (registryContainer) {
|
|
1688
|
-
console.log(` \u{1F4CB} Container found in registry: ${registryContainer.name}`);
|
|
1689
|
-
container = this.convertRegistryContainer(registryContainer);
|
|
1690
|
-
}
|
|
1691
|
-
}
|
|
1692
|
-
if (!container && this.containers) {
|
|
1693
|
-
container = this.containers.get(event.containerId);
|
|
1694
|
-
if (container) {
|
|
1695
|
-
console.log(` \u{1F4E6} Container found in config: ${container.name}`);
|
|
1696
|
-
}
|
|
1697
|
-
}
|
|
1698
|
-
if (!container) {
|
|
1699
|
-
console.error(` \u274C Container not found: ${event.containerId}`);
|
|
1700
|
-
console.error(` \u{1F4A1} Try adding it to the registry or config file`);
|
|
1701
|
-
if (this.options.onRequestSkipped) {
|
|
1702
|
-
this.options.onRequestSkipped(event.requestId, `Container not found: ${event.containerId}`);
|
|
1703
|
-
}
|
|
1704
|
-
return;
|
|
1705
|
-
}
|
|
1706
|
-
console.log(
|
|
1707
|
-
` \u{1F4E6} Using container: ${container.name} (${container.image}:${container.tag || "latest"})`
|
|
1708
|
-
);
|
|
1709
1714
|
const subscription = await this.router.getComputeSubscription(event.subscriptionId);
|
|
1710
1715
|
const clientAddress = subscription.client;
|
|
1711
1716
|
if (!clientAddress || clientAddress === "0x0000000000000000000000000000000000000000") {
|
|
@@ -1773,6 +1778,7 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1773
1778
|
commitmentData,
|
|
1774
1779
|
nodeWallet
|
|
1775
1780
|
);
|
|
1781
|
+
sentTxHash = tx.hash;
|
|
1776
1782
|
console.log(` \u{1F4E4} Transaction sent: ${tx.hash}`);
|
|
1777
1783
|
const receipt = await tx.wait();
|
|
1778
1784
|
if (receipt.status === 1) {
|
|
@@ -1807,7 +1813,7 @@ var NoosphereAgent = class _NoosphereAgent {
|
|
|
1807
1813
|
}
|
|
1808
1814
|
console.error(` \u274C Error processing request:`, error);
|
|
1809
1815
|
if (this.options.onRequestFailed) {
|
|
1810
|
-
this.options.onRequestFailed(event.requestId, errorMessage);
|
|
1816
|
+
this.options.onRequestFailed(event.requestId, errorMessage, sentTxHash);
|
|
1811
1817
|
}
|
|
1812
1818
|
} finally {
|
|
1813
1819
|
this.processingRequests.delete(event.requestId);
|