lightnode-sdk 0.4.1 → 0.4.3
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.d.ts +3 -3
- package/dist/index.js +4 -2
- package/dist/inference.d.ts +65 -2
- package/dist/inference.js +115 -3
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS } from "./networks.js";
|
|
2
2
|
import { fromWei } from "./subgraph.js";
|
|
3
3
|
import { aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv } from "./analytics.js";
|
|
4
|
-
import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference } from "./inference.js";
|
|
4
|
+
import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey } from "./inference.js";
|
|
5
5
|
import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
|
|
6
6
|
import { GatewayClient, GatewayHttpError } from "./gateway.js";
|
|
7
7
|
import * as crypto from "./crypto.js";
|
|
@@ -60,7 +60,7 @@ export declare class LightNode {
|
|
|
60
60
|
baseUrl?: string;
|
|
61
61
|
}): GatewayClient;
|
|
62
62
|
}
|
|
63
|
-
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
|
|
63
|
+
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
|
|
64
64
|
export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
|
|
65
|
-
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult } from "./inference.js";
|
|
65
|
+
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs } from "./inference.js";
|
|
66
66
|
export type { NetworkId, NetworkConfig, Worker, Job, ModelInfo, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics };
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@ import { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS } from "./networks.js";
|
|
|
2
2
|
import { fetchWorker, fetchWorkerJobs, fetchRecentJobs, fetchModels, fetchWorkers, summarize, fromWei, } from "./subgraph.js";
|
|
3
3
|
import { isRegistered } from "./onchain.js";
|
|
4
4
|
import { aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, } from "./analytics.js";
|
|
5
|
-
import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, } from "./inference.js";
|
|
5
|
+
import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, } from "./inference.js";
|
|
6
6
|
import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, } from "./errors.js";
|
|
7
7
|
import { GatewayClient, GatewayHttpError } from "./gateway.js";
|
|
8
8
|
import * as crypto from "./crypto.js";
|
|
@@ -94,4 +94,6 @@ export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggreg
|
|
|
94
94
|
// v0.3 inference-submit surface (BETA - see README "Submitting inference").
|
|
95
95
|
GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
|
|
96
96
|
// v0.4 high-level orchestrator: one call, full flow.
|
|
97
|
-
runInference,
|
|
97
|
+
runInference,
|
|
98
|
+
// v0.4.3 key-in-answer-out shortcut: same flow, no viem/SIWE wiring.
|
|
99
|
+
runInferenceWithKey, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
|
package/dist/inference.d.ts
CHANGED
|
@@ -159,7 +159,7 @@ export interface RunInferenceArgs {
|
|
|
159
159
|
onChunk?: (chunk: string, totalSoFar: string) => void;
|
|
160
160
|
/** Retry count if a worker stalls. Default 2 (so up to 3 paid attempts). */
|
|
161
161
|
maxRetries?: number;
|
|
162
|
-
/** How long to wait for JobCompleted before declaring the worker stalled. Default
|
|
162
|
+
/** How long to wait for JobCompleted before declaring the worker stalled. Default 120s. */
|
|
163
163
|
jobCompletedTimeoutMs?: number;
|
|
164
164
|
/**
|
|
165
165
|
* WebSocket constructor. In a browser, omit and `globalThis.WebSocket` is
|
|
@@ -179,7 +179,12 @@ export interface RunInferenceResult {
|
|
|
179
179
|
txs: {
|
|
180
180
|
createSession: `0x${string}`;
|
|
181
181
|
submitJob: `0x${string}`;
|
|
182
|
-
|
|
182
|
+
/**
|
|
183
|
+
* Worker's commit-result tx. Null if the on-chain event hasn't landed by the
|
|
184
|
+
* deadline but the WS-delivered, session-key-decrypted answer DID arrive -
|
|
185
|
+
* in that case the answer is still authentic; this is just the explorer link.
|
|
186
|
+
*/
|
|
187
|
+
jobCompleted: `0x${string}` | null;
|
|
183
188
|
};
|
|
184
189
|
/** The dispatcher-assigned worker that produced this response. */
|
|
185
190
|
worker: `0x${string}`;
|
|
@@ -224,3 +229,61 @@ export interface RunInferenceResult {
|
|
|
224
229
|
export declare function runInference(args: RunInferenceArgs): Promise<RunInferenceResult>;
|
|
225
230
|
/** Re-export the typed errors at this layer so a single import covers everything. */
|
|
226
231
|
export { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
|
|
232
|
+
import type { NetworkId } from "./types.js";
|
|
233
|
+
export interface RunInferenceWithKeyArgs {
|
|
234
|
+
/** Network ID (`"testnet"` / `"mainnet"`) or a custom NetworkConfig. */
|
|
235
|
+
network: NetworkId | NetworkConfig;
|
|
236
|
+
/**
|
|
237
|
+
* A funded EVM private key, hex with `0x` prefix. Pays the job fee + gas and
|
|
238
|
+
* signs createSession + submitJob. NEVER hardcode this - load from env.
|
|
239
|
+
*/
|
|
240
|
+
privateKey: string;
|
|
241
|
+
/** The plaintext prompt to send. UTF-8 encoded before encryption. */
|
|
242
|
+
prompt: string;
|
|
243
|
+
/** Inference model tag. Default: `"llama3-8b"`. */
|
|
244
|
+
model?: string;
|
|
245
|
+
/**
|
|
246
|
+
* Streaming callback invoked once per decrypted relay chunk. Use for live
|
|
247
|
+
* stdout / UI updates. Optional - the final `answer` is returned either way.
|
|
248
|
+
*/
|
|
249
|
+
onChunk?: (chunk: string, totalSoFar: string) => void;
|
|
250
|
+
/** Retry count if a worker stalls. Default 2 (so up to 3 paid attempts). */
|
|
251
|
+
maxRetries?: number;
|
|
252
|
+
/** How long to wait for JobCompleted before declaring the worker stalled. Default 120s. */
|
|
253
|
+
jobCompletedTimeoutMs?: number;
|
|
254
|
+
/**
|
|
255
|
+
* WebSocket constructor. In a browser this is auto-detected from
|
|
256
|
+
* `globalThis.WebSocket`. In Node, pass `WS` from the `ws` package
|
|
257
|
+
* (`import WS from "ws"`) - `ws` is not a hard dep of this SDK.
|
|
258
|
+
*/
|
|
259
|
+
WebSocket?: WebSocketCtor;
|
|
260
|
+
/** Override the relay URL (defaults to `wss://relay.<network>.lightchain.ai/ws`). */
|
|
261
|
+
relayUrl?: string;
|
|
262
|
+
/**
|
|
263
|
+
* Override the consumer-api gateway URL. Defaults to a network-derived URL.
|
|
264
|
+
* Useful for tests / mirrors / proxying through your own backend.
|
|
265
|
+
*/
|
|
266
|
+
gatewayUrl?: string;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* One call, key-in / answer-out encrypted inference. Builds viem clients,
|
|
270
|
+
* runs the SIWE handshake, opens the encrypted session, submits + decrypts,
|
|
271
|
+
* and returns. Same proof chain (`createSession`, `submitJob`, `jobCompleted`)
|
|
272
|
+
* as the lower-level `runInference`.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* ```ts
|
|
276
|
+
* import { runInferenceWithKey } from "lightnode-sdk";
|
|
277
|
+
* import WS from "ws";
|
|
278
|
+
*
|
|
279
|
+
* const { answer, txs } = await runInferenceWithKey({
|
|
280
|
+
* network: "testnet",
|
|
281
|
+
* privateKey: process.env.PRIVATE_KEY!,
|
|
282
|
+
* prompt: "Reply with a one-sentence fun fact about the ocean.",
|
|
283
|
+
* WebSocket: WS, // omit in the browser
|
|
284
|
+
* });
|
|
285
|
+
*
|
|
286
|
+
* console.log(answer);
|
|
287
|
+
* ```
|
|
288
|
+
*/
|
|
289
|
+
export declare function runInferenceWithKey(args: RunInferenceWithKeyArgs): Promise<RunInferenceResult>;
|
package/dist/inference.js
CHANGED
|
@@ -190,7 +190,7 @@ function topicAsUint(hex) {
|
|
|
190
190
|
return BigInt(hex);
|
|
191
191
|
}
|
|
192
192
|
async function runOneAttempt(args, attempt) {
|
|
193
|
-
const { prompt, gateway, wallet, publicClient, network, model = "llama3-8b", onChunk, jobCompletedTimeoutMs =
|
|
193
|
+
const { prompt, gateway, wallet, publicClient, network, model = "llama3-8b", onChunk, jobCompletedTimeoutMs = 120000, } = args;
|
|
194
194
|
const WS = pickWebSocket(args.WebSocket);
|
|
195
195
|
const relayUrl = args.relayUrl ?? `wss://relay.${network.id}.lightchain.ai/ws`;
|
|
196
196
|
// 1. prepareSession
|
|
@@ -326,6 +326,11 @@ async function runOneAttempt(args, attempt) {
|
|
|
326
326
|
throw new Error("JobSubmitted log missing in submitJob receipt");
|
|
327
327
|
const jobId = topicAsUint(jobLog.topics[1]);
|
|
328
328
|
// 6. wait for JobCompleted
|
|
329
|
+
// KEY INVARIANT: the real result is the WS-delivered, session-key-decrypted
|
|
330
|
+
// ciphertext. JobCompleted is an explorer pointer (the worker's commit-result
|
|
331
|
+
// tx). If the WS already produced an answer (chunks.length > 0) we MUST
|
|
332
|
+
// accept it even if the on-chain event hasn't landed - throwing stalled here
|
|
333
|
+
// wipes a delivered answer on retry, which reads as "the call returned nothing".
|
|
329
334
|
const deadline = Date.now() + jobCompletedTimeoutMs;
|
|
330
335
|
const jobIdTopic = (`0x${jobId.toString(16).padStart(64, "0")}`);
|
|
331
336
|
let completed = null;
|
|
@@ -338,8 +343,14 @@ async function runOneAttempt(args, attempt) {
|
|
|
338
343
|
});
|
|
339
344
|
completed =
|
|
340
345
|
logs.find((l) => l.topics[0] === JOB_COMPLETED_TOPIC && l.topics[1] === jobIdTopic) ?? null;
|
|
346
|
+
if (completed)
|
|
347
|
+
break;
|
|
348
|
+
// If the WS already delivered the answer, stop polling - no point burning
|
|
349
|
+
// more time on a confirmation that only serves as an explorer link.
|
|
350
|
+
if (chunks.length > 0)
|
|
351
|
+
break;
|
|
341
352
|
}
|
|
342
|
-
if (!completed) {
|
|
353
|
+
if (!completed && chunks.length === 0) {
|
|
343
354
|
try {
|
|
344
355
|
ws.close();
|
|
345
356
|
}
|
|
@@ -363,7 +374,11 @@ async function runOneAttempt(args, attempt) {
|
|
|
363
374
|
}
|
|
364
375
|
return {
|
|
365
376
|
answer: chunks.join(""),
|
|
366
|
-
|
|
377
|
+
// completed may be null when the answer arrived via the WS but JobCompleted
|
|
378
|
+
// hasn't landed on-chain yet. The decrypted answer is still authentic
|
|
379
|
+
// (session-key bound); callers can poll for the event later if they want
|
|
380
|
+
// the explorer-link form of the proof.
|
|
381
|
+
txs: { createSession: createTx, submitJob: submitTx, jobCompleted: completed?.transactionHash ?? null },
|
|
367
382
|
worker: prepared.createSessionArgs.worker,
|
|
368
383
|
sessionId,
|
|
369
384
|
jobId,
|
|
@@ -419,3 +434,100 @@ export async function runInference(args) {
|
|
|
419
434
|
}
|
|
420
435
|
/** Re-export the typed errors at this layer so a single import covers everything. */
|
|
421
436
|
export { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
|
|
437
|
+
import { NETWORKS } from "./networks.js";
|
|
438
|
+
import { GatewayClient as GatewayClientCtor, consumerGatewayUrl as consumerGatewayUrlFn } from "./gateway.js";
|
|
439
|
+
import { GatewayAuthError } from "./errors.js";
|
|
440
|
+
import { createPublicClient as viemCreatePublicClient, createWalletClient as viemCreateWalletClient, http as viemHttp } from "viem";
|
|
441
|
+
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
|
|
442
|
+
/**
|
|
443
|
+
* One call, key-in / answer-out encrypted inference. Builds viem clients,
|
|
444
|
+
* runs the SIWE handshake, opens the encrypted session, submits + decrypts,
|
|
445
|
+
* and returns. Same proof chain (`createSession`, `submitJob`, `jobCompleted`)
|
|
446
|
+
* as the lower-level `runInference`.
|
|
447
|
+
*
|
|
448
|
+
* @example
|
|
449
|
+
* ```ts
|
|
450
|
+
* import { runInferenceWithKey } from "lightnode-sdk";
|
|
451
|
+
* import WS from "ws";
|
|
452
|
+
*
|
|
453
|
+
* const { answer, txs } = await runInferenceWithKey({
|
|
454
|
+
* network: "testnet",
|
|
455
|
+
* privateKey: process.env.PRIVATE_KEY!,
|
|
456
|
+
* prompt: "Reply with a one-sentence fun fact about the ocean.",
|
|
457
|
+
* WebSocket: WS, // omit in the browser
|
|
458
|
+
* });
|
|
459
|
+
*
|
|
460
|
+
* console.log(answer);
|
|
461
|
+
* ```
|
|
462
|
+
*/
|
|
463
|
+
export async function runInferenceWithKey(args) {
|
|
464
|
+
// Resolve the network config and validate the key shape up front so a
|
|
465
|
+
// mistyped key fails BEFORE we touch the RPC or the gateway.
|
|
466
|
+
const network = typeof args.network === "string" ? NETWORKS[args.network] : args.network;
|
|
467
|
+
if (!network)
|
|
468
|
+
throw new Error(`unknown network: ${String(args.network)}`);
|
|
469
|
+
const networkId = (typeof args.network === "string" ? args.network : "mainnet");
|
|
470
|
+
const key = args.privateKey?.trim();
|
|
471
|
+
if (!key || !key.startsWith("0x") || key.length !== 66) {
|
|
472
|
+
throw new Error("runInferenceWithKey: privateKey must be a 0x-prefixed 32-byte hex string");
|
|
473
|
+
}
|
|
474
|
+
const account = viemPrivateKeyToAccount(key);
|
|
475
|
+
const chain = {
|
|
476
|
+
id: network.chainId,
|
|
477
|
+
name: network.label,
|
|
478
|
+
nativeCurrency: { name: "LCAI", symbol: "LCAI", decimals: 18 },
|
|
479
|
+
rpcUrls: { default: { http: [network.rpc] } },
|
|
480
|
+
};
|
|
481
|
+
// Keep viem's real types here so signMessage / etc. are typed. The MinimalX
|
|
482
|
+
// casts only happen at the runInference() call site below.
|
|
483
|
+
const publicClient = viemCreatePublicClient({ transport: viemHttp(network.rpc), chain });
|
|
484
|
+
const wallet = viemCreateWalletClient({ account, transport: viemHttp(network.rpc), chain });
|
|
485
|
+
// One-shot SIWE handshake. We do this inline (rather than re-export it) so
|
|
486
|
+
// the caller doesn't need a second import; in browsers + Node it works the
|
|
487
|
+
// same against the consumer-api gateway.
|
|
488
|
+
const gwBase = args.gatewayUrl ?? consumerGatewayUrlFn(networkId);
|
|
489
|
+
const chRes = await fetch(`${gwBase}/api/auth/challenge?address=${account.address}`, {
|
|
490
|
+
headers: { Accept: "application/json" },
|
|
491
|
+
});
|
|
492
|
+
if (!chRes.ok)
|
|
493
|
+
throw new GatewayAuthError(chRes.status, await chRes.text());
|
|
494
|
+
const ch = (await chRes.json());
|
|
495
|
+
if (!ch.message)
|
|
496
|
+
throw new GatewayAuthError(chRes.status, "auth challenge returned no message");
|
|
497
|
+
const signature = await wallet.signMessage({ account, message: ch.message });
|
|
498
|
+
const verifyRes = await fetch(`${gwBase}/api/auth/verify`, {
|
|
499
|
+
method: "POST",
|
|
500
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
501
|
+
body: JSON.stringify({ message: ch.message, signature }),
|
|
502
|
+
});
|
|
503
|
+
if (!verifyRes.ok)
|
|
504
|
+
throw new GatewayAuthError(verifyRes.status, await verifyRes.text());
|
|
505
|
+
const verify = (await verifyRes.json());
|
|
506
|
+
if (!verify.token)
|
|
507
|
+
throw new GatewayAuthError(verifyRes.status, "auth verify returned no token");
|
|
508
|
+
const gateway = new GatewayClientCtor({ network: networkId, bearer: verify.token, baseUrl: args.gatewayUrl });
|
|
509
|
+
// Pick a WebSocket: the browser global if present, otherwise the caller-
|
|
510
|
+
// supplied ctor. We deliberately do NOT try to dynamic-import "ws" - it
|
|
511
|
+
// isn't a hard dep, and a bundler trying to resolve it would fail noisily.
|
|
512
|
+
const wsCtor = args.WebSocket ??
|
|
513
|
+
(typeof globalThis !== "undefined" && globalThis.WebSocket
|
|
514
|
+
? globalThis.WebSocket
|
|
515
|
+
: undefined);
|
|
516
|
+
if (!wsCtor) {
|
|
517
|
+
throw new Error("runInferenceWithKey: no WebSocket constructor available. In Node, install `ws` and pass it: " +
|
|
518
|
+
"`import WS from 'ws'; runInferenceWithKey({ WebSocket: WS, ... })`");
|
|
519
|
+
}
|
|
520
|
+
return runInference({
|
|
521
|
+
prompt: args.prompt,
|
|
522
|
+
gateway,
|
|
523
|
+
wallet: wallet,
|
|
524
|
+
publicClient: publicClient,
|
|
525
|
+
network,
|
|
526
|
+
model: args.model,
|
|
527
|
+
onChunk: args.onChunk,
|
|
528
|
+
maxRetries: args.maxRetries,
|
|
529
|
+
jobCompletedTimeoutMs: args.jobCompletedTimeoutMs,
|
|
530
|
+
WebSocket: wsCtor,
|
|
531
|
+
relayUrl: args.relayUrl,
|
|
532
|
+
});
|
|
533
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
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",
|