lightnode-sdk 0.5.0 → 0.6.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/README.md +285 -178
- package/dist/agent.d.ts +122 -0
- package/dist/agent.js +176 -0
- package/dist/batch.d.ts +88 -0
- package/dist/batch.js +102 -0
- package/dist/bridge.js +1 -1
- package/dist/cli.js +16 -2
- package/dist/dao.d.ts +32 -7
- package/dist/dao.js +17 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.js +7 -1
- package/dist/inference.d.ts +12 -0
- package/dist/inference.js +8 -2
- package/dist/networks.js +9 -0
- package/dist/types.d.ts +10 -0
- package/package.json +1 -1
package/dist/agent.js
ADDED
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { runInferenceWithKey } from "./inference.js";
|
|
2
|
+
/**
|
|
3
|
+
* ReAct-style agent on top of `runInferenceWithKey`. Each iteration:
|
|
4
|
+
* 1. The SDK sends a system prompt that lists tools + the JSON tool-call
|
|
5
|
+
* format to the model, plus the running transcript.
|
|
6
|
+
* 2. The model emits either a tool call (`<tool>name {"k":"v"}</tool>`)
|
|
7
|
+
* or a final answer (`<answer>...</answer>`).
|
|
8
|
+
* 3. The SDK parses, runs the tool, threads the result back, repeats.
|
|
9
|
+
*
|
|
10
|
+
* Designed for smaller open models (llama3-8b). The protocol is simple
|
|
11
|
+
* string markers, not native function calling, so it works on any model
|
|
12
|
+
* the LightChain network exposes.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* import { Agent } from "lightnode-sdk";
|
|
17
|
+
*
|
|
18
|
+
* const agent = new Agent({
|
|
19
|
+
* network: "testnet",
|
|
20
|
+
* privateKey: process.env.PRIVATE_KEY!,
|
|
21
|
+
* model: "llama3-8b",
|
|
22
|
+
* system: "You are a careful research assistant.",
|
|
23
|
+
* tools: [
|
|
24
|
+
* {
|
|
25
|
+
* name: "add",
|
|
26
|
+
* description: "Add two integers and return the sum.",
|
|
27
|
+
* args: { a: "first integer", b: "second integer" },
|
|
28
|
+
* handler: ({ a, b }) => Number(a) + Number(b),
|
|
29
|
+
* },
|
|
30
|
+
* ],
|
|
31
|
+
* maxIterations: 3,
|
|
32
|
+
* });
|
|
33
|
+
*
|
|
34
|
+
* const { answer, steps } = await agent.run("What is 17 + 25?");
|
|
35
|
+
* console.log(answer); // "42"
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export class Agent {
|
|
39
|
+
constructor(opts) {
|
|
40
|
+
if (!opts.tools || opts.tools.length === 0) {
|
|
41
|
+
throw new Error("Agent: at least one tool is required (use runInferenceWithKey for plain inference)");
|
|
42
|
+
}
|
|
43
|
+
// Tool names must be unique - the SDK matches model-emitted names by
|
|
44
|
+
// string and a dup would silently route to the wrong handler.
|
|
45
|
+
const seen = new Set();
|
|
46
|
+
for (const t of opts.tools) {
|
|
47
|
+
if (seen.has(t.name))
|
|
48
|
+
throw new Error(`Agent: duplicate tool name "${t.name}"`);
|
|
49
|
+
seen.add(t.name);
|
|
50
|
+
}
|
|
51
|
+
this.opts = opts;
|
|
52
|
+
}
|
|
53
|
+
async run(userMessage) {
|
|
54
|
+
const maxIter = Math.max(1, this.opts.maxIterations ?? 5);
|
|
55
|
+
const steps = [];
|
|
56
|
+
const transcript = [`User: ${userMessage}`];
|
|
57
|
+
const system = this.buildSystemPrompt();
|
|
58
|
+
let iterations = 0;
|
|
59
|
+
// Strip Agent-only fields before passing through to runInferenceWithKey.
|
|
60
|
+
const passthrough = { ...this.opts };
|
|
61
|
+
delete passthrough.tools;
|
|
62
|
+
delete passthrough.maxIterations;
|
|
63
|
+
delete passthrough.onStep;
|
|
64
|
+
delete passthrough.system;
|
|
65
|
+
while (iterations < maxIter) {
|
|
66
|
+
iterations++;
|
|
67
|
+
const prompt = `${system}\n\n${transcript.join("\n\n")}\n\nAssistant:`;
|
|
68
|
+
const { answer: raw } = await runInferenceWithKey({
|
|
69
|
+
...passthrough,
|
|
70
|
+
prompt,
|
|
71
|
+
});
|
|
72
|
+
const parsed = parseAgentOutput(raw);
|
|
73
|
+
if (parsed.kind === "answer") {
|
|
74
|
+
const step = { kind: "answer", text: parsed.text };
|
|
75
|
+
steps.push(step);
|
|
76
|
+
this.opts.onStep?.(step);
|
|
77
|
+
return { answer: parsed.text, steps, iterations, hitLimit: false };
|
|
78
|
+
}
|
|
79
|
+
if (parsed.kind === "thought") {
|
|
80
|
+
const step = { kind: "thought", text: parsed.text };
|
|
81
|
+
steps.push(step);
|
|
82
|
+
this.opts.onStep?.(step);
|
|
83
|
+
transcript.push(`Assistant: <think>${parsed.text}</think>`);
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
// tool_call: look up + execute
|
|
87
|
+
const tool = this.opts.tools.find((t) => t.name === parsed.name);
|
|
88
|
+
if (!tool) {
|
|
89
|
+
const step = {
|
|
90
|
+
kind: "tool_error",
|
|
91
|
+
name: parsed.name,
|
|
92
|
+
args: parsed.args,
|
|
93
|
+
error: `unknown tool "${parsed.name}"`,
|
|
94
|
+
};
|
|
95
|
+
steps.push(step);
|
|
96
|
+
this.opts.onStep?.(step);
|
|
97
|
+
transcript.push(`Assistant: <tool>${parsed.name} ${JSON.stringify(parsed.args)}</tool>`);
|
|
98
|
+
transcript.push(`Observation: error: unknown tool "${parsed.name}"`);
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const result = await tool.handler(parsed.args);
|
|
103
|
+
const step = { kind: "tool_call", name: parsed.name, args: parsed.args, result };
|
|
104
|
+
steps.push(step);
|
|
105
|
+
this.opts.onStep?.(step);
|
|
106
|
+
transcript.push(`Assistant: <tool>${parsed.name} ${JSON.stringify(parsed.args)}</tool>`);
|
|
107
|
+
transcript.push(`Observation: ${JSON.stringify(result)}`);
|
|
108
|
+
}
|
|
109
|
+
catch (err) {
|
|
110
|
+
const msg = err.message ?? String(err);
|
|
111
|
+
const step = {
|
|
112
|
+
kind: "tool_error",
|
|
113
|
+
name: parsed.name,
|
|
114
|
+
args: parsed.args,
|
|
115
|
+
error: msg,
|
|
116
|
+
};
|
|
117
|
+
steps.push(step);
|
|
118
|
+
this.opts.onStep?.(step);
|
|
119
|
+
transcript.push(`Assistant: <tool>${parsed.name} ${JSON.stringify(parsed.args)}</tool>`);
|
|
120
|
+
transcript.push(`Observation: error: ${msg}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
return { answer: "", steps, iterations, hitLimit: true };
|
|
124
|
+
}
|
|
125
|
+
buildSystemPrompt() {
|
|
126
|
+
const base = this.opts.system?.trim() ?? "You are a helpful assistant.";
|
|
127
|
+
const toolDocs = this.opts.tools
|
|
128
|
+
.map((t) => {
|
|
129
|
+
const argList = Object.entries(t.args)
|
|
130
|
+
.map(([k, v]) => ` "${k}": <${v}>`)
|
|
131
|
+
.join(",\n");
|
|
132
|
+
return `- ${t.name}: ${t.description}\n Call as: <tool>${t.name} {\n${argList}\n }</tool>`;
|
|
133
|
+
})
|
|
134
|
+
.join("\n");
|
|
135
|
+
return `${base}
|
|
136
|
+
|
|
137
|
+
You have access to these tools:
|
|
138
|
+
${toolDocs}
|
|
139
|
+
|
|
140
|
+
To call a tool, write EXACTLY one line:
|
|
141
|
+
<tool>tool_name {"arg":"value"}</tool>
|
|
142
|
+
|
|
143
|
+
After the tool runs, the user will reply with: Observation: <json>
|
|
144
|
+
|
|
145
|
+
When you are done, respond with EXACTLY:
|
|
146
|
+
<answer>your final reply to the user</answer>
|
|
147
|
+
|
|
148
|
+
Use one tool at a time. Do not invent tools. Keep observations short.`;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Parse the model's raw output into either a tool call, a final answer, or
|
|
153
|
+
* a thought (everything else). Tolerant of whitespace and the model
|
|
154
|
+
* forgetting to close a tag. Visible for testing.
|
|
155
|
+
*/
|
|
156
|
+
export function parseAgentOutput(raw) {
|
|
157
|
+
const answer = raw.match(/<answer>([\s\S]*?)(?:<\/answer>|$)/i);
|
|
158
|
+
if (answer)
|
|
159
|
+
return { kind: "answer", text: answer[1].trim() };
|
|
160
|
+
const tool = raw.match(/<tool>\s*([a-zA-Z_][\w-]*)\s*(\{[\s\S]*?\})\s*(?:<\/tool>|$)/i);
|
|
161
|
+
if (tool) {
|
|
162
|
+
const name = tool[1];
|
|
163
|
+
let args = {};
|
|
164
|
+
try {
|
|
165
|
+
const parsed = JSON.parse(tool[2]);
|
|
166
|
+
if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
|
|
167
|
+
args = parsed;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
// best-effort: leave args empty so the handler sees missing fields
|
|
172
|
+
}
|
|
173
|
+
return { kind: "tool_call", name, args };
|
|
174
|
+
}
|
|
175
|
+
return { kind: "thought", text: raw.trim() };
|
|
176
|
+
}
|
package/dist/batch.d.ts
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { type RunInferenceWithKeyArgs, type RunInferenceResult } from "./inference.js";
|
|
2
|
+
/**
|
|
3
|
+
* One slot in a batch run. `prompt` is the only required field; everything
|
|
4
|
+
* else inherits from the shared base args passed to `runInferenceBatch`.
|
|
5
|
+
* A per-prompt `model` / `system` overrides the base when set.
|
|
6
|
+
*/
|
|
7
|
+
export interface BatchPrompt {
|
|
8
|
+
prompt: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
system?: string;
|
|
11
|
+
/** Opaque per-slot tag returned on the result so the caller can correlate. */
|
|
12
|
+
tag?: string;
|
|
13
|
+
}
|
|
14
|
+
/** One slot's outcome. `error` is set on failure; `result` on success. Always exactly one. */
|
|
15
|
+
export type BatchResult = {
|
|
16
|
+
index: number;
|
|
17
|
+
tag?: string;
|
|
18
|
+
prompt: string;
|
|
19
|
+
result: RunInferenceResult;
|
|
20
|
+
error: null;
|
|
21
|
+
} | {
|
|
22
|
+
index: number;
|
|
23
|
+
tag?: string;
|
|
24
|
+
prompt: string;
|
|
25
|
+
result: null;
|
|
26
|
+
error: {
|
|
27
|
+
name: string;
|
|
28
|
+
message: string;
|
|
29
|
+
jobId?: string;
|
|
30
|
+
};
|
|
31
|
+
};
|
|
32
|
+
export interface RunInferenceBatchArgs extends Omit<RunInferenceWithKeyArgs, "prompt"> {
|
|
33
|
+
/** Prompts to run. Each slot becomes one independent encrypted inference. */
|
|
34
|
+
prompts: ReadonlyArray<string | BatchPrompt>;
|
|
35
|
+
/**
|
|
36
|
+
* Shared system prompt prepended to every slot. A per-slot `system`
|
|
37
|
+
* overrides this. Implemented by prefixing the prompt at submit time
|
|
38
|
+
* (the underlying inference call has no native system role).
|
|
39
|
+
*/
|
|
40
|
+
system?: string;
|
|
41
|
+
/**
|
|
42
|
+
* Max parallel inferences in flight (default 4). Each slot is one
|
|
43
|
+
* createSession + submitJob pair, so this caps the concurrent wallet
|
|
44
|
+
* nonce pressure as well as the gateway socket fan-out.
|
|
45
|
+
*/
|
|
46
|
+
concurrency?: number;
|
|
47
|
+
/**
|
|
48
|
+
* Called when ANY slot resolves (success or failure), useful for live
|
|
49
|
+
* progress UI. Fires in order of completion, not submission.
|
|
50
|
+
*/
|
|
51
|
+
onSlotComplete?: (result: BatchResult) => void;
|
|
52
|
+
/**
|
|
53
|
+
* When set, abort all in-flight + queued slots if this signal fires.
|
|
54
|
+
* Slots already submitted on chain still settle on chain; the SDK just
|
|
55
|
+
* stops awaiting their answer.
|
|
56
|
+
*/
|
|
57
|
+
signal?: AbortSignal;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Run many prompts as parallel encrypted inferences against the same
|
|
61
|
+
* worker pool. Returns a stable array indexed by submission order.
|
|
62
|
+
*
|
|
63
|
+
* Slots fail independently - a stalled worker on one prompt does not
|
|
64
|
+
* cancel the others. The caller decides what to retry from the per-slot
|
|
65
|
+
* error.
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```ts
|
|
69
|
+
* import { runInferenceBatch } from "lightnode-sdk";
|
|
70
|
+
*
|
|
71
|
+
* const results = await runInferenceBatch({
|
|
72
|
+
* network: "testnet",
|
|
73
|
+
* privateKey: process.env.PRIVATE_KEY!,
|
|
74
|
+
* model: "llama3-8b",
|
|
75
|
+
* prompts: ["one", "two", "three"],
|
|
76
|
+
* concurrency: 3,
|
|
77
|
+
* onSlotComplete: ({ index, result, error }) => {
|
|
78
|
+
* console.log(`#${index}:`, error?.message ?? result?.answer);
|
|
79
|
+
* },
|
|
80
|
+
* });
|
|
81
|
+
*
|
|
82
|
+
* for (const r of results) {
|
|
83
|
+
* if (r.error) console.warn(`slot ${r.index} failed:`, r.error.message);
|
|
84
|
+
* else console.log(r.result.answer);
|
|
85
|
+
* }
|
|
86
|
+
* ```
|
|
87
|
+
*/
|
|
88
|
+
export declare function runInferenceBatch(args: RunInferenceBatchArgs): Promise<BatchResult[]>;
|
package/dist/batch.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { runInferenceWithKey } from "./inference.js";
|
|
2
|
+
import { StalledWorkerError } from "./errors.js";
|
|
3
|
+
/**
|
|
4
|
+
* Run many prompts as parallel encrypted inferences against the same
|
|
5
|
+
* worker pool. Returns a stable array indexed by submission order.
|
|
6
|
+
*
|
|
7
|
+
* Slots fail independently - a stalled worker on one prompt does not
|
|
8
|
+
* cancel the others. The caller decides what to retry from the per-slot
|
|
9
|
+
* error.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* import { runInferenceBatch } from "lightnode-sdk";
|
|
14
|
+
*
|
|
15
|
+
* const results = await runInferenceBatch({
|
|
16
|
+
* network: "testnet",
|
|
17
|
+
* privateKey: process.env.PRIVATE_KEY!,
|
|
18
|
+
* model: "llama3-8b",
|
|
19
|
+
* prompts: ["one", "two", "three"],
|
|
20
|
+
* concurrency: 3,
|
|
21
|
+
* onSlotComplete: ({ index, result, error }) => {
|
|
22
|
+
* console.log(`#${index}:`, error?.message ?? result?.answer);
|
|
23
|
+
* },
|
|
24
|
+
* });
|
|
25
|
+
*
|
|
26
|
+
* for (const r of results) {
|
|
27
|
+
* if (r.error) console.warn(`slot ${r.index} failed:`, r.error.message);
|
|
28
|
+
* else console.log(r.result.answer);
|
|
29
|
+
* }
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export async function runInferenceBatch(args) {
|
|
33
|
+
const concurrency = Math.max(1, args.concurrency ?? 4);
|
|
34
|
+
const slots = args.prompts.map((p) => (typeof p === "string" ? { prompt: p } : p));
|
|
35
|
+
const out = new Array(slots.length);
|
|
36
|
+
let nextIndex = 0;
|
|
37
|
+
let aborted = false;
|
|
38
|
+
const onAbort = () => {
|
|
39
|
+
aborted = true;
|
|
40
|
+
};
|
|
41
|
+
if (args.signal) {
|
|
42
|
+
if (args.signal.aborted)
|
|
43
|
+
aborted = true;
|
|
44
|
+
else
|
|
45
|
+
args.signal.addEventListener("abort", onAbort, { once: true });
|
|
46
|
+
}
|
|
47
|
+
const runOne = async (index) => {
|
|
48
|
+
if (aborted) {
|
|
49
|
+
out[index] = {
|
|
50
|
+
index,
|
|
51
|
+
tag: slots[index].tag,
|
|
52
|
+
prompt: slots[index].prompt,
|
|
53
|
+
result: null,
|
|
54
|
+
error: { name: "AbortError", message: "batch aborted before this slot ran" },
|
|
55
|
+
};
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
const slot = slots[index];
|
|
59
|
+
try {
|
|
60
|
+
const system = slot.system ?? args.system;
|
|
61
|
+
const finalPrompt = system ? `${system.trim()}\n\n${slot.prompt}` : slot.prompt;
|
|
62
|
+
// Strip batch-only fields before passing through to runInferenceWithKey.
|
|
63
|
+
const passthrough = { ...args };
|
|
64
|
+
delete passthrough.prompts;
|
|
65
|
+
delete passthrough.system;
|
|
66
|
+
delete passthrough.concurrency;
|
|
67
|
+
delete passthrough.onSlotComplete;
|
|
68
|
+
const result = await runInferenceWithKey({
|
|
69
|
+
...passthrough,
|
|
70
|
+
prompt: finalPrompt,
|
|
71
|
+
model: slot.model ?? args.model,
|
|
72
|
+
});
|
|
73
|
+
out[index] = { index, tag: slot.tag, prompt: slot.prompt, result, error: null };
|
|
74
|
+
}
|
|
75
|
+
catch (err) {
|
|
76
|
+
const e = err;
|
|
77
|
+
const jobId = e instanceof StalledWorkerError ? e.jobId.toString() : undefined;
|
|
78
|
+
out[index] = {
|
|
79
|
+
index,
|
|
80
|
+
tag: slot.tag,
|
|
81
|
+
prompt: slot.prompt,
|
|
82
|
+
result: null,
|
|
83
|
+
error: { name: e.name ?? "Error", message: e.message, jobId },
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
args.onSlotComplete?.(out[index]);
|
|
87
|
+
};
|
|
88
|
+
// N parallel workers pulling off the queue. Order of completion is
|
|
89
|
+
// non-deterministic, order of OUTPUT is stable via `index`.
|
|
90
|
+
const workers = new Array(Math.min(concurrency, slots.length)).fill(0).map(async () => {
|
|
91
|
+
while (true) {
|
|
92
|
+
const i = nextIndex++;
|
|
93
|
+
if (i >= slots.length)
|
|
94
|
+
return;
|
|
95
|
+
await runOne(i);
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
await Promise.all(workers);
|
|
99
|
+
if (args.signal)
|
|
100
|
+
args.signal.removeEventListener("abort", onAbort);
|
|
101
|
+
return out;
|
|
102
|
+
}
|
package/dist/bridge.js
CHANGED
|
@@ -36,7 +36,7 @@ export const BRIDGE_ROUTE = {
|
|
|
36
36
|
underlying: "0x9cA8530CA349c966Fe9ef903Df17a75B8A778927", // LCAI ERC-20
|
|
37
37
|
mailbox: "0x287cf56E5b1435Ae59BF9Ce6443F055A0321a063",
|
|
38
38
|
explorer: "https://etherscan.io",
|
|
39
|
-
rpc: "https://
|
|
39
|
+
rpc: "https://ethereum-rpc.publicnode.com",
|
|
40
40
|
label: "Ethereum",
|
|
41
41
|
},
|
|
42
42
|
"lightchain-mainnet": {
|
package/dist/cli.js
CHANGED
|
@@ -279,14 +279,28 @@ async function main() {
|
|
|
279
279
|
// Live read against Ethereum mainnet. We use viem's HTTP transport
|
|
280
280
|
// via a minimal inline client (no ethers dep). This is the only
|
|
281
281
|
// ecosystem read that needs a live RPC, so we wire it lazily.
|
|
282
|
+
// Default to publicnode (reliable, no key required). Caller can
|
|
283
|
+
// override with --rpc <url> if they have a higher-quality endpoint.
|
|
282
284
|
const { createPublicClient, http } = await import("viem");
|
|
283
|
-
const ethRpc = flag("--rpc") ?? "https://
|
|
285
|
+
const ethRpc = flag("--rpc") ?? "https://ethereum-rpc.publicnode.com";
|
|
284
286
|
const pub = createPublicClient({ transport: http(ethRpc) });
|
|
285
287
|
// The DAO ctor accepts a structurally-typed MinimalPublicClient; viem's
|
|
286
288
|
// PublicClient satisfies it. The unknown cast is the standard SDK pattern
|
|
287
289
|
// for keeping the public API free of viem generic noise.
|
|
288
290
|
const dao = new DAO(pub, "ethereum");
|
|
289
|
-
|
|
291
|
+
let cfg;
|
|
292
|
+
try {
|
|
293
|
+
cfg = await dao.config();
|
|
294
|
+
}
|
|
295
|
+
catch (e) {
|
|
296
|
+
const msg = e.message ?? String(e);
|
|
297
|
+
// Short-circuit the viem stack trace: a friendly one-liner is what
|
|
298
|
+
// the operator wants when the public RPC is having a bad day.
|
|
299
|
+
die(`dao config: RPC call failed against ${ethRpc}.\n` +
|
|
300
|
+
` Hint: try a different Ethereum RPC with --rpc <url>.\n` +
|
|
301
|
+
` Public options: https://ethereum-rpc.publicnode.com, https://rpc.ankr.com/eth, https://eth.merkle.io\n` +
|
|
302
|
+
` Underlying: ${msg.split("\n")[0]}`);
|
|
303
|
+
}
|
|
290
304
|
console.log(JSON.stringify({
|
|
291
305
|
votingDelayBlocks: cfg.votingDelayBlocks.toString(),
|
|
292
306
|
votingPeriodBlocks: cfg.votingPeriodBlocks.toString(),
|
package/dist/dao.d.ts
CHANGED
|
@@ -17,22 +17,47 @@
|
|
|
17
17
|
* This module covers the OZ Governor v5 surface: state machine, propose,
|
|
18
18
|
* castVote, queue, execute. Plus convenience reads of the constants.
|
|
19
19
|
*/
|
|
20
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Both deployed DAOs we know about:
|
|
22
|
+
*
|
|
23
|
+
* - `ethereum` LCAIGovernor on Ethereum mainnet. Token holders
|
|
24
|
+
* wrap LCAI ERC-20 into LCAI-Ballots (IVotes) at
|
|
25
|
+
* https://ballots.lightchain.ai, then vote /
|
|
26
|
+
* propose / queue / execute through the Governor.
|
|
27
|
+
* Proposal threshold: 140,000 LCAI wrapped.
|
|
28
|
+
*
|
|
29
|
+
* - `lightchain` LightChainGovernor on LightChain mainnet (chain
|
|
30
|
+
* 9200). Uses the NativeVotes precompile at
|
|
31
|
+
* 0x...0001001 - native LCAI itself acts as the
|
|
32
|
+
* voting token, no wrapping needed. Governor is
|
|
33
|
+
* an upgradeable proxy controlled by the
|
|
34
|
+
* TimelockController.
|
|
35
|
+
*
|
|
36
|
+
* Addresses sourced from LightChain's official "Mainnet Contract Addresses"
|
|
37
|
+
* page.
|
|
38
|
+
*/
|
|
39
|
+
export type DaoChain = "ethereum" | "lightchain";
|
|
21
40
|
export interface DaoAddresses {
|
|
22
41
|
chainId: number;
|
|
23
|
-
|
|
42
|
+
label: string;
|
|
43
|
+
/** OZ Governor contract (proxy when behind an upgradeable pattern). */
|
|
24
44
|
governor: `0x${string}`;
|
|
25
45
|
/** Timelock controller. queue/execute dispatch through this. */
|
|
26
46
|
timelock: `0x${string}`;
|
|
27
|
-
/** ERC-20 wrapped as IVotes
|
|
28
|
-
ballots: `0x${string}
|
|
29
|
-
/** LCAI ERC-20
|
|
30
|
-
token: `0x${string}
|
|
47
|
+
/** ERC-20 wrapped as IVotes (null when the chain uses a NativeVotes precompile). */
|
|
48
|
+
ballots: `0x${string}` | null;
|
|
49
|
+
/** Underlying governance token (LCAI ERC-20 on Ethereum, native LCAI on LightChain). */
|
|
50
|
+
token: `0x${string}` | null;
|
|
31
51
|
/** Treasury contract holding DAO funds. */
|
|
32
52
|
treasury: `0x${string}`;
|
|
53
|
+
/** Optional UI link (where regular users wrap / view proposals). */
|
|
54
|
+
wrapperUi?: string;
|
|
33
55
|
explorer: string;
|
|
34
56
|
}
|
|
35
|
-
/**
|
|
57
|
+
/**
|
|
58
|
+
* Confirmed deployment addresses. Each entry is what an SDK user passes to
|
|
59
|
+
* `new DAO(client, chainKey, walletClient?)`.
|
|
60
|
+
*/
|
|
36
61
|
export declare const DAO_ADDRESSES: Record<DaoChain, DaoAddresses>;
|
|
37
62
|
/**
|
|
38
63
|
* The 8-state OZ Governor v5 enum. The string label is what most builders
|
package/dist/dao.js
CHANGED
|
@@ -18,17 +18,33 @@
|
|
|
18
18
|
* castVote, queue, execute. Plus convenience reads of the constants.
|
|
19
19
|
*/
|
|
20
20
|
import { parseAbi } from "viem";
|
|
21
|
-
/**
|
|
21
|
+
/**
|
|
22
|
+
* Confirmed deployment addresses. Each entry is what an SDK user passes to
|
|
23
|
+
* `new DAO(client, chainKey, walletClient?)`.
|
|
24
|
+
*/
|
|
22
25
|
export const DAO_ADDRESSES = {
|
|
23
26
|
ethereum: {
|
|
24
27
|
chainId: 1,
|
|
28
|
+
label: "Ethereum mainnet",
|
|
25
29
|
governor: "0x6dfa413B5900a1a7947BC75E68AbBA093cB2492d",
|
|
26
30
|
timelock: "0xbE1c37F8C4DA77dD06F4A8AC5098Ec70273093d7",
|
|
27
31
|
ballots: "0x75F3D01c4D960FE986A598B7954A3b786B29cE49",
|
|
28
32
|
token: "0x9cA8530CA349c966Fe9ef903Df17a75B8A778927",
|
|
29
33
|
treasury: "0x07A716a551E5f4CA7D6C71Da9dF1cb1429Dba826",
|
|
34
|
+
wrapperUi: "https://ballots.lightchain.ai",
|
|
30
35
|
explorer: "https://etherscan.io",
|
|
31
36
|
},
|
|
37
|
+
lightchain: {
|
|
38
|
+
chainId: 9200,
|
|
39
|
+
label: "LightChain mainnet",
|
|
40
|
+
governor: "0x262E9f9232933E8565253918db703baD58DE93aB",
|
|
41
|
+
timelock: "0x79e571420c5473Ca9b0FCd599B1b0062D7793c97",
|
|
42
|
+
// Native voting via the genesis predeploy precompile; no separate wrapping token.
|
|
43
|
+
ballots: "0x0000000000000000000000000000000000001001",
|
|
44
|
+
token: null,
|
|
45
|
+
treasury: "0x786eDe8C42Ca54E54c9dCECa9b30052CF4743389",
|
|
46
|
+
explorer: "https://mainnet.lightscan.app",
|
|
47
|
+
},
|
|
32
48
|
};
|
|
33
49
|
/**
|
|
34
50
|
* The 8-state OZ Governor v5 enum. The string label is what most builders
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,8 @@ import { fromWei } from "./subgraph.js";
|
|
|
3
3
|
import { aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv } from "./analytics.js";
|
|
4
4
|
import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, runInferenceStream } from "./inference.js";
|
|
5
5
|
import { Conversation, chat } from "./chat.js";
|
|
6
|
+
import { runInferenceBatch } from "./batch.js";
|
|
7
|
+
import { Agent, parseAgentOutput } from "./agent.js";
|
|
6
8
|
import { preflight as workerPreflight, watch as workerWatch } from "./worker.js";
|
|
7
9
|
import { Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer } from "./bridge.js";
|
|
8
10
|
import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI } from "./dao.js";
|
|
@@ -88,11 +90,13 @@ export declare class LightNode {
|
|
|
88
90
|
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
89
91
|
* may pin an older minor than the local install command suggests).
|
|
90
92
|
*/
|
|
91
|
-
export declare const SDK_VERSION = "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, };
|
|
93
|
+
export declare const SDK_VERSION = "0.6.0";
|
|
94
|
+
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, 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, };
|
|
93
95
|
export type { BearerSource, GatewayClientOptions, SelectSessionResult, PrepareSessionResult, UploadBlobResult, SessionTokenResult } from "./gateway.js";
|
|
94
96
|
export type { SessionPreparation, RunInferenceArgs, RunInferenceResult, RunInferenceWithKeyArgs, RunInferenceStreamResult } from "./inference.js";
|
|
95
97
|
export type { ChatRole, ChatMessage, ConversationOptions, ConversationSendResult } from "./chat.js";
|
|
98
|
+
export type { BatchPrompt, BatchResult, RunInferenceBatchArgs } from "./batch.js";
|
|
99
|
+
export type { AgentTool, AgentStep, AgentOptions, AgentRunResult } from "./agent.js";
|
|
96
100
|
export type { WorkerPreflightArgs, WorkerPreflightResult, WorkerWatchOptions, WorkerEventKind, WorkerEvent, WorkerWatchHandle } from "./worker.js";
|
|
97
101
|
export type { BridgeChain, BridgeEndpoints, BridgeTransferArgs } from "./bridge.js";
|
|
98
102
|
export type { DaoChain, DaoAddresses, ProposalSummary, DaoConfig } from "./dao.js";
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,8 @@ import { isRegistered } from "./onchain.js";
|
|
|
4
4
|
import { aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, } from "./analytics.js";
|
|
5
5
|
import { modelId as computeModelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, runInference, runInferenceWithKey, runInferenceStream, } from "./inference.js";
|
|
6
6
|
import { Conversation, chat } from "./chat.js";
|
|
7
|
+
import { runInferenceBatch } from "./batch.js";
|
|
8
|
+
import { Agent, parseAgentOutput } from "./agent.js";
|
|
7
9
|
import { preflight as workerPreflight, watch as workerWatch } from "./worker.js";
|
|
8
10
|
import { Bridge, BRIDGE_ROUTE, HYPERLANE_ROUTER_ABI, ERC20_ABI, addressToBytes32, quoteBridgeFee, bridgeableBalance, bridgeAllowance, approveBridge, bridgeTransfer, } from "./bridge.js";
|
|
9
11
|
import { DAO, DAO_ADDRESSES, ProposalState, PROPOSAL_STATE_LABEL, VoteSupport, GOVERNOR_ABI, VOTES_ABI, } from "./dao.js";
|
|
@@ -143,7 +145,7 @@ export class LightNode {
|
|
|
143
145
|
* (especially in registry-proxy environments like StackBlitz where lockfiles
|
|
144
146
|
* may pin an older minor than the local install command suggests).
|
|
145
147
|
*/
|
|
146
|
-
export const SDK_VERSION = "0.
|
|
148
|
+
export const SDK_VERSION = "0.6.0";
|
|
147
149
|
export { NETWORKS, WORKER_REGISTRY, REGISTRY_TOPICS, aggregateModelStats, aggregateWorkerStats, networkAnalytics, modelStatsCsv, workerStatsCsv, workerJobsCsv, fromWei, computeModelId as modelId, estimateJobFee, JOB_REGISTRY_CONSUMER_ABI, consumerGatewayUrl, consumerGatewayHost,
|
|
148
150
|
// v0.3 inference-submit surface (BETA - see README "Submitting inference").
|
|
149
151
|
GatewayClient, GatewayHttpError, prepareSession, submitPrompt, decryptResponse, generateEcdhKeyPair, crypto,
|
|
@@ -155,6 +157,10 @@ runInferenceWithKey,
|
|
|
155
157
|
runInferenceStream,
|
|
156
158
|
// v0.5.0 multi-turn conversation helper (history client-side; one inference per turn).
|
|
157
159
|
Conversation, chat,
|
|
160
|
+
// v0.6.0 batch runner: many prompts, capped parallelism, stable result order.
|
|
161
|
+
runInferenceBatch,
|
|
162
|
+
// v0.6.0 ReAct-style agent: tool calling on any LightChain-hosted model.
|
|
163
|
+
Agent, parseAgentOutput,
|
|
158
164
|
// v0.5.0 worker preflight + watch (one real test inference + status polling).
|
|
159
165
|
workerPreflight, workerWatch,
|
|
160
166
|
// v0.5.0 Bridge SDK (Hyperlane Warp Route wrapper for LCAI <-> Ethereum).
|
package/dist/inference.d.ts
CHANGED
|
@@ -171,6 +171,11 @@ export interface RunInferenceArgs {
|
|
|
171
171
|
* Useful for tests / mirrors.
|
|
172
172
|
*/
|
|
173
173
|
relayUrl?: string;
|
|
174
|
+
/**
|
|
175
|
+
* Cancellation signal. Aborts pending awaits inside the run; in-flight
|
|
176
|
+
* submitJob transactions still settle on chain (the SDK stops listening).
|
|
177
|
+
*/
|
|
178
|
+
signal?: AbortSignal;
|
|
174
179
|
}
|
|
175
180
|
export interface RunInferenceResult {
|
|
176
181
|
/** The decrypted, fully-assembled model answer. */
|
|
@@ -264,6 +269,13 @@ export interface RunInferenceWithKeyArgs {
|
|
|
264
269
|
* Useful for tests / mirrors / proxying through your own backend.
|
|
265
270
|
*/
|
|
266
271
|
gatewayUrl?: string;
|
|
272
|
+
/**
|
|
273
|
+
* Cancellation signal. Aborts the SIWE handshake and stops awaiting the
|
|
274
|
+
* relay; in-flight submitJob transactions still settle on chain (the SDK
|
|
275
|
+
* just stops listening). Throws `Error("aborted")` synchronously if the
|
|
276
|
+
* signal is already fired when the call starts.
|
|
277
|
+
*/
|
|
278
|
+
signal?: AbortSignal;
|
|
267
279
|
}
|
|
268
280
|
/**
|
|
269
281
|
* One call, key-in / answer-out encrypted inference. Builds viem clients,
|
package/dist/inference.js
CHANGED
|
@@ -428,12 +428,14 @@ export async function runInference(args) {
|
|
|
428
428
|
const maxRetries = args.maxRetries ?? 2;
|
|
429
429
|
const stalled = [];
|
|
430
430
|
for (let attempt = 1; attempt <= maxRetries + 1; attempt++) {
|
|
431
|
+
if (args.signal?.aborted)
|
|
432
|
+
throw new Error("runInference: aborted");
|
|
431
433
|
try {
|
|
432
434
|
const result = await runOneAttempt(args, attempt);
|
|
433
435
|
return { ...result, stalled };
|
|
434
436
|
}
|
|
435
437
|
catch (err) {
|
|
436
|
-
if (err instanceof StalledWorkerError && attempt <= maxRetries) {
|
|
438
|
+
if (err instanceof StalledWorkerError && attempt <= maxRetries && !args.signal?.aborted) {
|
|
437
439
|
stalled.push({ jobId: err.jobId, worker: err.worker, submitTx: err.submitTx });
|
|
438
440
|
continue;
|
|
439
441
|
}
|
|
@@ -482,6 +484,9 @@ export async function runInferenceWithKey(args) {
|
|
|
482
484
|
if (!key || !key.startsWith("0x") || key.length !== 66) {
|
|
483
485
|
throw new Error("runInferenceWithKey: privateKey must be a 0x-prefixed 32-byte hex string");
|
|
484
486
|
}
|
|
487
|
+
if (args.signal?.aborted) {
|
|
488
|
+
throw new Error("runInferenceWithKey: aborted before start");
|
|
489
|
+
}
|
|
485
490
|
const account = viemPrivateKeyToAccount(key);
|
|
486
491
|
const chain = {
|
|
487
492
|
id: network.chainId,
|
|
@@ -504,7 +509,7 @@ export async function runInferenceWithKey(args) {
|
|
|
504
509
|
// looks like a CORS or undici-level reachability error.
|
|
505
510
|
const fetchOrFail = async (url, init, label) => {
|
|
506
511
|
try {
|
|
507
|
-
return await fetch(url, init);
|
|
512
|
+
return await fetch(url, { ...init, signal: args.signal });
|
|
508
513
|
}
|
|
509
514
|
catch (err) {
|
|
510
515
|
const cause = err.cause;
|
|
@@ -574,6 +579,7 @@ export async function runInferenceWithKey(args) {
|
|
|
574
579
|
jobCompletedTimeoutMs: args.jobCompletedTimeoutMs,
|
|
575
580
|
WebSocket: wsCtor,
|
|
576
581
|
relayUrl: args.relayUrl,
|
|
582
|
+
signal: args.signal,
|
|
577
583
|
});
|
|
578
584
|
}
|
|
579
585
|
/**
|
package/dist/networks.js
CHANGED
|
@@ -17,6 +17,12 @@ export const NETWORKS = {
|
|
|
17
17
|
aiConfig: "0x24D11533C354092ed6E18b964257819cE78Ce77D",
|
|
18
18
|
jobRegistry: "0xfB15F90298e4CcD7106E76fFB5e520315cC42B0b",
|
|
19
19
|
minStakeLcai: 50000,
|
|
20
|
+
// Sourced from LightChain's official "Mainnet Contract Addresses" page.
|
|
21
|
+
feePool: "0x0000000000000000000000000000000000001004",
|
|
22
|
+
nativeVotes: "0x0000000000000000000000000000000000001001",
|
|
23
|
+
governor: "0x262E9f9232933E8565253918db703baD58DE93aB",
|
|
24
|
+
timelock: "0x79e571420c5473Ca9b0FCd599B1b0062D7793c97",
|
|
25
|
+
treasury: "0x786eDe8C42Ca54E54c9dCECa9b30052CF4743389",
|
|
20
26
|
},
|
|
21
27
|
testnet: {
|
|
22
28
|
id: "testnet",
|
|
@@ -30,6 +36,9 @@ export const NETWORKS = {
|
|
|
30
36
|
aiConfig: "0xeCF4Ca5Ba6D97ae586993e170764a1E92231b67e",
|
|
31
37
|
jobRegistry: "0x531b3a87c5d785441b9cf55b98169f20fd9056a7",
|
|
32
38
|
minStakeLcai: 5000,
|
|
39
|
+
// Same genesis predeploys as mainnet (chain bytecode is identical).
|
|
40
|
+
feePool: "0x0000000000000000000000000000000000001004",
|
|
41
|
+
nativeVotes: "0x0000000000000000000000000000000000001001",
|
|
33
42
|
},
|
|
34
43
|
};
|
|
35
44
|
/** WorkerRegistry genesis predeploy (same address on both networks). */
|
package/dist/types.d.ts
CHANGED
|
@@ -12,6 +12,16 @@ export interface NetworkConfig {
|
|
|
12
12
|
aiConfig: string;
|
|
13
13
|
jobRegistry: string;
|
|
14
14
|
minStakeLcai: number;
|
|
15
|
+
/** FeePool genesis predeploy. Where per-job fees accumulate before payout. */
|
|
16
|
+
feePool?: string;
|
|
17
|
+
/** NativeVotes precompile (mainnet only - backs LightChainGovernor). */
|
|
18
|
+
nativeVotes?: string;
|
|
19
|
+
/** On-chain DAO governor (mainnet only today). */
|
|
20
|
+
governor?: string;
|
|
21
|
+
/** Governor timelock controller (mainnet only). */
|
|
22
|
+
timelock?: string;
|
|
23
|
+
/** DAO-controlled treasury (mainnet only). */
|
|
24
|
+
treasury?: string;
|
|
15
25
|
}
|
|
16
26
|
export interface Worker {
|
|
17
27
|
id: string;
|