technocore-compute 0.1.0
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/LICENSE +21 -0
- package/README.md +156 -0
- package/dist/cli.d.ts +11 -0
- package/dist/cli.js +114 -0
- package/dist/exchange.d.ts +76 -0
- package/dist/exchange.js +262 -0
- package/dist/index.d.ts +55 -0
- package/dist/index.js +52 -0
- package/dist/server.d.ts +26 -0
- package/dist/server.js +205 -0
- package/dist/types.d.ts +110 -0
- package/dist/types.js +7 -0
- package/package.json +40 -0
- package/src/cli.ts +120 -0
- package/src/exchange.ts +308 -0
- package/src/index.ts +63 -0
- package/src/server.ts +234 -0
- package/src/types.ts +124 -0
- package/test/exchange.test.ts +218 -0
- package/tsconfig.json +18 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 technocore-compute contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# technocore-compute
|
|
2
|
+
|
|
3
|
+
P2P compute exchange for AI agents — rent GPU/CPU time via tclk payments on technocore.chat.
|
|
4
|
+
|
|
5
|
+
## What it does
|
|
6
|
+
|
|
7
|
+
Agents can **offer** compute resources and other agents can **rent** them, with trustless payments handled via tclk HTLC/PTLC.
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
Provider: "I have an A100 with 80GB VRAM, $5/hr"
|
|
11
|
+
Consumer: "I need 24GB VRAM for 1 hour"
|
|
12
|
+
→ Matched via exchange
|
|
13
|
+
→ Payment locked via tclk HTLC
|
|
14
|
+
→ Compute runs
|
|
15
|
+
→ Consumer reveals secret → Provider gets paid
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install technocore-compute
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
import { ComputeExchange } from 'technocore-compute';
|
|
28
|
+
|
|
29
|
+
const exchange = new ComputeExchange();
|
|
30
|
+
|
|
31
|
+
// Register as a provider
|
|
32
|
+
exchange.registerProvider({
|
|
33
|
+
did: 'did:key:z6Mk...',
|
|
34
|
+
name: 'GPU Farm',
|
|
35
|
+
hardware: 'gpu',
|
|
36
|
+
gpuModel: 'A100',
|
|
37
|
+
vramGB: 80,
|
|
38
|
+
pricePerHour: '5000000', // 5M FLOP/hr
|
|
39
|
+
minDurationSec: 300,
|
|
40
|
+
maxDurationSec: 86400,
|
|
41
|
+
available: true,
|
|
42
|
+
rating: 4.8,
|
|
43
|
+
jobsCompleted: 42,
|
|
44
|
+
room: 'compute-gpu-farm',
|
|
45
|
+
registeredAt: Date.now(),
|
|
46
|
+
lastHeartbeat: Date.now(),
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Find providers for your needs
|
|
50
|
+
const providers = exchange.findProviders({
|
|
51
|
+
hardware: 'gpu',
|
|
52
|
+
minVramGB: 24,
|
|
53
|
+
maxPricePerHour: '10000000',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
// Create a compute request
|
|
57
|
+
const request = exchange.createRequest({
|
|
58
|
+
consumerDid: 'did:key:z6Mk...',
|
|
59
|
+
hardware: 'gpu',
|
|
60
|
+
minVramGB: 24,
|
|
61
|
+
durationSec: 3600,
|
|
62
|
+
maxPricePerHour: '10000000',
|
|
63
|
+
description: 'Train a small model',
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Match with best provider
|
|
67
|
+
const job = exchange.matchRequest(request.id);
|
|
68
|
+
|
|
69
|
+
// Job lifecycle
|
|
70
|
+
exchange.acceptJob(job.id); // Provider confirms
|
|
71
|
+
exchange.startJob(job.id); // Compute starts
|
|
72
|
+
exchange.completeJob(job.id); // Done! Payment released
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## API Server
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# Start the server
|
|
79
|
+
technocore-compute serve --port 4010
|
|
80
|
+
|
|
81
|
+
# Register a provider
|
|
82
|
+
curl -X POST http://localhost:4010/providers \
|
|
83
|
+
-H "Content-Type: application/json" \
|
|
84
|
+
-d '{
|
|
85
|
+
"did": "did:key:z6Mk...",
|
|
86
|
+
"name": "My GPU",
|
|
87
|
+
"hardware": "gpu",
|
|
88
|
+
"gpuModel": "RTX4090",
|
|
89
|
+
"vramGB": 24,
|
|
90
|
+
"pricePerHour": "2000000"
|
|
91
|
+
}'
|
|
92
|
+
|
|
93
|
+
# Find providers
|
|
94
|
+
curl -X POST http://localhost:4010/find \
|
|
95
|
+
-H "Content-Type: application/json" \
|
|
96
|
+
-d '{"hardware": "gpu", "minVramGB": 24}'
|
|
97
|
+
|
|
98
|
+
# Create request
|
|
99
|
+
curl -X POST http://localhost:4010/requests \
|
|
100
|
+
-H "Content-Type: application/json" \
|
|
101
|
+
-d '{
|
|
102
|
+
"consumerDid": "did:key:z6Mk...",
|
|
103
|
+
"hardware": "gpu",
|
|
104
|
+
"minVramGB": 24,
|
|
105
|
+
"durationSec": 3600,
|
|
106
|
+
"maxPricePerHour": "10000000"
|
|
107
|
+
}'
|
|
108
|
+
|
|
109
|
+
# Match request with provider
|
|
110
|
+
curl -X POST http://localhost:4010/requests/req-123/match
|
|
111
|
+
|
|
112
|
+
# Complete a job
|
|
113
|
+
curl -X POST http://localhost:4010/jobs/job-123/complete
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
## Endpoints
|
|
117
|
+
|
|
118
|
+
| Method | Endpoint | Description |
|
|
119
|
+
|--------|----------|-------------|
|
|
120
|
+
| GET | `/stats` | Exchange statistics |
|
|
121
|
+
| GET | `/providers` | List all providers |
|
|
122
|
+
| POST | `/providers` | Register a provider |
|
|
123
|
+
| GET | `/requests` | List pending requests |
|
|
124
|
+
| POST | `/requests` | Create a compute request |
|
|
125
|
+
| POST | `/requests/:id/match` | Match request with provider |
|
|
126
|
+
| GET | `/jobs` | List all jobs |
|
|
127
|
+
| POST | `/jobs/:id/accept` | Accept a job |
|
|
128
|
+
| POST | `/jobs/:id/start` | Start a job |
|
|
129
|
+
| POST | `/jobs/:id/complete` | Complete a job |
|
|
130
|
+
| POST | `/find` | Find matching providers |
|
|
131
|
+
|
|
132
|
+
## CLI
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
technocore-compute serve --port 4010
|
|
136
|
+
technocore-compute providers
|
|
137
|
+
technocore-compute find --hardware gpu --min-vram 24
|
|
138
|
+
technocore-compute stats
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## How it works with tclk
|
|
142
|
+
|
|
143
|
+
The exchange handles matching, but payment goes through tclk:
|
|
144
|
+
|
|
145
|
+
1. Consumer creates request → exchange finds provider
|
|
146
|
+
2. Both agree on terms → tclk offer posted to technocore.chat
|
|
147
|
+
3. Consumer accepts tclk offer → generates secret
|
|
148
|
+
4. Provider locks funds with hash
|
|
149
|
+
5. Compute runs
|
|
150
|
+
6. Consumer reveals secret → provider gets paid
|
|
151
|
+
|
|
152
|
+
See [technocore-ts](https://github.com/dannybyarun/technocore-ts) for tclk integration.
|
|
153
|
+
|
|
154
|
+
## License
|
|
155
|
+
|
|
156
|
+
MIT
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* technocore-compute CLI
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* technocore-compute serve [--port 4010]
|
|
7
|
+
* technocore-compute providers
|
|
8
|
+
* technocore-compute find --hardware gpu --min-vram 16
|
|
9
|
+
* technocore-compute stats
|
|
10
|
+
*/
|
|
11
|
+
import { ComputeServer } from './server.js';
|
|
12
|
+
import { ComputeExchange } from './exchange.js';
|
|
13
|
+
const args = process.argv.slice(2);
|
|
14
|
+
const command = args[0];
|
|
15
|
+
async function main() {
|
|
16
|
+
switch (command) {
|
|
17
|
+
case 'serve': {
|
|
18
|
+
const port = getArg(args, '--port') || '4010';
|
|
19
|
+
const server = new ComputeServer({ port: parseInt(port) });
|
|
20
|
+
await server.start();
|
|
21
|
+
console.log(`\nEndpoints:`);
|
|
22
|
+
console.log(` GET /stats — Exchange statistics`);
|
|
23
|
+
console.log(` GET /providers — List all providers`);
|
|
24
|
+
console.log(` POST /providers — Register a provider`);
|
|
25
|
+
console.log(` GET /requests — List pending requests`);
|
|
26
|
+
console.log(` POST /requests — Create a compute request`);
|
|
27
|
+
console.log(` POST /requests/:id/match — Match a request with provider`);
|
|
28
|
+
console.log(` GET /jobs — List all jobs`);
|
|
29
|
+
console.log(` POST /jobs/:id/accept — Accept a job`);
|
|
30
|
+
console.log(` POST /jobs/:id/start — Start a job`);
|
|
31
|
+
console.log(` POST /jobs/:id/complete — Complete a job`);
|
|
32
|
+
console.log(` POST /find — Find matching providers`);
|
|
33
|
+
break;
|
|
34
|
+
}
|
|
35
|
+
case 'providers': {
|
|
36
|
+
const exchange = new ComputeExchange();
|
|
37
|
+
const providers = exchange.listProviders();
|
|
38
|
+
if (providers.length === 0) {
|
|
39
|
+
console.log('No providers registered. Use POST /providers to register one.');
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
console.log(`\n${providers.length} provider(s):\n`);
|
|
43
|
+
for (const p of providers) {
|
|
44
|
+
const hw = p.hardware.toUpperCase();
|
|
45
|
+
const price = `${p.pricePerHour} FLOP/hr`;
|
|
46
|
+
const spec = p.gpuModel ? `${p.gpuModel} ${p.vramGB}GB` : `${p.cpuCores} cores ${p.ramGB}GB`;
|
|
47
|
+
console.log(` ${p.name} (${hw})`);
|
|
48
|
+
console.log(` ${spec} — ${price}`);
|
|
49
|
+
console.log(` Jobs: ${p.jobsCompleted} | Rating: ${p.rating}/5 | ${p.available ? '✅ Available' : '❌ Busy'}`);
|
|
50
|
+
console.log();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
break;
|
|
54
|
+
}
|
|
55
|
+
case 'find': {
|
|
56
|
+
const exchange = new ComputeExchange();
|
|
57
|
+
const hardware = getArg(args, '--hardware') || 'gpu';
|
|
58
|
+
const minVram = getArg(args, '--min-vram');
|
|
59
|
+
const maxPrice = getArg(args, '--max-price');
|
|
60
|
+
const providers = exchange.findProviders({
|
|
61
|
+
hardware,
|
|
62
|
+
minVramGB: minVram ? parseInt(minVram) : undefined,
|
|
63
|
+
maxPricePerHour: maxPrice,
|
|
64
|
+
});
|
|
65
|
+
if (providers.length === 0) {
|
|
66
|
+
console.log('No matching providers found.');
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
console.log(`\n${providers.length} matching provider(s) for ${hardware}:\n`);
|
|
70
|
+
for (const p of providers) {
|
|
71
|
+
console.log(` ${p.name} — ${p.pricePerHour} FLOP/hr`);
|
|
72
|
+
if (p.gpuModel)
|
|
73
|
+
console.log(` ${p.gpuModel}, ${p.vramGB}GB VRAM`);
|
|
74
|
+
if (p.cpuCores)
|
|
75
|
+
console.log(` ${p.cpuCores} cores, ${p.ramGB}GB RAM`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
case 'stats': {
|
|
81
|
+
const exchange = new ComputeExchange();
|
|
82
|
+
const stats = exchange.getStats();
|
|
83
|
+
console.log('\n📊 Exchange Stats:\n');
|
|
84
|
+
console.log(` Providers: ${stats.availableProviders}/${stats.totalProviders} available`);
|
|
85
|
+
console.log(` Jobs: ${stats.activeJobs} active, ${stats.completedJobs} completed`);
|
|
86
|
+
console.log(` Volume: ${stats.totalVolume} FLOP\n`);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
default:
|
|
90
|
+
console.log(`
|
|
91
|
+
technocore-compute — P2P compute exchange for AI agents
|
|
92
|
+
|
|
93
|
+
Commands:
|
|
94
|
+
serve [--port 4010] Start the API server
|
|
95
|
+
providers List registered providers
|
|
96
|
+
find [options] Find matching providers
|
|
97
|
+
stats Show exchange statistics
|
|
98
|
+
|
|
99
|
+
Find options:
|
|
100
|
+
--hardware gpu|cpu Hardware type (default: gpu)
|
|
101
|
+
--min-vram 16 Minimum VRAM in GB
|
|
102
|
+
--max-price 5000000 Maximum price per hour
|
|
103
|
+
|
|
104
|
+
Example:
|
|
105
|
+
technocore-compute serve --port 4010
|
|
106
|
+
technocore-compute find --hardware gpu --min-vram 24
|
|
107
|
+
`);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function getArg(args, flag) {
|
|
111
|
+
const idx = args.indexOf(flag);
|
|
112
|
+
return idx >= 0 ? args[idx + 1] : undefined;
|
|
113
|
+
}
|
|
114
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComputeExchange - P2P compute marketplace for AI agents
|
|
3
|
+
*
|
|
4
|
+
* Manages provider registration, compute requests, job matching,
|
|
5
|
+
* and tclk payment coordination.
|
|
6
|
+
*/
|
|
7
|
+
import type { ComputeProvider, ComputeRequest, ComputeJob, ComputeType, ExchangeStats, JobStatus } from './types.js';
|
|
8
|
+
export interface ExchangeConfig {
|
|
9
|
+
/** technocore.chat base URL */
|
|
10
|
+
technocoreBaseUrl?: string;
|
|
11
|
+
/** Room prefix for compute exchange */
|
|
12
|
+
roomPrefix?: string;
|
|
13
|
+
/** How often to refresh providers (ms) */
|
|
14
|
+
refreshIntervalMs?: number;
|
|
15
|
+
/** Data directory for persistence */
|
|
16
|
+
dataDir?: string;
|
|
17
|
+
}
|
|
18
|
+
export declare class ComputeExchange {
|
|
19
|
+
private providers;
|
|
20
|
+
private requests;
|
|
21
|
+
private jobs;
|
|
22
|
+
private config;
|
|
23
|
+
constructor(config?: ExchangeConfig);
|
|
24
|
+
/** Register a compute provider */
|
|
25
|
+
registerProvider(provider: ComputeProvider): void;
|
|
26
|
+
/** Update provider heartbeat */
|
|
27
|
+
heartbeat(did: string): void;
|
|
28
|
+
/** Set provider availability */
|
|
29
|
+
setAvailable(did: string, available: boolean): void;
|
|
30
|
+
/** Remove stale providers (no heartbeat in 5 minutes) */
|
|
31
|
+
pruneStaleProviders(maxAgeMs?: number): number;
|
|
32
|
+
/** Get a provider by DID */
|
|
33
|
+
getProvider(did: string): ComputeProvider | undefined;
|
|
34
|
+
/** List all providers */
|
|
35
|
+
listProviders(): ComputeProvider[];
|
|
36
|
+
/** Find providers matching requirements */
|
|
37
|
+
findProviders(requirements: {
|
|
38
|
+
hardware?: ComputeType;
|
|
39
|
+
minVramGB?: number;
|
|
40
|
+
minCpuCores?: number;
|
|
41
|
+
minRamGB?: number;
|
|
42
|
+
maxPricePerHour?: string;
|
|
43
|
+
}): ComputeProvider[];
|
|
44
|
+
/** Create a compute request */
|
|
45
|
+
createRequest(request: Omit<ComputeRequest, 'id' | 'createdAt' | 'expiresAt'>): ComputeRequest;
|
|
46
|
+
/** Get a request by ID */
|
|
47
|
+
getRequest(id: string): ComputeRequest | undefined;
|
|
48
|
+
/** List all pending requests */
|
|
49
|
+
listPendingRequests(): ComputeRequest[];
|
|
50
|
+
/** Remove expired requests */
|
|
51
|
+
pruneExpiredRequests(): number;
|
|
52
|
+
/** Match a request with the best available provider */
|
|
53
|
+
matchRequest(requestId: string): ComputeJob | null;
|
|
54
|
+
/** Create a job from a request and provider */
|
|
55
|
+
createJob(request: ComputeRequest, provider: ComputeProvider): ComputeJob;
|
|
56
|
+
/** Accept a job (provider confirms) */
|
|
57
|
+
acceptJob(jobId: string): boolean;
|
|
58
|
+
/** Start a job */
|
|
59
|
+
startJob(jobId: string): boolean;
|
|
60
|
+
/** Complete a job */
|
|
61
|
+
completeJob(jobId: string): boolean;
|
|
62
|
+
/** Fail a job */
|
|
63
|
+
failJob(jobId: string, _reason?: string): boolean;
|
|
64
|
+
/** Refund a job */
|
|
65
|
+
refundJob(jobId: string): boolean;
|
|
66
|
+
/** Get a job by ID */
|
|
67
|
+
getJob(id: string): ComputeJob | undefined;
|
|
68
|
+
/** List all jobs */
|
|
69
|
+
listJobs(status?: JobStatus): ComputeJob[];
|
|
70
|
+
/** Get exchange statistics */
|
|
71
|
+
getStats(): ExchangeStats;
|
|
72
|
+
/** Export all data as JSON */
|
|
73
|
+
toJSON(): string;
|
|
74
|
+
/** Import data from JSON */
|
|
75
|
+
fromJSON(json: string): void;
|
|
76
|
+
}
|
package/dist/exchange.js
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ComputeExchange - P2P compute marketplace for AI agents
|
|
3
|
+
*
|
|
4
|
+
* Manages provider registration, compute requests, job matching,
|
|
5
|
+
* and tclk payment coordination.
|
|
6
|
+
*/
|
|
7
|
+
export class ComputeExchange {
|
|
8
|
+
providers = new Map();
|
|
9
|
+
requests = new Map();
|
|
10
|
+
jobs = new Map();
|
|
11
|
+
config;
|
|
12
|
+
constructor(config = {}) {
|
|
13
|
+
this.config = {
|
|
14
|
+
technocoreBaseUrl: config.technocoreBaseUrl || 'https://technocore.chat',
|
|
15
|
+
roomPrefix: config.roomPrefix || 'compute',
|
|
16
|
+
refreshIntervalMs: config.refreshIntervalMs || 30_000,
|
|
17
|
+
dataDir: config.dataDir || './compute-data',
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
// ── Provider Management ──────────────────────────────────────────────────
|
|
21
|
+
/** Register a compute provider */
|
|
22
|
+
registerProvider(provider) {
|
|
23
|
+
this.providers.set(provider.did, {
|
|
24
|
+
...provider,
|
|
25
|
+
registeredAt: provider.registeredAt || Date.now(),
|
|
26
|
+
lastHeartbeat: provider.lastHeartbeat || Date.now(),
|
|
27
|
+
available: provider.available !== undefined ? provider.available : true,
|
|
28
|
+
rating: provider.rating || 0,
|
|
29
|
+
jobsCompleted: provider.jobsCompleted || 0,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Update provider heartbeat */
|
|
33
|
+
heartbeat(did) {
|
|
34
|
+
const provider = this.providers.get(did);
|
|
35
|
+
if (provider) {
|
|
36
|
+
provider.lastHeartbeat = Date.now();
|
|
37
|
+
provider.available = true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
/** Set provider availability */
|
|
41
|
+
setAvailable(did, available) {
|
|
42
|
+
const provider = this.providers.get(did);
|
|
43
|
+
if (provider) {
|
|
44
|
+
provider.available = available;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/** Remove stale providers (no heartbeat in 5 minutes) */
|
|
48
|
+
pruneStaleProviders(maxAgeMs = 300_000) {
|
|
49
|
+
const now = Date.now();
|
|
50
|
+
let pruned = 0;
|
|
51
|
+
for (const [did, provider] of this.providers) {
|
|
52
|
+
if (now - provider.lastHeartbeat > maxAgeMs) {
|
|
53
|
+
this.providers.delete(did);
|
|
54
|
+
pruned++;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return pruned;
|
|
58
|
+
}
|
|
59
|
+
/** Get a provider by DID */
|
|
60
|
+
getProvider(did) {
|
|
61
|
+
return this.providers.get(did);
|
|
62
|
+
}
|
|
63
|
+
/** List all providers */
|
|
64
|
+
listProviders() {
|
|
65
|
+
return Array.from(this.providers.values());
|
|
66
|
+
}
|
|
67
|
+
/** Find providers matching requirements */
|
|
68
|
+
findProviders(requirements) {
|
|
69
|
+
return this.listProviders().filter(p => {
|
|
70
|
+
if (!p.available)
|
|
71
|
+
return false;
|
|
72
|
+
if (requirements.hardware && requirements.hardware !== 'any' && p.hardware !== requirements.hardware)
|
|
73
|
+
return false;
|
|
74
|
+
if (requirements.minVramGB && (!p.vramGB || p.vramGB < requirements.minVramGB))
|
|
75
|
+
return false;
|
|
76
|
+
if (requirements.minCpuCores && (!p.cpuCores || p.cpuCores < requirements.minCpuCores))
|
|
77
|
+
return false;
|
|
78
|
+
if (requirements.minRamGB && (!p.ramGB || p.ramGB < requirements.minRamGB))
|
|
79
|
+
return false;
|
|
80
|
+
if (requirements.maxPricePerHour) {
|
|
81
|
+
const price = BigInt(p.pricePerHour);
|
|
82
|
+
const max = BigInt(requirements.maxPricePerHour);
|
|
83
|
+
if (price > max)
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
86
|
+
return true;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
// ── Request Management ───────────────────────────────────────────────────
|
|
90
|
+
/** Create a compute request */
|
|
91
|
+
createRequest(request) {
|
|
92
|
+
const id = `req-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
93
|
+
const fullRequest = {
|
|
94
|
+
...request,
|
|
95
|
+
id,
|
|
96
|
+
createdAt: Date.now(),
|
|
97
|
+
expiresAt: Date.now() + (request.durationSec * 1000 || 3_600_000),
|
|
98
|
+
};
|
|
99
|
+
this.requests.set(id, fullRequest);
|
|
100
|
+
return fullRequest;
|
|
101
|
+
}
|
|
102
|
+
/** Get a request by ID */
|
|
103
|
+
getRequest(id) {
|
|
104
|
+
return this.requests.get(id);
|
|
105
|
+
}
|
|
106
|
+
/** List all pending requests */
|
|
107
|
+
listPendingRequests() {
|
|
108
|
+
const now = Date.now();
|
|
109
|
+
return Array.from(this.requests.values()).filter(r => r.expiresAt > now);
|
|
110
|
+
}
|
|
111
|
+
/** Remove expired requests */
|
|
112
|
+
pruneExpiredRequests() {
|
|
113
|
+
const now = Date.now();
|
|
114
|
+
let pruned = 0;
|
|
115
|
+
for (const [id, request] of this.requests) {
|
|
116
|
+
if (request.expiresAt <= now) {
|
|
117
|
+
this.requests.delete(id);
|
|
118
|
+
pruned++;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
return pruned;
|
|
122
|
+
}
|
|
123
|
+
// ── Job Matching ─────────────────────────────────────────────────────────
|
|
124
|
+
/** Match a request with the best available provider */
|
|
125
|
+
matchRequest(requestId) {
|
|
126
|
+
const request = this.requests.get(requestId);
|
|
127
|
+
if (!request)
|
|
128
|
+
return null;
|
|
129
|
+
const providers = this.findProviders({
|
|
130
|
+
hardware: request.hardware,
|
|
131
|
+
minVramGB: request.minVramGB,
|
|
132
|
+
minCpuCores: request.minCpuCores,
|
|
133
|
+
minRamGB: request.minRamGB,
|
|
134
|
+
maxPricePerHour: request.maxPricePerHour,
|
|
135
|
+
});
|
|
136
|
+
if (providers.length === 0)
|
|
137
|
+
return null;
|
|
138
|
+
// Sort by price (cheapest first), then by rating (highest first)
|
|
139
|
+
providers.sort((a, b) => {
|
|
140
|
+
const priceDiff = BigInt(a.pricePerHour) - BigInt(b.pricePerHour);
|
|
141
|
+
if (priceDiff !== 0n)
|
|
142
|
+
return priceDiff < 0n ? -1 : 1;
|
|
143
|
+
return b.rating - a.rating;
|
|
144
|
+
});
|
|
145
|
+
const bestProvider = providers[0];
|
|
146
|
+
return this.createJob(request, bestProvider);
|
|
147
|
+
}
|
|
148
|
+
/** Create a job from a request and provider */
|
|
149
|
+
createJob(request, provider) {
|
|
150
|
+
const jobId = `job-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
151
|
+
const hours = request.durationSec / 3600;
|
|
152
|
+
const cost = BigInt(provider.pricePerHour) * BigInt(Math.ceil(hours * 10)) / 10n;
|
|
153
|
+
const job = {
|
|
154
|
+
id: jobId,
|
|
155
|
+
providerDid: provider.did,
|
|
156
|
+
consumerDid: request.consumerDid,
|
|
157
|
+
request,
|
|
158
|
+
provider,
|
|
159
|
+
status: 'matched',
|
|
160
|
+
totalCost: cost.toString(),
|
|
161
|
+
room: `${this.config.roomPrefix}-${jobId.slice(4, 12)}`,
|
|
162
|
+
createdAt: Date.now(),
|
|
163
|
+
};
|
|
164
|
+
this.jobs.set(jobId, job);
|
|
165
|
+
return job;
|
|
166
|
+
}
|
|
167
|
+
/** Accept a job (provider confirms) */
|
|
168
|
+
acceptJob(jobId) {
|
|
169
|
+
const job = this.jobs.get(jobId);
|
|
170
|
+
if (!job || job.status !== 'matched')
|
|
171
|
+
return false;
|
|
172
|
+
job.status = 'locked';
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
/** Start a job */
|
|
176
|
+
startJob(jobId) {
|
|
177
|
+
const job = this.jobs.get(jobId);
|
|
178
|
+
if (!job || job.status !== 'locked')
|
|
179
|
+
return false;
|
|
180
|
+
job.status = 'running';
|
|
181
|
+
job.startedAt = Date.now();
|
|
182
|
+
return true;
|
|
183
|
+
}
|
|
184
|
+
/** Complete a job */
|
|
185
|
+
completeJob(jobId) {
|
|
186
|
+
const job = this.jobs.get(jobId);
|
|
187
|
+
if (!job || job.status !== 'running')
|
|
188
|
+
return false;
|
|
189
|
+
job.status = 'completed';
|
|
190
|
+
job.completedAt = Date.now();
|
|
191
|
+
// Update provider stats
|
|
192
|
+
const provider = this.providers.get(job.providerDid);
|
|
193
|
+
if (provider) {
|
|
194
|
+
provider.jobsCompleted++;
|
|
195
|
+
}
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
/** Fail a job */
|
|
199
|
+
failJob(jobId, _reason) {
|
|
200
|
+
const job = this.jobs.get(jobId);
|
|
201
|
+
if (!job)
|
|
202
|
+
return false;
|
|
203
|
+
job.status = 'failed';
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
/** Refund a job */
|
|
207
|
+
refundJob(jobId) {
|
|
208
|
+
const job = this.jobs.get(jobId);
|
|
209
|
+
if (!job)
|
|
210
|
+
return false;
|
|
211
|
+
job.status = 'refunded';
|
|
212
|
+
return true;
|
|
213
|
+
}
|
|
214
|
+
/** Get a job by ID */
|
|
215
|
+
getJob(id) {
|
|
216
|
+
return this.jobs.get(id);
|
|
217
|
+
}
|
|
218
|
+
/** List all jobs */
|
|
219
|
+
listJobs(status) {
|
|
220
|
+
const jobs = Array.from(this.jobs.values());
|
|
221
|
+
return status ? jobs.filter(j => j.status === status) : jobs;
|
|
222
|
+
}
|
|
223
|
+
// ── Stats ────────────────────────────────────────────────────────────────
|
|
224
|
+
/** Get exchange statistics */
|
|
225
|
+
getStats() {
|
|
226
|
+
const allJobs = Array.from(this.jobs.values());
|
|
227
|
+
const allProviders = Array.from(this.providers.values());
|
|
228
|
+
return {
|
|
229
|
+
totalProviders: allProviders.length,
|
|
230
|
+
availableProviders: allProviders.filter(p => p.available).length,
|
|
231
|
+
totalJobs: allJobs.length,
|
|
232
|
+
activeJobs: allJobs.filter(j => ['matched', 'locked', 'running'].includes(j.status)).length,
|
|
233
|
+
completedJobs: allJobs.filter(j => j.status === 'completed').length,
|
|
234
|
+
totalVolume: allJobs
|
|
235
|
+
.filter(j => j.status === 'completed')
|
|
236
|
+
.reduce((sum, j) => sum + BigInt(j.totalCost), 0n)
|
|
237
|
+
.toString(),
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// ── Serialization ────────────────────────────────────────────────────────
|
|
241
|
+
/** Export all data as JSON */
|
|
242
|
+
toJSON() {
|
|
243
|
+
return JSON.stringify({
|
|
244
|
+
providers: Array.from(this.providers.entries()),
|
|
245
|
+
requests: Array.from(this.requests.entries()),
|
|
246
|
+
jobs: Array.from(this.jobs.entries()),
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
/** Import data from JSON */
|
|
250
|
+
fromJSON(json) {
|
|
251
|
+
try {
|
|
252
|
+
const data = JSON.parse(json);
|
|
253
|
+
if (data.providers)
|
|
254
|
+
this.providers = new Map(data.providers);
|
|
255
|
+
if (data.requests)
|
|
256
|
+
this.requests = new Map(data.requests);
|
|
257
|
+
if (data.jobs)
|
|
258
|
+
this.jobs = new Map(data.jobs);
|
|
259
|
+
}
|
|
260
|
+
catch { }
|
|
261
|
+
}
|
|
262
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* technocore-compute — P2P compute exchange for AI agents
|
|
3
|
+
*
|
|
4
|
+
* Agents can offer compute resources (GPU/CPU) and other agents
|
|
5
|
+
* can rent them, with payments handled via tclk HTLC/PTLC.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* import { ComputeExchange } from 'technocore-compute';
|
|
10
|
+
*
|
|
11
|
+
* const exchange = new ComputeExchange();
|
|
12
|
+
*
|
|
13
|
+
* // Register as a provider
|
|
14
|
+
* exchange.registerProvider({
|
|
15
|
+
* did: 'did:key:z6Mk...',
|
|
16
|
+
* name: 'GPU Farm',
|
|
17
|
+
* hardware: 'gpu',
|
|
18
|
+
* gpuModel: 'A100',
|
|
19
|
+
* vramGB: 80,
|
|
20
|
+
* pricePerHour: '5000000',
|
|
21
|
+
* minDurationSec: 300,
|
|
22
|
+
* maxDurationSec: 86400,
|
|
23
|
+
* available: true,
|
|
24
|
+
* rating: 4.8,
|
|
25
|
+
* jobsCompleted: 42,
|
|
26
|
+
* room: 'compute-gpu-farm',
|
|
27
|
+
* registeredAt: Date.now(),
|
|
28
|
+
* lastHeartbeat: Date.now(),
|
|
29
|
+
* });
|
|
30
|
+
*
|
|
31
|
+
* // Find providers
|
|
32
|
+
* const providers = exchange.findProviders({
|
|
33
|
+
* hardware: 'gpu',
|
|
34
|
+
* minVramGB: 24,
|
|
35
|
+
* maxPricePerHour: '10000000',
|
|
36
|
+
* });
|
|
37
|
+
*
|
|
38
|
+
* // Create a request and match
|
|
39
|
+
* const request = exchange.createRequest({
|
|
40
|
+
* consumerDid: 'did:key:z6Mk...',
|
|
41
|
+
* hardware: 'gpu',
|
|
42
|
+
* minVramGB: 24,
|
|
43
|
+
* durationSec: 3600,
|
|
44
|
+
* maxPricePerHour: '10000000',
|
|
45
|
+
* description: 'Train a small model',
|
|
46
|
+
* });
|
|
47
|
+
*
|
|
48
|
+
* const job = exchange.matchRequest(request.id);
|
|
49
|
+
* ```
|
|
50
|
+
*/
|
|
51
|
+
export { ComputeExchange } from './exchange.js';
|
|
52
|
+
export type { ExchangeConfig } from './exchange.js';
|
|
53
|
+
export { ComputeServer } from './server.js';
|
|
54
|
+
export type { ServerConfig } from './server.js';
|
|
55
|
+
export type { ComputeProvider, ComputeRequest, ComputeJob, ComputeType, JobStatus, ExchangeStats, } from './types.js';
|