lightnode-sdk 0.4.2 → 0.4.4
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 +58 -0
- package/dist/inference.js +118 -10
- 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
|
@@ -229,3 +229,61 @@ export interface RunInferenceResult {
|
|
|
229
229
|
export declare function runInference(args: RunInferenceArgs): Promise<RunInferenceResult>;
|
|
230
230
|
/** Re-export the typed errors at this layer so a single import covers everything. */
|
|
231
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
|
@@ -326,16 +326,31 @@ 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
|
-
//
|
|
330
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
//
|
|
329
|
+
// The actual *result* is the WS-delivered, session-key-decrypted ciphertext.
|
|
330
|
+
// JobCompleted is an explorer pointer (the worker's commit-result tx).
|
|
331
|
+
// Polling rules:
|
|
332
|
+
// - No chunks yet: poll for the full deadline (default 120s). Still nothing
|
|
333
|
+
// -> throw stalled so the outer loop can retry with a different worker.
|
|
334
|
+
// - Chunks arrived: keep polling for a 45s grace window after the FIRST
|
|
335
|
+
// chunk. Workers usually commit JobCompleted within ~10s of broadcasting
|
|
336
|
+
// the answer, so 45s is generous. If it still doesn't land, surface the
|
|
337
|
+
// answer with txs.jobCompleted=null (the answer is still session-key
|
|
338
|
+
// authentic; the on-chain proof can be polled for separately by callers).
|
|
334
339
|
const deadline = Date.now() + jobCompletedTimeoutMs;
|
|
340
|
+
const POST_CHUNKS_GRACE_MS = 45000;
|
|
341
|
+
const waitStart = Date.now();
|
|
342
|
+
let firstChunkAt = chunks.length > 0 ? waitStart : null;
|
|
335
343
|
const jobIdTopic = (`0x${jobId.toString(16).padStart(64, "0")}`);
|
|
336
344
|
let completed = null;
|
|
337
|
-
while (!completed
|
|
345
|
+
while (!completed) {
|
|
346
|
+
const now = Date.now();
|
|
347
|
+
if (now >= deadline)
|
|
348
|
+
break;
|
|
349
|
+
if (firstChunkAt != null && now - firstChunkAt >= POST_CHUNKS_GRACE_MS)
|
|
350
|
+
break;
|
|
338
351
|
await new Promise((res) => setTimeout(res, 3000));
|
|
352
|
+
if (firstChunkAt == null && chunks.length > 0)
|
|
353
|
+
firstChunkAt = Date.now();
|
|
339
354
|
const logs = await publicClient.getLogs({
|
|
340
355
|
address: network.jobRegistry,
|
|
341
356
|
fromBlock: submitReceipt.blockNumber,
|
|
@@ -345,10 +360,6 @@ async function runOneAttempt(args, attempt) {
|
|
|
345
360
|
logs.find((l) => l.topics[0] === JOB_COMPLETED_TOPIC && l.topics[1] === jobIdTopic) ?? null;
|
|
346
361
|
if (completed)
|
|
347
362
|
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;
|
|
352
363
|
}
|
|
353
364
|
if (!completed && chunks.length === 0) {
|
|
354
365
|
try {
|
|
@@ -434,3 +445,100 @@ export async function runInference(args) {
|
|
|
434
445
|
}
|
|
435
446
|
/** Re-export the typed errors at this layer so a single import covers everything. */
|
|
436
447
|
export { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
|
|
448
|
+
import { NETWORKS } from "./networks.js";
|
|
449
|
+
import { GatewayClient as GatewayClientCtor, consumerGatewayUrl as consumerGatewayUrlFn } from "./gateway.js";
|
|
450
|
+
import { GatewayAuthError } from "./errors.js";
|
|
451
|
+
import { createPublicClient as viemCreatePublicClient, createWalletClient as viemCreateWalletClient, http as viemHttp } from "viem";
|
|
452
|
+
import { privateKeyToAccount as viemPrivateKeyToAccount } from "viem/accounts";
|
|
453
|
+
/**
|
|
454
|
+
* One call, key-in / answer-out encrypted inference. Builds viem clients,
|
|
455
|
+
* runs the SIWE handshake, opens the encrypted session, submits + decrypts,
|
|
456
|
+
* and returns. Same proof chain (`createSession`, `submitJob`, `jobCompleted`)
|
|
457
|
+
* as the lower-level `runInference`.
|
|
458
|
+
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```ts
|
|
461
|
+
* import { runInferenceWithKey } from "lightnode-sdk";
|
|
462
|
+
* import WS from "ws";
|
|
463
|
+
*
|
|
464
|
+
* const { answer, txs } = await runInferenceWithKey({
|
|
465
|
+
* network: "testnet",
|
|
466
|
+
* privateKey: process.env.PRIVATE_KEY!,
|
|
467
|
+
* prompt: "Reply with a one-sentence fun fact about the ocean.",
|
|
468
|
+
* WebSocket: WS, // omit in the browser
|
|
469
|
+
* });
|
|
470
|
+
*
|
|
471
|
+
* console.log(answer);
|
|
472
|
+
* ```
|
|
473
|
+
*/
|
|
474
|
+
export async function runInferenceWithKey(args) {
|
|
475
|
+
// Resolve the network config and validate the key shape up front so a
|
|
476
|
+
// mistyped key fails BEFORE we touch the RPC or the gateway.
|
|
477
|
+
const network = typeof args.network === "string" ? NETWORKS[args.network] : args.network;
|
|
478
|
+
if (!network)
|
|
479
|
+
throw new Error(`unknown network: ${String(args.network)}`);
|
|
480
|
+
const networkId = (typeof args.network === "string" ? args.network : "mainnet");
|
|
481
|
+
const key = args.privateKey?.trim();
|
|
482
|
+
if (!key || !key.startsWith("0x") || key.length !== 66) {
|
|
483
|
+
throw new Error("runInferenceWithKey: privateKey must be a 0x-prefixed 32-byte hex string");
|
|
484
|
+
}
|
|
485
|
+
const account = viemPrivateKeyToAccount(key);
|
|
486
|
+
const chain = {
|
|
487
|
+
id: network.chainId,
|
|
488
|
+
name: network.label,
|
|
489
|
+
nativeCurrency: { name: "LCAI", symbol: "LCAI", decimals: 18 },
|
|
490
|
+
rpcUrls: { default: { http: [network.rpc] } },
|
|
491
|
+
};
|
|
492
|
+
// Keep viem's real types here so signMessage / etc. are typed. The MinimalX
|
|
493
|
+
// casts only happen at the runInference() call site below.
|
|
494
|
+
const publicClient = viemCreatePublicClient({ transport: viemHttp(network.rpc), chain });
|
|
495
|
+
const wallet = viemCreateWalletClient({ account, transport: viemHttp(network.rpc), chain });
|
|
496
|
+
// One-shot SIWE handshake. We do this inline (rather than re-export it) so
|
|
497
|
+
// the caller doesn't need a second import; in browsers + Node it works the
|
|
498
|
+
// same against the consumer-api gateway.
|
|
499
|
+
const gwBase = args.gatewayUrl ?? consumerGatewayUrlFn(networkId);
|
|
500
|
+
const chRes = await fetch(`${gwBase}/api/auth/challenge?address=${account.address}`, {
|
|
501
|
+
headers: { Accept: "application/json" },
|
|
502
|
+
});
|
|
503
|
+
if (!chRes.ok)
|
|
504
|
+
throw new GatewayAuthError(chRes.status, await chRes.text());
|
|
505
|
+
const ch = (await chRes.json());
|
|
506
|
+
if (!ch.message)
|
|
507
|
+
throw new GatewayAuthError(chRes.status, "auth challenge returned no message");
|
|
508
|
+
const signature = await wallet.signMessage({ account, message: ch.message });
|
|
509
|
+
const verifyRes = await fetch(`${gwBase}/api/auth/verify`, {
|
|
510
|
+
method: "POST",
|
|
511
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
512
|
+
body: JSON.stringify({ message: ch.message, signature }),
|
|
513
|
+
});
|
|
514
|
+
if (!verifyRes.ok)
|
|
515
|
+
throw new GatewayAuthError(verifyRes.status, await verifyRes.text());
|
|
516
|
+
const verify = (await verifyRes.json());
|
|
517
|
+
if (!verify.token)
|
|
518
|
+
throw new GatewayAuthError(verifyRes.status, "auth verify returned no token");
|
|
519
|
+
const gateway = new GatewayClientCtor({ network: networkId, bearer: verify.token, baseUrl: args.gatewayUrl });
|
|
520
|
+
// Pick a WebSocket: the browser global if present, otherwise the caller-
|
|
521
|
+
// supplied ctor. We deliberately do NOT try to dynamic-import "ws" - it
|
|
522
|
+
// isn't a hard dep, and a bundler trying to resolve it would fail noisily.
|
|
523
|
+
const wsCtor = args.WebSocket ??
|
|
524
|
+
(typeof globalThis !== "undefined" && globalThis.WebSocket
|
|
525
|
+
? globalThis.WebSocket
|
|
526
|
+
: undefined);
|
|
527
|
+
if (!wsCtor) {
|
|
528
|
+
throw new Error("runInferenceWithKey: no WebSocket constructor available. In Node, install `ws` and pass it: " +
|
|
529
|
+
"`import WS from 'ws'; runInferenceWithKey({ WebSocket: WS, ... })`");
|
|
530
|
+
}
|
|
531
|
+
return runInference({
|
|
532
|
+
prompt: args.prompt,
|
|
533
|
+
gateway,
|
|
534
|
+
wallet: wallet,
|
|
535
|
+
publicClient: publicClient,
|
|
536
|
+
network,
|
|
537
|
+
model: args.model,
|
|
538
|
+
onChunk: args.onChunk,
|
|
539
|
+
maxRetries: args.maxRetries,
|
|
540
|
+
jobCompletedTimeoutMs: args.jobCompletedTimeoutMs,
|
|
541
|
+
WebSocket: wsCtor,
|
|
542
|
+
relayUrl: args.relayUrl,
|
|
543
|
+
});
|
|
544
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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",
|