lightnode-sdk 0.9.1 → 0.10.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/add.js +5 -5
- package/dist/gateway.d.ts +18 -4
- package/dist/gateway.js +22 -6
- package/dist/index.d.ts +1 -1
- package/dist/inference.d.ts +28 -2
- package/dist/inference.js +60 -7
- package/package.json +1 -1
package/dist/add.js
CHANGED
|
@@ -1085,17 +1085,17 @@ export default function ChatWeb3() {
|
|
|
1085
1085
|
setBusyStage("");
|
|
1086
1086
|
patchLastAssistant({ text: totalSoFar });
|
|
1087
1087
|
};
|
|
1088
|
+
const onStage = (s: string) => setBusyStage(s);
|
|
1089
|
+
const sendOpts = { onChunk, onStage };
|
|
1088
1090
|
|
|
1089
1091
|
const chat = await ensureSession();
|
|
1090
|
-
|
|
1091
|
-
const result = await chat.send(prompt, { onChunk }).catch(async () => {
|
|
1092
|
+
const result = await chat.send(prompt, sendOpts).catch(async () => {
|
|
1092
1093
|
// Session expired or the worker stopped serving - reopen once and retry.
|
|
1093
1094
|
sessionRef.current = null;
|
|
1094
1095
|
patchLastAssistant({ text: "" });
|
|
1095
1096
|
setBusyStage("Re-opening session...");
|
|
1096
1097
|
const fresh = await ensureSession();
|
|
1097
|
-
|
|
1098
|
-
return fresh.send(prompt, { onChunk });
|
|
1098
|
+
return fresh.send(prompt, sendOpts);
|
|
1099
1099
|
});
|
|
1100
1100
|
|
|
1101
1101
|
patchLastAssistant({
|
|
@@ -1181,7 +1181,7 @@ export default function ChatWeb3() {
|
|
|
1181
1181
|
)
|
|
1182
1182
|
) : (
|
|
1183
1183
|
<div className="animate-pulse-dot pt-1 text-sm text-muted-foreground">
|
|
1184
|
-
{busyStage || "
|
|
1184
|
+
{busyStage || "Thinking..."}
|
|
1185
1185
|
</div>
|
|
1186
1186
|
)}
|
|
1187
1187
|
{t.submitTx && (
|
package/dist/gateway.d.ts
CHANGED
|
@@ -92,8 +92,19 @@ export declare class GatewayClient {
|
|
|
92
92
|
name: string;
|
|
93
93
|
}[];
|
|
94
94
|
}>;
|
|
95
|
-
/** Protected: dispatcher picks a worker for a session and returns its pubkey.
|
|
96
|
-
|
|
95
|
+
/** Protected: dispatcher picks a worker for a session and returns its pubkey.
|
|
96
|
+
* Pass requiredCapabilities (e.g. ["search"]) to bind only to a worker that
|
|
97
|
+
* advertises them. */
|
|
98
|
+
selectSession(modelId: `0x${string}`, opts?: {
|
|
99
|
+
requiredCapabilities?: string[];
|
|
100
|
+
}): Promise<SelectSessionResult>;
|
|
101
|
+
/** Public: union of capabilities advertised by active workers for a model
|
|
102
|
+
* (e.g. ["search"]). Used to gate UI before a session binds. Note: this
|
|
103
|
+
* endpoint may 404 on networks where consumer-api hasn't deployed it yet. */
|
|
104
|
+
getModelCapabilities(modelIdHex: `0x${string}`): Promise<{
|
|
105
|
+
modelId: string;
|
|
106
|
+
capabilities: string[];
|
|
107
|
+
}>;
|
|
97
108
|
/**
|
|
98
109
|
* Protected: hand the dispatcher the encrypted session key it can give the
|
|
99
110
|
* worker, get back the EIP-712 signature authorising on-chain createSession.
|
|
@@ -116,8 +127,11 @@ export declare class GatewayClient {
|
|
|
116
127
|
selectionId?: string;
|
|
117
128
|
requiredCapabilities?: string[];
|
|
118
129
|
}): Promise<PrepareSessionResult>;
|
|
119
|
-
/** Protected: upload an encrypted prompt blob; returns the EIP-4844 blob hash.
|
|
120
|
-
|
|
130
|
+
/** Protected: upload an encrypted prompt blob; returns the EIP-4844 blob hash.
|
|
131
|
+
* Pass { searchEnabled: true } to opt this job into worker-side web search. */
|
|
132
|
+
uploadBlob(base64Data: string, opts?: {
|
|
133
|
+
searchEnabled?: boolean;
|
|
134
|
+
}): Promise<UploadBlobResult>;
|
|
121
135
|
/** Protected: fetch the relay JWT for an active session (202 = pending). */
|
|
122
136
|
getSessionToken(sessionId: number): Promise<SessionTokenResult>;
|
|
123
137
|
private req;
|
package/dist/gateway.js
CHANGED
|
@@ -75,9 +75,21 @@ export class GatewayClient {
|
|
|
75
75
|
getModels() {
|
|
76
76
|
return this.req("GET", "/api/models");
|
|
77
77
|
}
|
|
78
|
-
/** Protected: dispatcher picks a worker for a session and returns its pubkey.
|
|
79
|
-
|
|
80
|
-
|
|
78
|
+
/** Protected: dispatcher picks a worker for a session and returns its pubkey.
|
|
79
|
+
* Pass requiredCapabilities (e.g. ["search"]) to bind only to a worker that
|
|
80
|
+
* advertises them. */
|
|
81
|
+
selectSession(modelId, opts) {
|
|
82
|
+
const body = { modelId };
|
|
83
|
+
if (opts?.requiredCapabilities && opts.requiredCapabilities.length > 0) {
|
|
84
|
+
body.requiredCapabilities = opts.requiredCapabilities;
|
|
85
|
+
}
|
|
86
|
+
return this.req("POST", "/api/sessions/select", body);
|
|
87
|
+
}
|
|
88
|
+
/** Public: union of capabilities advertised by active workers for a model
|
|
89
|
+
* (e.g. ["search"]). Used to gate UI before a session binds. Note: this
|
|
90
|
+
* endpoint may 404 on networks where consumer-api hasn't deployed it yet. */
|
|
91
|
+
getModelCapabilities(modelIdHex) {
|
|
92
|
+
return this.req("GET", `/api/models/${modelIdHex}/capabilities`);
|
|
81
93
|
}
|
|
82
94
|
/**
|
|
83
95
|
* Protected: hand the dispatcher the encrypted session key it can give the
|
|
@@ -92,9 +104,13 @@ export class GatewayClient {
|
|
|
92
104
|
prepareSession(input) {
|
|
93
105
|
return this.req("POST", "/api/sessions/prepare", input);
|
|
94
106
|
}
|
|
95
|
-
/** Protected: upload an encrypted prompt blob; returns the EIP-4844 blob hash.
|
|
96
|
-
|
|
97
|
-
|
|
107
|
+
/** Protected: upload an encrypted prompt blob; returns the EIP-4844 blob hash.
|
|
108
|
+
* Pass { searchEnabled: true } to opt this job into worker-side web search. */
|
|
109
|
+
uploadBlob(base64Data, opts) {
|
|
110
|
+
const body = { data: base64Data };
|
|
111
|
+
if (opts?.searchEnabled === true)
|
|
112
|
+
body.searchEnabled = true;
|
|
113
|
+
return this.req("POST", "/api/blobs", body);
|
|
98
114
|
}
|
|
99
115
|
/** Protected: fetch the relay JWT for an active session (202 = pending). */
|
|
100
116
|
getSessionToken(sessionId) {
|
package/dist/index.d.ts
CHANGED
|
@@ -137,7 +137,7 @@ export declare class LightNode {
|
|
|
137
137
|
export declare const SDK_VERSION = "0.7.20";
|
|
138
138
|
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, resolveJobTransactions, siweSignIn, siweChallenge, siweVerify, fetchWorkerModels, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto, runInference, runInferenceWithKey, runInferenceStream, openSession, runJobOnSession, LightChatSession, Conversation, chat, runInferenceBatch, Agent, parseAgentOutput, 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, WorkerOperator, WORKER_REGISTRY_ABI, JOB_REGISTRY_OPERATOR_ABI, AI_CONFIG_ABI, JOB_STATE, decodeWorkerError, WorkerOpError, isWorkerOpError, };
|
|
139
139
|
export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
|
|
140
|
-
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
|
|
140
|
+
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult, OpenSession, OpenSessionArgs, RunJobOpts, WebSearchSource } from "./inference.js";
|
|
141
141
|
export type { ChatRole, ChatMessage, ConversationOptions, ConversationSendResult } from "./chat.js";
|
|
142
142
|
export type { BatchPrompt, BatchResult, RunInferenceBatchArgs } from "./batch.js";
|
|
143
143
|
export type { AgentTool, AgentStep, AgentOptions, AgentRunResult } from "./agent.js";
|
package/dist/inference.d.ts
CHANGED
|
@@ -73,6 +73,8 @@ export interface SessionPreparation {
|
|
|
73
73
|
expiry: bigint;
|
|
74
74
|
};
|
|
75
75
|
nonce: number;
|
|
76
|
+
/** Capabilities the bound worker advertises (e.g. ["search"]). */
|
|
77
|
+
workerCapabilities?: string[];
|
|
76
78
|
}
|
|
77
79
|
/**
|
|
78
80
|
* Step 1 + 2 of the protocol: ask the gateway which worker to use, generate a
|
|
@@ -82,12 +84,16 @@ export interface SessionPreparation {
|
|
|
82
84
|
* After this returns, the caller submits the on-chain `createSession` tx with
|
|
83
85
|
* `createSessionArgs` and remembers `sessionKey` for the rest of the session.
|
|
84
86
|
*/
|
|
85
|
-
export declare function prepareSession(gateway: GatewayClient, modelTag: string
|
|
87
|
+
export declare function prepareSession(gateway: GatewayClient, modelTag: string, opts?: {
|
|
88
|
+
requiredCapabilities?: string[];
|
|
89
|
+
}): Promise<SessionPreparation>;
|
|
86
90
|
/**
|
|
87
91
|
* Encrypt a UTF-8 prompt with the session key, upload as a blob, and return
|
|
88
92
|
* the EIP-4844 blob hash to pass to `submitJob(sessionId, blobHash)`.
|
|
89
93
|
*/
|
|
90
|
-
export declare function submitPrompt(gateway: GatewayClient, sessionKey: Uint8Array, prompt: string
|
|
94
|
+
export declare function submitPrompt(gateway: GatewayClient, sessionKey: Uint8Array, prompt: string, opts?: {
|
|
95
|
+
searchEnabled?: boolean;
|
|
96
|
+
}): Promise<`0x${string}`>;
|
|
91
97
|
/** Decrypt a worker response (raw bytes or base64 from the relay) with the session key. */
|
|
92
98
|
export declare function decryptResponse(sessionKey: Uint8Array, ciphertext: Uint8Array | string): Promise<string>;
|
|
93
99
|
/** Re-export so callers don't have to import from a second module just for the URL helper. */
|
|
@@ -203,6 +209,8 @@ export interface RunInferenceResult {
|
|
|
203
209
|
worker: `0x${string}`;
|
|
204
210
|
submitTx: `0x${string}`;
|
|
205
211
|
}>;
|
|
212
|
+
/** Web-search citations, when searchEnabled and the worker returned them. */
|
|
213
|
+
sources?: WebSearchSource[];
|
|
206
214
|
}
|
|
207
215
|
/** A live, on-chain session. Open once, then run many jobs through it - each
|
|
208
216
|
* follow-up turn skips SIWE + createSession, leaving just the submitJob tx. */
|
|
@@ -219,6 +227,8 @@ export interface OpenSession {
|
|
|
219
227
|
readonly createTx: `0x${string}`;
|
|
220
228
|
/** Unix seconds when the on-chain session window closes. */
|
|
221
229
|
readonly expirySec: number;
|
|
230
|
+
/** Capabilities the bound worker advertises (e.g. ["search"]). */
|
|
231
|
+
readonly capabilities: string[];
|
|
222
232
|
}
|
|
223
233
|
export interface OpenSessionArgs {
|
|
224
234
|
gateway: GatewayClient;
|
|
@@ -226,6 +236,8 @@ export interface OpenSessionArgs {
|
|
|
226
236
|
publicClient: MinimalPublicClient;
|
|
227
237
|
network: NetworkConfig;
|
|
228
238
|
model?: string;
|
|
239
|
+
/** Bind only to a worker advertising these (e.g. ["search"]). */
|
|
240
|
+
requiredCapabilities?: string[];
|
|
229
241
|
}
|
|
230
242
|
/**
|
|
231
243
|
* prepareSession + the on-chain createSession tx. Do this once, then run many
|
|
@@ -233,8 +245,20 @@ export interface OpenSessionArgs {
|
|
|
233
245
|
* passes or the chosen worker stops serving.
|
|
234
246
|
*/
|
|
235
247
|
export declare function openSession(args: OpenSessionArgs): Promise<OpenSession>;
|
|
248
|
+
/** A web-search citation returned alongside an answer (from the worker's
|
|
249
|
+
* "metadata" relay frame when searchEnabled was set). */
|
|
250
|
+
export interface WebSearchSource {
|
|
251
|
+
position: number;
|
|
252
|
+
title: string;
|
|
253
|
+
url: string;
|
|
254
|
+
description: string;
|
|
255
|
+
}
|
|
236
256
|
export interface RunJobOpts {
|
|
237
257
|
onChunk?: (chunk: string, totalSoFar: string) => void;
|
|
258
|
+
/** Opt this job into worker-side web search (requires a search-capable worker). */
|
|
259
|
+
searchEnabled?: boolean;
|
|
260
|
+
/** Human-readable progress, e.g. "Uploading prompt to chain..." then "Thinking...". */
|
|
261
|
+
onStage?: (stage: string) => void;
|
|
238
262
|
jobCompletedTimeoutMs?: number;
|
|
239
263
|
WebSocket?: WebSocketCtor;
|
|
240
264
|
relayUrl?: string;
|
|
@@ -266,6 +290,8 @@ export declare class LightChatSession {
|
|
|
266
290
|
get sessionId(): bigint;
|
|
267
291
|
get worker(): `0x${string}`;
|
|
268
292
|
get model(): string;
|
|
293
|
+
/** Capabilities the bound worker advertises (e.g. ["search"]). */
|
|
294
|
+
get capabilities(): string[];
|
|
269
295
|
/** true once the on-chain session window has closed; re-open before sending. */
|
|
270
296
|
get expired(): boolean;
|
|
271
297
|
send(prompt: string, opts?: RunJobOpts): Promise<RunInferenceResult>;
|
package/dist/inference.js
CHANGED
|
@@ -73,8 +73,9 @@ export const JOB_REGISTRY_CONSUMER_ABI = [
|
|
|
73
73
|
* After this returns, the caller submits the on-chain `createSession` tx with
|
|
74
74
|
* `createSessionArgs` and remembers `sessionKey` for the rest of the session.
|
|
75
75
|
*/
|
|
76
|
-
export async function prepareSession(gateway, modelTag) {
|
|
76
|
+
export async function prepareSession(gateway, modelTag, opts) {
|
|
77
77
|
const id = modelId(modelTag);
|
|
78
|
+
const requiredCapabilities = opts?.requiredCapabilities;
|
|
78
79
|
// The gateway returns 409 selection_mismatch when a NEWER selectSession()
|
|
79
80
|
// for the same wallet supersedes ours between the select and the prepare.
|
|
80
81
|
// The error message is literally "re-run POST /api/sessions/select", so we
|
|
@@ -98,7 +99,7 @@ export async function prepareSession(gateway, modelTag) {
|
|
|
98
99
|
// call has already superseded ours by the time the gateway processes
|
|
99
100
|
// it. prepareSession 409s when the same happens between select and
|
|
100
101
|
// prepare. The whole select -> prepare flow is one atomic unit.
|
|
101
|
-
const selected = await gateway.selectSession(id);
|
|
102
|
+
const selected = await gateway.selectSession(id, requiredCapabilities ? { requiredCapabilities } : undefined);
|
|
102
103
|
const sessionKey = await generateSessionKey();
|
|
103
104
|
// Workers' pubkeys arrive as base64; disputer's as hex - decodePublicKey
|
|
104
105
|
// accepts either.
|
|
@@ -119,10 +120,12 @@ export async function prepareSession(gateway, modelTag) {
|
|
|
119
120
|
encWorkerKey: bytesToBase64(encWorker),
|
|
120
121
|
encDisputerKey: bytesToBase64(encDisputer),
|
|
121
122
|
...(selected.selectionId ? { selectionId: selected.selectionId } : {}),
|
|
123
|
+
...(requiredCapabilities ? { requiredCapabilities } : {}),
|
|
122
124
|
});
|
|
123
125
|
return {
|
|
124
126
|
sessionKey,
|
|
125
127
|
nonce: prepared.nonce,
|
|
128
|
+
workerCapabilities: selected.workerCapabilities ?? [],
|
|
126
129
|
createSessionArgs: {
|
|
127
130
|
paramsHash: id,
|
|
128
131
|
worker: prepared.worker,
|
|
@@ -148,9 +151,9 @@ export async function prepareSession(gateway, modelTag) {
|
|
|
148
151
|
* Encrypt a UTF-8 prompt with the session key, upload as a blob, and return
|
|
149
152
|
* the EIP-4844 blob hash to pass to `submitJob(sessionId, blobHash)`.
|
|
150
153
|
*/
|
|
151
|
-
export async function submitPrompt(gateway, sessionKey, prompt) {
|
|
154
|
+
export async function submitPrompt(gateway, sessionKey, prompt, opts) {
|
|
152
155
|
const ct = await encrypt(sessionKey, utf8ToBytes(prompt));
|
|
153
|
-
const res = await gateway.uploadBlob(bytesToBase64(ct));
|
|
156
|
+
const res = await gateway.uploadBlob(bytesToBase64(ct), opts?.searchEnabled ? { searchEnabled: true } : undefined);
|
|
154
157
|
const first = res.blobHashes?.[0];
|
|
155
158
|
if (!first)
|
|
156
159
|
throw new Error("gateway returned no blob hashes");
|
|
@@ -228,6 +231,37 @@ function pickWebSocket(provided) {
|
|
|
228
231
|
function topicAsUint(hex) {
|
|
229
232
|
return BigInt(hex);
|
|
230
233
|
}
|
|
234
|
+
/** Parse a decrypted "metadata" relay frame into web-search citations. Mirrors
|
|
235
|
+
* lcai-chat-v2's parseWebSearchSources: requires type === "webSearchSources"
|
|
236
|
+
* and an array of { position:number, url:string, title?, snippet? }. */
|
|
237
|
+
function parseWebSearchSources(payload) {
|
|
238
|
+
let parsed;
|
|
239
|
+
try {
|
|
240
|
+
parsed = JSON.parse(payload);
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
if (!parsed || typeof parsed !== "object")
|
|
246
|
+
return [];
|
|
247
|
+
const obj = parsed;
|
|
248
|
+
if (obj.type !== "webSearchSources" || !Array.isArray(obj.sources))
|
|
249
|
+
return [];
|
|
250
|
+
const out = [];
|
|
251
|
+
for (const s of obj.sources) {
|
|
252
|
+
if (!s || typeof s !== "object")
|
|
253
|
+
continue;
|
|
254
|
+
const src = s;
|
|
255
|
+
const position = typeof src.position === "number" ? src.position : undefined;
|
|
256
|
+
const url = typeof src.url === "string" ? src.url : "";
|
|
257
|
+
const title = typeof src.title === "string" ? src.title : "";
|
|
258
|
+
const snippet = typeof src.snippet === "string" ? src.snippet : "";
|
|
259
|
+
if (!position || !url)
|
|
260
|
+
continue;
|
|
261
|
+
out.push({ position, title: title || url, url, description: snippet });
|
|
262
|
+
}
|
|
263
|
+
return out;
|
|
264
|
+
}
|
|
231
265
|
/**
|
|
232
266
|
* prepareSession + the on-chain createSession tx. Do this once, then run many
|
|
233
267
|
* jobs through the handle with `runJobOnSession`. Re-open when `expirySec`
|
|
@@ -235,7 +269,7 @@ function topicAsUint(hex) {
|
|
|
235
269
|
*/
|
|
236
270
|
export async function openSession(args) {
|
|
237
271
|
const { gateway, wallet, publicClient, network, model = "llama3-8b" } = args;
|
|
238
|
-
const prepared = await prepareSession(gateway, model);
|
|
272
|
+
const prepared = await prepareSession(gateway, model, args.requiredCapabilities ? { requiredCapabilities: args.requiredCapabilities } : undefined);
|
|
239
273
|
const fee = await estimateJobFee(network, model);
|
|
240
274
|
const createTx = await wallet.writeContract({
|
|
241
275
|
address: network.jobRegistry,
|
|
@@ -269,6 +303,7 @@ export async function openSession(args) {
|
|
|
269
303
|
worker: prepared.createSessionArgs.worker,
|
|
270
304
|
createTx,
|
|
271
305
|
expirySec: Number(prepared.createSessionArgs.expiry),
|
|
306
|
+
capabilities: prepared.workerCapabilities ?? [],
|
|
272
307
|
};
|
|
273
308
|
}
|
|
274
309
|
/**
|
|
@@ -277,7 +312,7 @@ export async function openSession(args) {
|
|
|
277
312
|
*/
|
|
278
313
|
export async function runJobOnSession(session, prompt, opts = {}, attempt = 1) {
|
|
279
314
|
const { gateway, wallet, publicClient, network, sessionId, sessionKey, worker, fee, createTx } = session;
|
|
280
|
-
const { onChunk, jobCompletedTimeoutMs = 120000 } = opts;
|
|
315
|
+
const { onChunk, onStage, searchEnabled, jobCompletedTimeoutMs = 120000 } = opts;
|
|
281
316
|
const WS = pickWebSocket(opts.WebSocket);
|
|
282
317
|
const relayUrl = opts.relayUrl ?? `wss://relay.${network.id}.lightchain.ai/ws`;
|
|
283
318
|
// Shim so the job body below can keep referencing prepared.* unchanged.
|
|
@@ -327,6 +362,7 @@ export async function runJobOnSession(session, prompt, opts = {}, attempt = 1) {
|
|
|
327
362
|
setTimeout(() => reject(new Error("relay WebSocket open timeout")), 20000);
|
|
328
363
|
});
|
|
329
364
|
const chunks = [];
|
|
365
|
+
const sources = [];
|
|
330
366
|
let streamDone = false;
|
|
331
367
|
let streamDoneAt = null;
|
|
332
368
|
const handleMessage = async (rawData) => {
|
|
@@ -344,6 +380,18 @@ export async function runJobOnSession(session, prompt, opts = {}, attempt = 1) {
|
|
|
344
380
|
catch {
|
|
345
381
|
return;
|
|
346
382
|
}
|
|
383
|
+
// "metadata" carries web-search citations (sent before the answer stream).
|
|
384
|
+
if (frame.type === "metadata" && frame.payload) {
|
|
385
|
+
try {
|
|
386
|
+
const decoded = await decryptResponse(prepared.sessionKey, frame.payload);
|
|
387
|
+
for (const s of parseWebSearchSources(decoded))
|
|
388
|
+
sources.push(s);
|
|
389
|
+
}
|
|
390
|
+
catch {
|
|
391
|
+
/* ignore malformed metadata */
|
|
392
|
+
}
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
347
395
|
// "complete" marks end-of-stream (it may or may not carry a payload).
|
|
348
396
|
// Record it so the JobCompleted wait can stop promptly instead of polling
|
|
349
397
|
// the full grace window after the answer is already in hand.
|
|
@@ -382,7 +430,8 @@ export async function runJobOnSession(session, prompt, opts = {}, attempt = 1) {
|
|
|
382
430
|
ws.addEventListener("message", (ev) => handleMessage(ev.data));
|
|
383
431
|
}
|
|
384
432
|
// 4. encrypt + upload prompt
|
|
385
|
-
|
|
433
|
+
onStage?.(searchEnabled ? "Searching the web + uploading prompt..." : "Uploading prompt to chain...");
|
|
434
|
+
const promptHash = await submitPrompt(gateway, prepared.sessionKey, prompt, searchEnabled ? { searchEnabled: true } : undefined);
|
|
386
435
|
// 5. submitJob on-chain
|
|
387
436
|
const submitTx = await wallet.writeContract({
|
|
388
437
|
address: network.jobRegistry,
|
|
@@ -406,6 +455,7 @@ export async function runJobOnSession(session, prompt, opts = {}, attempt = 1) {
|
|
|
406
455
|
if (!jobLog)
|
|
407
456
|
throw new Error("JobSubmitted log missing in submitJob receipt");
|
|
408
457
|
const jobId = topicAsUint(jobLog.topics[1]);
|
|
458
|
+
onStage?.("Thinking...");
|
|
409
459
|
// 6. wait for JobCompleted
|
|
410
460
|
// The actual *result* is the WS-delivered, session-key-decrypted ciphertext.
|
|
411
461
|
// JobCompleted is an explorer pointer (the worker's commit-result tx).
|
|
@@ -483,6 +533,7 @@ export async function runJobOnSession(session, prompt, opts = {}, attempt = 1) {
|
|
|
483
533
|
jobId,
|
|
484
534
|
attempts: attempt,
|
|
485
535
|
stalled: [],
|
|
536
|
+
...(sources.length > 0 ? { sources } : {}),
|
|
486
537
|
};
|
|
487
538
|
}
|
|
488
539
|
/** One attempt = open a fresh session, then run one job through it. */
|
|
@@ -526,6 +577,8 @@ export class LightChatSession {
|
|
|
526
577
|
get sessionId() { return this.session.sessionId; }
|
|
527
578
|
get worker() { return this.session.worker; }
|
|
528
579
|
get model() { return this.session.model; }
|
|
580
|
+
/** Capabilities the bound worker advertises (e.g. ["search"]). */
|
|
581
|
+
get capabilities() { return this.session.capabilities; }
|
|
529
582
|
/** true once the on-chain session window has closed; re-open before sending. */
|
|
530
583
|
get expired() { return Date.now() / 1000 >= this.session.expirySec; }
|
|
531
584
|
send(prompt, opts = {}) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lightnode-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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",
|