@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.
Files changed (2) hide show
  1. package/dist/cli.js +272 -28
  2. 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
- const { name, description, output, handler, timeoutMs = 30000 } = config;
5823
- return {
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
- handler(parsed.data, context),
6101
+ runHandler(parsed.data, context),
5851
6102
  new Promise((_, reject) => {
5852
- controller.signal.addEventListener("abort", () => {
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.11.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.11.0",
28
- "@elsium-ai/observe": "^0.11.0",
29
- "elsium-ai": "^0.11.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"