@elsium-ai/cli 0.11.0 → 0.12.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/cli.js +272 -28
- package/package.json +4 -4
package/dist/cli.js
CHANGED
|
@@ -5814,29 +5814,280 @@ init_src();
|
|
|
5814
5814
|
|
|
5815
5815
|
// ../tools/src/define.ts
|
|
5816
5816
|
init_src();
|
|
5817
|
+
|
|
5818
|
+
// ../tools/src/sandbox/runner.ts
|
|
5819
|
+
init_src();
|
|
5820
|
+
import { Worker } from "node:worker_threads";
|
|
5821
|
+
var WORKER_SCRIPT = `
|
|
5822
|
+
const { parentPort, workerData } = require('node:worker_threads')
|
|
5823
|
+
|
|
5824
|
+
if (!parentPort) throw new Error('worker-runner must be run as a worker thread')
|
|
5825
|
+
|
|
5826
|
+
const { handlerPath } = workerData
|
|
5827
|
+
let handlerPromise = null
|
|
5828
|
+
|
|
5829
|
+
async function loadHandler() {
|
|
5830
|
+
if (!handlerPromise) {
|
|
5831
|
+
handlerPromise = (async () => {
|
|
5832
|
+
const mod = await import(handlerPath)
|
|
5833
|
+
const fn = (mod && (mod.default || mod.handler)) || null
|
|
5834
|
+
if (typeof fn !== 'function') {
|
|
5835
|
+
throw new Error(
|
|
5836
|
+
'Sandbox handler module must export a default function or a named "handler" function: ' + handlerPath,
|
|
5837
|
+
)
|
|
5838
|
+
}
|
|
5839
|
+
return fn
|
|
5840
|
+
})().catch((err) => {
|
|
5841
|
+
handlerPromise = null
|
|
5842
|
+
throw err
|
|
5843
|
+
})
|
|
5844
|
+
}
|
|
5845
|
+
return handlerPromise
|
|
5846
|
+
}
|
|
5847
|
+
|
|
5848
|
+
parentPort.on('message', async (msg) => {
|
|
5849
|
+
if (!msg || msg.type !== 'invoke') return
|
|
5850
|
+
try {
|
|
5851
|
+
const handler = await loadHandler()
|
|
5852
|
+
const result = await handler(msg.input)
|
|
5853
|
+
parentPort.postMessage({
|
|
5854
|
+
type: 'result',
|
|
5855
|
+
invocationId: msg.invocationId,
|
|
5856
|
+
success: true,
|
|
5857
|
+
data: result,
|
|
5858
|
+
})
|
|
5859
|
+
} catch (err) {
|
|
5860
|
+
parentPort.postMessage({
|
|
5861
|
+
type: 'result',
|
|
5862
|
+
invocationId: msg.invocationId,
|
|
5863
|
+
success: false,
|
|
5864
|
+
error: {
|
|
5865
|
+
name: (err && err.name) || 'Error',
|
|
5866
|
+
message: (err && err.message) || String(err),
|
|
5867
|
+
stack: err && err.stack,
|
|
5868
|
+
},
|
|
5869
|
+
})
|
|
5870
|
+
}
|
|
5871
|
+
})
|
|
5872
|
+
`;
|
|
5873
|
+
function rejectPending(state, error) {
|
|
5874
|
+
if (!state.pending)
|
|
5875
|
+
return;
|
|
5876
|
+
const pending = state.pending;
|
|
5877
|
+
state.pending = null;
|
|
5878
|
+
pending.reject(error);
|
|
5879
|
+
}
|
|
5880
|
+
function attachWorkerListeners(worker, state) {
|
|
5881
|
+
worker.on("message", (msg) => {
|
|
5882
|
+
if (msg?.type !== "result")
|
|
5883
|
+
return;
|
|
5884
|
+
const pending = state.pending;
|
|
5885
|
+
if (!pending || pending.id !== msg.invocationId)
|
|
5886
|
+
return;
|
|
5887
|
+
state.pending = null;
|
|
5888
|
+
if (msg.success) {
|
|
5889
|
+
pending.resolve(msg.data);
|
|
5890
|
+
} else {
|
|
5891
|
+
const error = new Error(msg.error?.message ?? "Sandbox handler failed");
|
|
5892
|
+
if (msg.error?.name)
|
|
5893
|
+
error.name = msg.error.name;
|
|
5894
|
+
if (msg.error?.stack)
|
|
5895
|
+
error.stack = msg.error.stack;
|
|
5896
|
+
pending.reject(error);
|
|
5897
|
+
}
|
|
5898
|
+
});
|
|
5899
|
+
worker.on("error", (err2) => {
|
|
5900
|
+
state.worker = null;
|
|
5901
|
+
rejectPending(state, err2);
|
|
5902
|
+
});
|
|
5903
|
+
worker.on("exit", (code) => {
|
|
5904
|
+
if (state.worker === worker)
|
|
5905
|
+
state.worker = null;
|
|
5906
|
+
if (code !== 0) {
|
|
5907
|
+
rejectPending(state, new Error(`Sandbox worker exited with code ${code}`));
|
|
5908
|
+
}
|
|
5909
|
+
});
|
|
5910
|
+
}
|
|
5911
|
+
function ensureWorker(state, handlerPath) {
|
|
5912
|
+
if (state.worker)
|
|
5913
|
+
return state.worker;
|
|
5914
|
+
const w = new Worker(WORKER_SCRIPT, {
|
|
5915
|
+
eval: true,
|
|
5916
|
+
workerData: { handlerPath }
|
|
5917
|
+
});
|
|
5918
|
+
attachWorkerListeners(w, state);
|
|
5919
|
+
w.unref();
|
|
5920
|
+
state.worker = w;
|
|
5921
|
+
return w;
|
|
5922
|
+
}
|
|
5923
|
+
function killWorker(state) {
|
|
5924
|
+
const dying = state.worker;
|
|
5925
|
+
state.worker = null;
|
|
5926
|
+
if (dying) {
|
|
5927
|
+
dying.terminate().catch(() => {});
|
|
5928
|
+
}
|
|
5929
|
+
}
|
|
5930
|
+
function postInvocation(state, worker, invocationId, input, timeoutMs, handlerPath, signal) {
|
|
5931
|
+
return new Promise((resolve2, reject) => {
|
|
5932
|
+
let timer = null;
|
|
5933
|
+
let abortHandler = null;
|
|
5934
|
+
const cleanup = () => {
|
|
5935
|
+
if (timer)
|
|
5936
|
+
clearTimeout(timer);
|
|
5937
|
+
if (signal && abortHandler)
|
|
5938
|
+
signal.removeEventListener("abort", abortHandler);
|
|
5939
|
+
};
|
|
5940
|
+
state.pending = {
|
|
5941
|
+
id: invocationId,
|
|
5942
|
+
resolve: (v) => {
|
|
5943
|
+
cleanup();
|
|
5944
|
+
resolve2(v);
|
|
5945
|
+
},
|
|
5946
|
+
reject: (e) => {
|
|
5947
|
+
cleanup();
|
|
5948
|
+
reject(e);
|
|
5949
|
+
}
|
|
5950
|
+
};
|
|
5951
|
+
timer = setTimeout(() => {
|
|
5952
|
+
if (state.pending?.id !== invocationId)
|
|
5953
|
+
return;
|
|
5954
|
+
const pending = state.pending;
|
|
5955
|
+
state.pending = null;
|
|
5956
|
+
killWorker(state);
|
|
5957
|
+
pending.reject(ElsiumError.timeout(`sandbox(${handlerPath})`, timeoutMs));
|
|
5958
|
+
}, timeoutMs);
|
|
5959
|
+
if (signal) {
|
|
5960
|
+
abortHandler = () => {
|
|
5961
|
+
if (state.pending?.id !== invocationId)
|
|
5962
|
+
return;
|
|
5963
|
+
const pending = state.pending;
|
|
5964
|
+
state.pending = null;
|
|
5965
|
+
killWorker(state);
|
|
5966
|
+
pending.reject(new Error("Sandbox invocation aborted"));
|
|
5967
|
+
};
|
|
5968
|
+
signal.addEventListener("abort", abortHandler, { once: true });
|
|
5969
|
+
}
|
|
5970
|
+
try {
|
|
5971
|
+
worker.postMessage({ type: "invoke", invocationId, input });
|
|
5972
|
+
} catch (err2) {
|
|
5973
|
+
if (state.pending?.id === invocationId)
|
|
5974
|
+
state.pending = null;
|
|
5975
|
+
cleanup();
|
|
5976
|
+
reject(err2 instanceof Error ? err2 : new Error(String(err2)));
|
|
5977
|
+
}
|
|
5978
|
+
});
|
|
5979
|
+
}
|
|
5980
|
+
function createWorkerSandboxRunner(config, defaultTimeoutMs) {
|
|
5981
|
+
const handlerPath = typeof config.handler === "string" ? config.handler : config.handler.href;
|
|
5982
|
+
const timeoutMs = config.timeoutMs ?? defaultTimeoutMs;
|
|
5983
|
+
const state = {
|
|
5984
|
+
worker: null,
|
|
5985
|
+
pending: null,
|
|
5986
|
+
chain: Promise.resolve(),
|
|
5987
|
+
disposed: false
|
|
5988
|
+
};
|
|
5989
|
+
async function runOnce(input, signal) {
|
|
5990
|
+
if (state.disposed) {
|
|
5991
|
+
throw new Error("Sandbox runner has been disposed");
|
|
5992
|
+
}
|
|
5993
|
+
if (signal?.aborted) {
|
|
5994
|
+
throw new Error("Sandbox invocation aborted");
|
|
5995
|
+
}
|
|
5996
|
+
const worker = ensureWorker(state, handlerPath);
|
|
5997
|
+
const invocationId = generateId("si");
|
|
5998
|
+
return postInvocation(state, worker, invocationId, input, timeoutMs, handlerPath, signal);
|
|
5999
|
+
}
|
|
6000
|
+
return {
|
|
6001
|
+
async invoke(input, signal) {
|
|
6002
|
+
const previous = state.chain.catch(() => {
|
|
6003
|
+
return;
|
|
6004
|
+
});
|
|
6005
|
+
const next = previous.then(() => runOnce(input, signal));
|
|
6006
|
+
state.chain = next.catch(() => {
|
|
6007
|
+
return;
|
|
6008
|
+
});
|
|
6009
|
+
return next;
|
|
6010
|
+
},
|
|
6011
|
+
async dispose() {
|
|
6012
|
+
state.disposed = true;
|
|
6013
|
+
const w = state.worker;
|
|
6014
|
+
state.worker = null;
|
|
6015
|
+
rejectPending(state, new Error("Sandbox runner disposed"));
|
|
6016
|
+
if (w) {
|
|
6017
|
+
try {
|
|
6018
|
+
await w.terminate();
|
|
6019
|
+
} catch {}
|
|
6020
|
+
}
|
|
6021
|
+
}
|
|
6022
|
+
};
|
|
6023
|
+
}
|
|
6024
|
+
|
|
6025
|
+
// ../tools/src/define.ts
|
|
6026
|
+
function formatZodErrors(error) {
|
|
6027
|
+
return error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ");
|
|
6028
|
+
}
|
|
6029
|
+
function buildExecutionFailure(toolCallId, startTime, error) {
|
|
6030
|
+
return {
|
|
6031
|
+
success: false,
|
|
6032
|
+
error,
|
|
6033
|
+
toolCallId,
|
|
6034
|
+
durationMs: Math.round(performance.now() - startTime)
|
|
6035
|
+
};
|
|
6036
|
+
}
|
|
6037
|
+
function buildExecutionSuccess(toolCallId, startTime, data) {
|
|
6038
|
+
return {
|
|
6039
|
+
success: true,
|
|
6040
|
+
data,
|
|
6041
|
+
toolCallId,
|
|
6042
|
+
durationMs: Math.round(performance.now() - startTime)
|
|
6043
|
+
};
|
|
6044
|
+
}
|
|
5817
6045
|
function defineTool(config) {
|
|
5818
6046
|
const input = config.input ?? config.parameters;
|
|
5819
6047
|
if (!input) {
|
|
5820
6048
|
throw ElsiumError.validation(`Tool "${config.name}" requires an input schema (use "input" or "parameters" key)`);
|
|
5821
6049
|
}
|
|
5822
|
-
|
|
5823
|
-
|
|
6050
|
+
if (!config.handler && !config.sandbox) {
|
|
6051
|
+
throw ElsiumError.validation(`Tool "${config.name}" requires either an inline "handler" or a "sandbox" config`);
|
|
6052
|
+
}
|
|
6053
|
+
if (config.sandbox && config.sandbox.mode !== "worker") {
|
|
6054
|
+
throw ElsiumError.validation(`Tool "${config.name}" sandbox.mode must be "worker" (received "${config.sandbox.mode}")`);
|
|
6055
|
+
}
|
|
6056
|
+
const { name, description, output, sandbox, timeoutMs = 30000 } = config;
|
|
6057
|
+
const handler = config.handler;
|
|
6058
|
+
let sandboxRunner = null;
|
|
6059
|
+
function getSandboxRunner() {
|
|
6060
|
+
if (!sandboxRunner) {
|
|
6061
|
+
if (!sandbox) {
|
|
6062
|
+
throw ElsiumError.validation(`Tool "${name}" has no sandbox config`);
|
|
6063
|
+
}
|
|
6064
|
+
sandboxRunner = createWorkerSandboxRunner(sandbox, timeoutMs);
|
|
6065
|
+
}
|
|
6066
|
+
return sandboxRunner;
|
|
6067
|
+
}
|
|
6068
|
+
async function runHandler(parsedInput, context) {
|
|
6069
|
+
if (sandbox) {
|
|
6070
|
+
const result = await getSandboxRunner().invoke(parsedInput, context.signal);
|
|
6071
|
+
return result;
|
|
6072
|
+
}
|
|
6073
|
+
if (!handler) {
|
|
6074
|
+
throw ElsiumError.validation(`Tool "${name}" has no handler`);
|
|
6075
|
+
}
|
|
6076
|
+
return handler(parsedInput, context);
|
|
6077
|
+
}
|
|
6078
|
+
const tool = {
|
|
5824
6079
|
name,
|
|
5825
6080
|
description,
|
|
5826
6081
|
inputSchema: input,
|
|
5827
6082
|
outputSchema: output,
|
|
5828
6083
|
timeoutMs,
|
|
6084
|
+
sandbox,
|
|
5829
6085
|
async execute(rawInput, partialCtx) {
|
|
5830
6086
|
const toolCallId = partialCtx?.toolCallId ?? generateId("tc");
|
|
5831
6087
|
const startTime = performance.now();
|
|
5832
6088
|
const parsed = input.safeParse(rawInput);
|
|
5833
6089
|
if (!parsed.success) {
|
|
5834
|
-
return {
|
|
5835
|
-
success: false,
|
|
5836
|
-
error: `Invalid input: ${parsed.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`,
|
|
5837
|
-
toolCallId,
|
|
5838
|
-
durationMs: Math.round(performance.now() - startTime)
|
|
5839
|
-
};
|
|
6090
|
+
return buildExecutionFailure(toolCallId, startTime, `Invalid input: ${formatZodErrors(parsed.error)}`);
|
|
5840
6091
|
}
|
|
5841
6092
|
const controller = new AbortController;
|
|
5842
6093
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
@@ -5847,9 +6098,9 @@ function defineTool(config) {
|
|
|
5847
6098
|
};
|
|
5848
6099
|
try {
|
|
5849
6100
|
const result = await Promise.race([
|
|
5850
|
-
|
|
6101
|
+
runHandler(parsed.data, context),
|
|
5851
6102
|
new Promise((_, reject) => {
|
|
5852
|
-
|
|
6103
|
+
context.signal?.addEventListener("abort", () => {
|
|
5853
6104
|
reject(ElsiumError.timeout(name, timeoutMs));
|
|
5854
6105
|
});
|
|
5855
6106
|
})
|
|
@@ -5857,28 +6108,13 @@ function defineTool(config) {
|
|
|
5857
6108
|
if (output) {
|
|
5858
6109
|
const validated = output.safeParse(result);
|
|
5859
6110
|
if (!validated.success) {
|
|
5860
|
-
return {
|
|
5861
|
-
success: false,
|
|
5862
|
-
error: `Invalid output: ${validated.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join(", ")}`,
|
|
5863
|
-
toolCallId,
|
|
5864
|
-
durationMs: Math.round(performance.now() - startTime)
|
|
5865
|
-
};
|
|
6111
|
+
return buildExecutionFailure(toolCallId, startTime, `Invalid output: ${formatZodErrors(validated.error)}`);
|
|
5866
6112
|
}
|
|
5867
6113
|
}
|
|
5868
|
-
return
|
|
5869
|
-
success: true,
|
|
5870
|
-
data: result,
|
|
5871
|
-
toolCallId,
|
|
5872
|
-
durationMs: Math.round(performance.now() - startTime)
|
|
5873
|
-
};
|
|
6114
|
+
return buildExecutionSuccess(toolCallId, startTime, result);
|
|
5874
6115
|
} catch (error) {
|
|
5875
6116
|
const message = error instanceof Error ? error.message : String(error);
|
|
5876
|
-
return
|
|
5877
|
-
success: false,
|
|
5878
|
-
error: message,
|
|
5879
|
-
toolCallId,
|
|
5880
|
-
durationMs: Math.round(performance.now() - startTime)
|
|
5881
|
-
};
|
|
6117
|
+
return buildExecutionFailure(toolCallId, startTime, message);
|
|
5882
6118
|
} finally {
|
|
5883
6119
|
clearTimeout(timer);
|
|
5884
6120
|
}
|
|
@@ -5889,8 +6125,16 @@ function defineTool(config) {
|
|
|
5889
6125
|
description,
|
|
5890
6126
|
inputSchema: zodToJsonSchema(input)
|
|
5891
6127
|
};
|
|
6128
|
+
},
|
|
6129
|
+
async dispose() {
|
|
6130
|
+
if (sandboxRunner) {
|
|
6131
|
+
const r = sandboxRunner;
|
|
6132
|
+
sandboxRunner = null;
|
|
6133
|
+
await r.dispose();
|
|
6134
|
+
}
|
|
5892
6135
|
}
|
|
5893
6136
|
};
|
|
6137
|
+
return tool;
|
|
5894
6138
|
}
|
|
5895
6139
|
// ../tools/src/toolkit.ts
|
|
5896
6140
|
init_src();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@elsium-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "CLI tool for ElsiumAI projects",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Eric Utrera <ebutrera9103@gmail.com>",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"dev": "bun --watch src/cli.ts"
|
|
25
25
|
},
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@elsium-ai/core": "^0.
|
|
28
|
-
"@elsium-ai/observe": "^0.
|
|
29
|
-
"elsium-ai": "^0.
|
|
27
|
+
"@elsium-ai/core": "^0.12.0",
|
|
28
|
+
"@elsium-ai/observe": "^0.12.0",
|
|
29
|
+
"elsium-ai": "^0.12.0"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"typescript": "^5.7.0"
|