lightnode-sdk 0.4.9 → 0.5.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/dist/bridge.d.ts +233 -0
- package/dist/bridge.js +201 -0
- package/dist/chat.d.ts +70 -0
- package/dist/chat.js +88 -0
- package/dist/cli.js +105 -2
- package/dist/dao.d.ts +439 -0
- package/dist/dao.js +234 -0
- package/dist/index.d.ts +29 -2
- package/dist/index.js +60 -3
- package/dist/onchain-models.d.ts +380 -0
- package/dist/onchain-models.js +187 -0
- package/dist/subgraph.d.ts +2 -0
- package/dist/subgraph.js +6 -0
- package/dist/worker.d.ts +104 -0
- package/dist/worker.js +186 -0
- package/package.json +1 -1
package/dist/worker.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SDK-level worker preflight + watch.
|
|
3
|
+
*
|
|
4
|
+
* The desktop app already has local worker-health parsers (parseWorkerHealth,
|
|
5
|
+
* parseSpeedTest) that need shell access to the running container. Those are
|
|
6
|
+
* great for operators ON the worker box, but useless for a builder watching
|
|
7
|
+
* the network from anywhere else.
|
|
8
|
+
*
|
|
9
|
+
* This module fills the remote-only gap:
|
|
10
|
+
* - `preflight()` runs ONE real test inference against the live network
|
|
11
|
+
* and returns a verdict (works / over-deadline / failed). This is the
|
|
12
|
+
* "test job before joining a worker pool" the community has been asking
|
|
13
|
+
* for; works from any machine with a funded wallet.
|
|
14
|
+
* - `watch()` polls a worker's on-chain + indexer status on a fixed
|
|
15
|
+
* interval and yields an event each time the status meaningfully
|
|
16
|
+
* changes (registered <-> deregistered, last-seen went stale, completion
|
|
17
|
+
* rate dropped). Suitable for a CI gate or a `cron` job.
|
|
18
|
+
*
|
|
19
|
+
* Both are pure SDK calls. No Docker, no SSH, no privileged access.
|
|
20
|
+
*/
|
|
21
|
+
import { runInferenceWithKey } from "./inference.js";
|
|
22
|
+
import { isStalledWorker } from "./errors.js";
|
|
23
|
+
const DEFAULT_PROMPT = "Reply with the single word OK.";
|
|
24
|
+
/**
|
|
25
|
+
* Run one real encrypted inference against the live network and classify
|
|
26
|
+
* the result. Useful as a CI gate ("did the wallet survive a real call this
|
|
27
|
+
* deploy?") or as a pre-join check for a worker operator who wants to test
|
|
28
|
+
* the protocol path before staking.
|
|
29
|
+
*/
|
|
30
|
+
export async function preflight(args) {
|
|
31
|
+
const deadlineMs = args.deadlineMs ?? 60000;
|
|
32
|
+
const prompt = args.prompt ?? DEFAULT_PROMPT;
|
|
33
|
+
const t0 = Date.now();
|
|
34
|
+
try {
|
|
35
|
+
const r = await runInferenceWithKey({ ...args, prompt });
|
|
36
|
+
const elapsedMs = Date.now() - t0;
|
|
37
|
+
const verdict = elapsedMs > deadlineMs ? "over-deadline" : "ok";
|
|
38
|
+
return {
|
|
39
|
+
verdict,
|
|
40
|
+
elapsedMs,
|
|
41
|
+
summary: verdict === "ok"
|
|
42
|
+
? `OK in ${(elapsedMs / 1000).toFixed(1)}s. Worker ${r.worker} replied with ${r.answer.length} chars.`
|
|
43
|
+
: `Answer arrived but took ${(elapsedMs / 1000).toFixed(1)}s, over the ${(deadlineMs / 1000).toFixed(0)}s deadline.`,
|
|
44
|
+
answer: r.answer,
|
|
45
|
+
worker: r.worker,
|
|
46
|
+
txs: r.txs,
|
|
47
|
+
error: null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (e) {
|
|
51
|
+
const elapsedMs = Date.now() - t0;
|
|
52
|
+
if (isStalledWorker(e)) {
|
|
53
|
+
return {
|
|
54
|
+
verdict: "stalled",
|
|
55
|
+
elapsedMs,
|
|
56
|
+
summary: "All retry attempts stalled (workers never produced an answer). Protocol refunds the fees automatically.",
|
|
57
|
+
answer: "",
|
|
58
|
+
worker: null,
|
|
59
|
+
txs: { createSession: null, submitJob: null, jobCompleted: null },
|
|
60
|
+
error: e.message,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return {
|
|
64
|
+
verdict: "failed",
|
|
65
|
+
elapsedMs,
|
|
66
|
+
summary: `Test inference failed: ${e.message}`,
|
|
67
|
+
answer: "",
|
|
68
|
+
worker: null,
|
|
69
|
+
txs: { createSession: null, submitJob: null, jobCompleted: null },
|
|
70
|
+
error: e.message,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Poll one worker's on-chain + indexer status and yield an event each time
|
|
76
|
+
* something meaningful changes (registration, staleness, completed-jobs
|
|
77
|
+
* counter, earnings). Runs until `handle.stop()` is called or `maxEvents`
|
|
78
|
+
* is reached.
|
|
79
|
+
*/
|
|
80
|
+
export function watch(ln, address, opts = {}) {
|
|
81
|
+
const intervalMs = opts.intervalMs ?? 30000;
|
|
82
|
+
const staleSecs = opts.staleSecs ?? 90;
|
|
83
|
+
const maxEvents = opts.maxEvents ?? Number.POSITIVE_INFINITY;
|
|
84
|
+
const networkId = ln.network.label.toLowerCase().includes("mainnet") ? "mainnet" : "testnet";
|
|
85
|
+
const queue = [];
|
|
86
|
+
const waiters = [];
|
|
87
|
+
let stopped = false;
|
|
88
|
+
let eventCount = 0;
|
|
89
|
+
let prevReg = null;
|
|
90
|
+
let prevStale = null;
|
|
91
|
+
let prevJobs = null;
|
|
92
|
+
let prevEarn = null;
|
|
93
|
+
let timer = null;
|
|
94
|
+
const push = (kind, state) => {
|
|
95
|
+
const event = { kind, at: Date.now(), worker: address, network: networkId, state };
|
|
96
|
+
eventCount++;
|
|
97
|
+
if (waiters.length > 0) {
|
|
98
|
+
const resolve = waiters.shift();
|
|
99
|
+
if (resolve)
|
|
100
|
+
resolve({ value: event, done: false });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
queue.push(event);
|
|
104
|
+
}
|
|
105
|
+
if (eventCount >= maxEvents)
|
|
106
|
+
stop();
|
|
107
|
+
};
|
|
108
|
+
const stop = () => {
|
|
109
|
+
if (stopped)
|
|
110
|
+
return;
|
|
111
|
+
stopped = true;
|
|
112
|
+
if (timer)
|
|
113
|
+
clearTimeout(timer);
|
|
114
|
+
timer = null;
|
|
115
|
+
while (waiters.length > 0) {
|
|
116
|
+
const resolve = waiters.shift();
|
|
117
|
+
if (resolve)
|
|
118
|
+
resolve({ value: undefined, done: true });
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
const poll = async () => {
|
|
122
|
+
if (stopped)
|
|
123
|
+
return;
|
|
124
|
+
try {
|
|
125
|
+
const [reg, worker] = await Promise.all([ln.isRegistered(address), ln.getWorker(address)]);
|
|
126
|
+
const now = Math.floor(Date.now() / 1000);
|
|
127
|
+
const lastSeenSecsAgo = worker?.last_seen_at != null ? Math.max(0, now - worker.last_seen_at) : null;
|
|
128
|
+
const isStale = lastSeenSecsAgo == null ? false : lastSeenSecsAgo > staleSecs;
|
|
129
|
+
const jobs = worker?.jobs_completed ?? null;
|
|
130
|
+
const earn = worker ? Number(BigInt(worker.total_earned ?? "0")) / 1e18 : 0;
|
|
131
|
+
const state = {
|
|
132
|
+
registered: reg,
|
|
133
|
+
lastSeenSecsAgo,
|
|
134
|
+
jobsCompleted: jobs,
|
|
135
|
+
earningsLcai: earn,
|
|
136
|
+
activeJobs: worker?.active_job_count ?? null,
|
|
137
|
+
isStale,
|
|
138
|
+
};
|
|
139
|
+
// First poll: emit a snapshot so the caller always sees an initial event.
|
|
140
|
+
if (prevReg === null && prevStale === null && prevJobs === null && prevEarn === null) {
|
|
141
|
+
push("snapshot", state);
|
|
142
|
+
}
|
|
143
|
+
else {
|
|
144
|
+
if (prevReg === false && reg === true)
|
|
145
|
+
push("registered", state);
|
|
146
|
+
if (prevReg === true && reg === false)
|
|
147
|
+
push("deregistered", state);
|
|
148
|
+
if (prevStale === false && isStale)
|
|
149
|
+
push("went-stale", state);
|
|
150
|
+
if (prevStale === true && !isStale)
|
|
151
|
+
push("back-online", state);
|
|
152
|
+
if (jobs != null && prevJobs != null && jobs > prevJobs)
|
|
153
|
+
push("jobs-completed", state);
|
|
154
|
+
if (earn > (prevEarn ?? 0) + 1e-9)
|
|
155
|
+
push("earnings-up", state);
|
|
156
|
+
}
|
|
157
|
+
prevReg = reg;
|
|
158
|
+
prevStale = isStale;
|
|
159
|
+
prevJobs = jobs;
|
|
160
|
+
prevEarn = earn;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
// Transient indexer / RPC error - do nothing, the next poll will retry.
|
|
164
|
+
}
|
|
165
|
+
if (!stopped)
|
|
166
|
+
timer = setTimeout(poll, intervalMs);
|
|
167
|
+
};
|
|
168
|
+
// Kick off the first poll immediately.
|
|
169
|
+
void poll();
|
|
170
|
+
return {
|
|
171
|
+
events: {
|
|
172
|
+
[Symbol.asyncIterator]() {
|
|
173
|
+
return {
|
|
174
|
+
async next() {
|
|
175
|
+
if (queue.length > 0)
|
|
176
|
+
return { value: queue.shift(), done: false };
|
|
177
|
+
if (stopped)
|
|
178
|
+
return { value: undefined, done: true };
|
|
179
|
+
return new Promise((resolve) => waiters.push(resolve));
|
|
180
|
+
},
|
|
181
|
+
};
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
stop,
|
|
185
|
+
};
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Read-only TypeScript client for LightChain AI: workers, jobs, models, on-chain registration, and per-model network analytics. Independent, community-built (not an official LightChain package).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|