lightnode-sdk 0.4.8 → 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/index.d.ts CHANGED
@@ -1,7 +1,12 @@
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, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey } from "./inference.js";
4
+ import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, runInferenceStream } from "./inference.js";
5
+ import { Conversation, chat } from "./chat.js";
6
+ import { preflight as workerPreflight, watch as workerWatch } from "./worker.js";
7
+ import { Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer } from "./bridge.js";
8
+ import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI } from "./dao.js";
9
+ import { OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL } from "./onchain-models.js";
5
10
  import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker } from "./errors.js";
6
11
  import { GatewayClient, GatewayHttpError } from "./gateway.js";
7
12
  import * as crypto from "./crypto.js";
@@ -45,6 +50,23 @@ export declare class LightNode {
45
50
  isRegistered(address: string): Promise<boolean | null>;
46
51
  /** Settled worker earnings in whole LCAI (from total_earned wei). */
47
52
  getEarningsLcai(address: string): Promise<number>;
53
+ /**
54
+ * One job's current status, classified for builders deciding whether to
55
+ * retry / claim a refund / accept the answer. `category` is the
56
+ * builder-friendly label; `raw` is the indexer's literal state string.
57
+ * Null when the indexer has never seen the job (still pending propagation).
58
+ */
59
+ getJobStatus(jobId: string | bigint): Promise<{
60
+ id: string;
61
+ raw: string;
62
+ category: "submitted" | "in-flight" | "completed" | "stalled" | "disputed" | "resolved" | "unknown";
63
+ worker: string | null;
64
+ model: string | null;
65
+ submittedAt: number | null;
66
+ completedAt: number | null;
67
+ workerShareLcai: number;
68
+ refundable: boolean;
69
+ } | null>;
48
70
  /** keccak256 of a model tag (its on-chain + indexer id). */
49
71
  modelId(tag: string): `0x${string}`;
50
72
  /** On-chain inference fee for a model, in whole LCAI (what submitJob must be paid). */
@@ -66,8 +88,13 @@ export declare class LightNode {
66
88
  * (especially in registry-proxy environments like StackBlitz where lockfiles
67
89
  * may pin an older minor than the local install command suggests).
68
90
  */
69
- export declare const SDK_VERSION = "0.4.8";
70
- export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
91
+ export declare const SDK_VERSION = "0.5.0";
92
+ export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, Conversation, chat, workerPreflight, workerWatch, Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
71
93
  export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
72
- export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs } from "./inference.js";
94
+ export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
95
+ export type { ChatRole, ChatMessage, ConversationOptions, ConversationSendResult } from "./chat.js";
96
+ export type { WorkerPreflightArgs, WorkerPreflightResult, WorkerWatchOptions, WorkerEventKind, WorkerEvent, WorkerWatchHandle } from "./worker.js";
97
+ export type { BridgeChain, BridgeEndpoints, BridgeTransferArgs } from "./bridge.js";
98
+ export type { DaoChain, DaoAddresses, ProposalSummary, DaoConfig } from "./dao.js";
99
+ export type { BaseModel, ModelVariant, AccessTier, AccessPolicy, Benchmark, OnchainModelRegistryOptions } from "./onchain-models.js";
73
100
  export type { NetworkId, NetworkConfig, Worker, Job, ModelInfo, NetworkStats, ModelStat, WorkerStat, NetworkAnalytics };
package/dist/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  import { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS } from "./networks.js";
2
- import { fetchWorker, fetchWorkerJobs, fetchRecentJobs, fetchModels, fetchWorkers, summarize, fromWei, } from "./subgraph.js";
2
+ import { fetchWorker, fetchWorkerJobs, fetchRecentJobs, fetchJob, 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, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, } from "./inference.js";
5
+ import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, runInferenceStream, } from "./inference.js";
6
+ import { Conversation, chat } from "./chat.js";
7
+ import { preflight as workerPreflight, watch as workerWatch } from "./worker.js";
8
+ import { Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, } from "./bridge.js";
9
+ import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, } from "./dao.js";
10
+ import { OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, } from "./onchain-models.js";
6
11
  import { StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, } from "./errors.js";
7
12
  import { GatewayClient, GatewayHttpError } from "./gateway.js";
8
13
  import * as crypto from "./crypto.js";
@@ -72,6 +77,48 @@ export class LightNode {
72
77
  const w = await fetchWorker(this.network, address);
73
78
  return w ? fromWei(w.total_earned) : 0;
74
79
  }
80
+ /**
81
+ * One job's current status, classified for builders deciding whether to
82
+ * retry / claim a refund / accept the answer. `category` is the
83
+ * builder-friendly label; `raw` is the indexer's literal state string.
84
+ * Null when the indexer has never seen the job (still pending propagation).
85
+ */
86
+ async getJobStatus(jobId) {
87
+ const j = await fetchJob(this.network, jobId);
88
+ if (!j)
89
+ return null;
90
+ const state = (j.state ?? "").trim();
91
+ const stateLow = state.toLowerCase();
92
+ const category = /completed|released|paid/.test(stateLow)
93
+ ? "completed"
94
+ : /timed.?out|stalled|expired/.test(stateLow)
95
+ ? "stalled"
96
+ : /disputed/.test(stateLow)
97
+ ? "disputed"
98
+ : /resolved/.test(stateLow)
99
+ ? "resolved"
100
+ : /ack/.test(stateLow)
101
+ ? "in-flight"
102
+ : /submitted/.test(stateLow)
103
+ ? "submitted"
104
+ : "unknown";
105
+ // A refund is on the table when the worker accepted the job but never
106
+ // produced an answer within the protocol's dispute window. The protocol's
107
+ // own timeout/dispute pipeline reclaims the fee; this flag is the SDK's
108
+ // builder-facing hint that the on-chain refund call is the right path.
109
+ const refundable = category === "stalled" || category === "disputed";
110
+ return {
111
+ id: j.id,
112
+ raw: state,
113
+ category,
114
+ worker: j.worker ?? null,
115
+ model: j.model_id ?? null,
116
+ submittedAt: j.submitted_at ?? null,
117
+ completedAt: j.completed_at ?? null,
118
+ workerShareLcai: fromWei(j.worker_share),
119
+ refundable,
120
+ };
121
+ }
75
122
  /** keccak256 of a model tag (its on-chain + indexer id). */
76
123
  modelId(tag) {
77
124
  return computeModelId(tag);
@@ -96,11 +143,23 @@ export class LightNode {
96
143
  * (especially in registry-proxy environments like StackBlitz where lockfiles
97
144
  * may pin an older minor than the local install command suggests).
98
145
  */
99
- export const SDK_VERSION = "0.4.8";
146
+ export const SDK_VERSION = "0.5.0";
100
147
  export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
101
148
  // v0.3 inference-submit surface (BETA - see README "Submitting inference").
102
149
  GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
103
150
  // v0.4 high-level orchestrator: one call, full flow.
104
151
  runInference,
105
152
  // v0.4.3 key-in-answer-out shortcut: same flow, no viem/SIWE wiring.
106
- runInferenceWithKey, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
153
+ runInferenceWithKey,
154
+ // v0.4.9 AsyncIterable<string> wrapper around runInferenceWithKey.
155
+ runInferenceStream,
156
+ // v0.5.0 multi-turn conversation helper (history client-side; one inference per turn).
157
+ Conversation, chat,
158
+ // v0.5.0 worker preflight + watch (one real test inference + status polling).
159
+ workerPreflight, workerWatch,
160
+ // v0.5.0 Bridge SDK (Hyperlane Warp Route wrapper for LCAI <-> Ethereum).
161
+ Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer,
162
+ // v0.5.0 DAO SDK (LCAIGovernor wrapper on Ethereum mainnet).
163
+ DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI,
164
+ // v0.5.0 On-chain model registry reader (AIVMModelRegistry + BenchmarkRegistry).
165
+ OnchainModelRegistry, AIVM_MODEL_REGISTRY_ABI, BENCHMARK_REGISTRY_ABI, ModelStatus, MODEL_STATUS_LABEL, StalledWorkerError, OnChainRevertError, RelayTokenTimeoutError, GatewayAuthError, isStalledWorker, };
@@ -287,3 +287,37 @@ export interface RunInferenceWithKeyArgs {
287
287
  * ```
288
288
  */
289
289
  export declare function runInferenceWithKey(args: RunInferenceWithKeyArgs): Promise<RunInferenceResult>;
290
+ export interface RunInferenceStreamResult {
291
+ /** Streamed chunks (decrypted, in arrival order). */
292
+ [Symbol.asyncIterator](): AsyncIterator<string>;
293
+ /**
294
+ * Resolves with the same shape `runInference` returns once the iterator
295
+ * has finished (i.e. you've consumed all chunks). `answer` is the full
296
+ * assembled string. Awaiting this before consuming the iterator hangs;
297
+ * always iterate first or in parallel with another consumer.
298
+ */
299
+ done: Promise<RunInferenceResult>;
300
+ }
301
+ /**
302
+ * Stream-shaped wrapper over `runInferenceWithKey`. Returns an async-iterable
303
+ * of decrypted chunks plus a `done` promise that resolves to the full result
304
+ * once the iteration completes.
305
+ *
306
+ * @example
307
+ * ```ts
308
+ * import { runInferenceStream } from "lightnode-sdk";
309
+ *
310
+ * const stream = runInferenceStream({
311
+ * network: "testnet",
312
+ * privateKey: process.env.PRIVATE_KEY!,
313
+ * prompt: "Write a haiku about decentralized AI.",
314
+ * });
315
+ *
316
+ * for await (const chunk of stream) {
317
+ * process.stdout.write(chunk);
318
+ * }
319
+ * const { txs } = await stream.done;
320
+ * console.log("\n", txs);
321
+ * ```
322
+ */
323
+ export declare function runInferenceStream(args: RunInferenceWithKeyArgs): RunInferenceStreamResult;
package/dist/inference.js CHANGED
@@ -535,16 +535,32 @@ export async function runInferenceWithKey(args) {
535
535
  if (!verify.token)
536
536
  throw new GatewayAuthError(verifyRes.status, "auth verify returned no token");
537
537
  const gateway = new GatewayClientCtor({ network: networkId, bearer: verify.token, baseUrl: args.gatewayUrl ?? gwBase });
538
- // Pick a WebSocket: the browser global if present, otherwise the caller-
539
- // supplied ctor. We deliberately do NOT try to dynamic-import "ws" - it
540
- // isn't a hard dep, and a bundler trying to resolve it would fail noisily.
541
- const wsCtor = args.WebSocket ??
538
+ // Pick a WebSocket: caller-supplied wins, else the browser global, else try
539
+ // to lazy-import the `ws` package (Node). The webpackIgnore hint keeps
540
+ // bundlers from blowing up trying to resolve `ws` for browser bundles where
541
+ // we never reach this branch.
542
+ let wsCtor = args.WebSocket ??
542
543
  (typeof globalThis !== "undefined" && globalThis.WebSocket
543
544
  ? globalThis.WebSocket
544
545
  : undefined);
545
546
  if (!wsCtor) {
546
- throw new Error("runInferenceWithKey: no WebSocket constructor available. In Node, install `ws` and pass it: " +
547
- "`import WS from 'ws'; runInferenceWithKey({ WebSocket: WS, ... })`");
547
+ try {
548
+ // Hide the module name from TS's static resolver via a Function-built
549
+ // dynamic import - otherwise TS errors trying to find @types/ws (we do
550
+ // not want that as a SDK devDep). The webpackIgnore-style comment also
551
+ // keeps browser bundlers from trying to resolve `ws`.
552
+ const dynamicImport = Function("n", "return import(/* webpackIgnore: true */ n)");
553
+ const mod = await dynamicImport("ws");
554
+ wsCtor = mod.default ?? mod.WebSocket;
555
+ }
556
+ catch {
557
+ // `ws` not installed - keep wsCtor undefined and fall into the error below.
558
+ }
559
+ }
560
+ if (!wsCtor) {
561
+ throw new Error("runInferenceWithKey: no WebSocket constructor available. In Node, install `ws` " +
562
+ "(`npm i ws`) - the SDK will pick it up automatically. Or pass one explicitly: " +
563
+ "`import WS from 'ws'; runInferenceWithKey({ WebSocket: WS, ... })`.");
548
564
  }
549
565
  return runInference({
550
566
  prompt: args.prompt,
@@ -560,3 +576,88 @@ export async function runInferenceWithKey(args) {
560
576
  relayUrl: args.relayUrl,
561
577
  });
562
578
  }
579
+ /**
580
+ * Stream-shaped wrapper over `runInferenceWithKey`. Returns an async-iterable
581
+ * of decrypted chunks plus a `done` promise that resolves to the full result
582
+ * once the iteration completes.
583
+ *
584
+ * @example
585
+ * ```ts
586
+ * import { runInferenceStream } from "lightnode-sdk";
587
+ *
588
+ * const stream = runInferenceStream({
589
+ * network: "testnet",
590
+ * privateKey: process.env.PRIVATE_KEY!,
591
+ * prompt: "Write a haiku about decentralized AI.",
592
+ * });
593
+ *
594
+ * for await (const chunk of stream) {
595
+ * process.stdout.write(chunk);
596
+ * }
597
+ * const { txs } = await stream.done;
598
+ * console.log("\n", txs);
599
+ * ```
600
+ */
601
+ export function runInferenceStream(args) {
602
+ // Bounded queue of pending chunks; consumed in order by the iterator. We
603
+ // can't use an unbounded array because the inference may produce chunks
604
+ // faster than the consumer reads them - bounding at 1024 is enough to absorb
605
+ // model-output bursts without unbounded memory growth.
606
+ const queue = [];
607
+ const waiters = [];
608
+ let finished = false;
609
+ let error = null;
610
+ const push = (chunk) => {
611
+ if (waiters.length > 0) {
612
+ const resolve = waiters.shift();
613
+ if (resolve)
614
+ resolve({ value: chunk, done: false });
615
+ }
616
+ else {
617
+ queue.push(chunk);
618
+ }
619
+ };
620
+ const finish = (err = null) => {
621
+ finished = true;
622
+ error = err;
623
+ while (waiters.length > 0) {
624
+ const resolve = waiters.shift();
625
+ if (!resolve)
626
+ continue;
627
+ if (err)
628
+ resolve({ value: undefined, done: true });
629
+ else
630
+ resolve({ value: undefined, done: true });
631
+ }
632
+ };
633
+ const done = runInferenceWithKey({
634
+ ...args,
635
+ onChunk: (chunk) => push(chunk),
636
+ })
637
+ .then((res) => {
638
+ finish(null);
639
+ return res;
640
+ })
641
+ .catch((e) => {
642
+ finish(e);
643
+ throw e;
644
+ });
645
+ return {
646
+ [Symbol.asyncIterator]() {
647
+ return {
648
+ async next() {
649
+ if (queue.length > 0) {
650
+ return { value: queue.shift(), done: false };
651
+ }
652
+ if (finished) {
653
+ if (error)
654
+ throw error;
655
+ return { value: undefined, done: true };
656
+ }
657
+ return new Promise((resolve) => waiters.push(resolve));
658
+ },
659
+ };
660
+ },
661
+ done,
662
+ };
663
+ }