@samrahimi/smol-js 0.7.0 → 0.7.1

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.mts CHANGED
@@ -1191,27 +1191,31 @@ declare class ExaResearchTool extends Tool {
1191
1191
 
1192
1192
  /**
1193
1193
  * ProxyTool - Bridges the smol-js agent runtime to an external standalone tool
1194
- * executed in an isolated Bun process.
1194
+ * executed in an isolated Bun process via the toolHarness adapter.
1195
1195
  *
1196
- * The tool file is never imported into the main Node.js process. Instead, when
1197
- * an agent invokes this proxy, it spawns `bun run <toolPath>` and communicates
1198
- * via a lightweight stdin/stdout JSON protocol.
1196
+ * The tool file is never imported into the main Node.js process. When an agent
1197
+ * invokes this proxy, it spawns:
1199
1198
  *
1200
- * Protocol (stdout of child):
1201
- * - Lines prefixed with `[TOOL_OUTPUT]` are streaming log lines from the tool.
1199
+ * bun run <toolHarness.ts> <toolPath> <argsJson>
1200
+ *
1201
+ * The harness is the ONLY place that speaks the stdout protocol. The tool
1202
+ * itself simply exports { TOOL_METADATA, execute } and knows nothing about
1203
+ * how it is being called.
1204
+ *
1205
+ * Protocol (stdout of harness/child):
1202
1206
  * - A line prefixed with `[TOOL_RESULT]` contains the JSON-serialized return value.
1203
1207
  * - A line prefixed with `[TOOL_ERROR]` means the tool threw; payload is the message.
1204
- * - Any other stdout line is treated as a streaming log line.
1208
+ * - Any other stdout line (e.g. console.log inside execute()) is streaming output.
1205
1209
  *
1206
- * Extensibility note: The transport (child_process + stdio) is encapsulated here.
1207
- * A future subclass or factory could swap this for HTTP, gRPC, or IPC with no change
1208
- * to the ProxyTool interface visible to agents or YAML definitions.
1210
+ * Extensibility: The spawn+stdio transport is fully encapsulated here. A future
1211
+ * variant (HTTP, gRPC, IPC) only needs to replace execute() the tool file and
1212
+ * YAML definition stay identical.
1209
1213
  */
1210
1214
 
1211
1215
  interface ProxyToolConfig {
1212
1216
  /** Absolute path to the .ts or .js tool file */
1213
1217
  toolPath: string;
1214
- /** Tool name (must match the class name inside the file) */
1218
+ /** Tool name (must match the file's base name) */
1215
1219
  name: string;
1216
1220
  /** Human-readable description exposed to agents */
1217
1221
  description: string;
@@ -1230,14 +1234,16 @@ declare class ProxyTool extends Tool {
1230
1234
  private readonly toolPath;
1231
1235
  private readonly timeout;
1232
1236
  private bunPath;
1237
+ private harnessPath;
1233
1238
  constructor(config: ProxyToolConfig);
1234
1239
  /**
1235
- * Ensure Bun is available before first invocation.
1240
+ * Ensure Bun is available and locate the harness before first invocation.
1236
1241
  */
1237
1242
  setup(): Promise<void>;
1238
1243
  /**
1239
- * Spawn the tool in a Bun child process, pass serialized args via CLI,
1240
- * stream stdout back as log lines, and parse the final result.
1244
+ * Spawn the harness in a Bun child process. The harness imports the tool,
1245
+ * calls execute(args), and writes the protocol lines. Any console.log from
1246
+ * the tool flows through stdout as plain lines.
1241
1247
  */
1242
1248
  execute(args: Record<string, unknown>): Promise<unknown>;
1243
1249
  private processLine;
package/dist/index.d.ts CHANGED
@@ -1191,27 +1191,31 @@ declare class ExaResearchTool extends Tool {
1191
1191
 
1192
1192
  /**
1193
1193
  * ProxyTool - Bridges the smol-js agent runtime to an external standalone tool
1194
- * executed in an isolated Bun process.
1194
+ * executed in an isolated Bun process via the toolHarness adapter.
1195
1195
  *
1196
- * The tool file is never imported into the main Node.js process. Instead, when
1197
- * an agent invokes this proxy, it spawns `bun run <toolPath>` and communicates
1198
- * via a lightweight stdin/stdout JSON protocol.
1196
+ * The tool file is never imported into the main Node.js process. When an agent
1197
+ * invokes this proxy, it spawns:
1199
1198
  *
1200
- * Protocol (stdout of child):
1201
- * - Lines prefixed with `[TOOL_OUTPUT]` are streaming log lines from the tool.
1199
+ * bun run <toolHarness.ts> <toolPath> <argsJson>
1200
+ *
1201
+ * The harness is the ONLY place that speaks the stdout protocol. The tool
1202
+ * itself simply exports { TOOL_METADATA, execute } and knows nothing about
1203
+ * how it is being called.
1204
+ *
1205
+ * Protocol (stdout of harness/child):
1202
1206
  * - A line prefixed with `[TOOL_RESULT]` contains the JSON-serialized return value.
1203
1207
  * - A line prefixed with `[TOOL_ERROR]` means the tool threw; payload is the message.
1204
- * - Any other stdout line is treated as a streaming log line.
1208
+ * - Any other stdout line (e.g. console.log inside execute()) is streaming output.
1205
1209
  *
1206
- * Extensibility note: The transport (child_process + stdio) is encapsulated here.
1207
- * A future subclass or factory could swap this for HTTP, gRPC, or IPC with no change
1208
- * to the ProxyTool interface visible to agents or YAML definitions.
1210
+ * Extensibility: The spawn+stdio transport is fully encapsulated here. A future
1211
+ * variant (HTTP, gRPC, IPC) only needs to replace execute() the tool file and
1212
+ * YAML definition stay identical.
1209
1213
  */
1210
1214
 
1211
1215
  interface ProxyToolConfig {
1212
1216
  /** Absolute path to the .ts or .js tool file */
1213
1217
  toolPath: string;
1214
- /** Tool name (must match the class name inside the file) */
1218
+ /** Tool name (must match the file's base name) */
1215
1219
  name: string;
1216
1220
  /** Human-readable description exposed to agents */
1217
1221
  description: string;
@@ -1230,14 +1234,16 @@ declare class ProxyTool extends Tool {
1230
1234
  private readonly toolPath;
1231
1235
  private readonly timeout;
1232
1236
  private bunPath;
1237
+ private harnessPath;
1233
1238
  constructor(config: ProxyToolConfig);
1234
1239
  /**
1235
- * Ensure Bun is available before first invocation.
1240
+ * Ensure Bun is available and locate the harness before first invocation.
1236
1241
  */
1237
1242
  setup(): Promise<void>;
1238
1243
  /**
1239
- * Spawn the tool in a Bun child process, pass serialized args via CLI,
1240
- * stream stdout back as log lines, and parse the final result.
1244
+ * Spawn the harness in a Bun child process. The harness imports the tool,
1245
+ * calls execute(args), and writes the protocol lines. Any console.log from
1246
+ * the tool flows through stdout as plain lines.
1241
1247
  */
1242
1248
  execute(args: Record<string, unknown>): Promise<unknown>;
1243
1249
  private processLine;
package/dist/index.js CHANGED
@@ -792,7 +792,7 @@ Total time: ${(duration / 1e3).toFixed(2)}s`);
792
792
  }
793
793
  /** Sleep for a specified duration */
794
794
  sleep(ms) {
795
- return new Promise((resolve6) => setTimeout(resolve6, ms));
795
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
796
796
  }
797
797
  };
798
798
 
@@ -1461,12 +1461,12 @@ var UserInputTool = class extends Tool {
1461
1461
  input: process.stdin,
1462
1462
  output: process.stdout
1463
1463
  });
1464
- return new Promise((resolve6) => {
1464
+ return new Promise((resolve7) => {
1465
1465
  rl.question(`
1466
1466
  [Agent asks]: ${question}
1467
1467
  Your response: `, (answer) => {
1468
1468
  rl.close();
1469
- resolve6(answer);
1469
+ resolve7(answer);
1470
1470
  });
1471
1471
  });
1472
1472
  }
@@ -2795,7 +2795,7 @@ var ExaResearchTool = class extends Tool {
2795
2795
  while (Date.now() - startTime < this.maxPollTime) {
2796
2796
  attempts++;
2797
2797
  if (attempts > 1) {
2798
- await new Promise((resolve6) => setTimeout(resolve6, this.pollInterval));
2798
+ await new Promise((resolve7) => setTimeout(resolve7, this.pollInterval));
2799
2799
  }
2800
2800
  const statusResponse = await fetch(`https://api.exa.ai/research/v1/${researchId}`, {
2801
2801
  method: "GET",
@@ -2848,6 +2848,7 @@ var ExaResearchTool = class extends Tool {
2848
2848
 
2849
2849
  // src/tools/ProxyTool.ts
2850
2850
  var import_child_process2 = require("child_process");
2851
+ var path6 = __toESM(require("path"));
2851
2852
 
2852
2853
  // src/utils/bunInstaller.ts
2853
2854
  var import_child_process = require("child_process");
@@ -2906,10 +2907,12 @@ function whichBun() {
2906
2907
  }
2907
2908
 
2908
2909
  // src/tools/ProxyTool.ts
2909
- var TOOL_OUTPUT_PREFIX = "[TOOL_OUTPUT]";
2910
2910
  var TOOL_RESULT_PREFIX = "[TOOL_RESULT]";
2911
2911
  var TOOL_ERROR_PREFIX = "[TOOL_ERROR]";
2912
2912
  var DEFAULT_TOOL_TIMEOUT_MS = 6e4;
2913
+ function resolveHarnessPath() {
2914
+ return path6.resolve(__dirname, "..", "toolHarness.ts");
2915
+ }
2913
2916
  var ProxyTool = class extends Tool {
2914
2917
  name;
2915
2918
  description;
@@ -2918,6 +2921,7 @@ var ProxyTool = class extends Tool {
2918
2921
  toolPath;
2919
2922
  timeout;
2920
2923
  bunPath = null;
2924
+ harnessPath = null;
2921
2925
  constructor(config) {
2922
2926
  super();
2923
2927
  this.name = config.name;
@@ -2928,23 +2932,25 @@ var ProxyTool = class extends Tool {
2928
2932
  this.timeout = config.timeout ?? DEFAULT_TOOL_TIMEOUT_MS;
2929
2933
  }
2930
2934
  /**
2931
- * Ensure Bun is available before first invocation.
2935
+ * Ensure Bun is available and locate the harness before first invocation.
2932
2936
  */
2933
2937
  async setup() {
2934
2938
  this.bunPath = await ensureBunAvailable();
2939
+ this.harnessPath = resolveHarnessPath();
2935
2940
  this.isSetup = true;
2936
2941
  }
2937
2942
  /**
2938
- * Spawn the tool in a Bun child process, pass serialized args via CLI,
2939
- * stream stdout back as log lines, and parse the final result.
2943
+ * Spawn the harness in a Bun child process. The harness imports the tool,
2944
+ * calls execute(args), and writes the protocol lines. Any console.log from
2945
+ * the tool flows through stdout as plain lines.
2940
2946
  */
2941
2947
  async execute(args) {
2942
- if (!this.bunPath) {
2948
+ if (!this.bunPath || !this.harnessPath) {
2943
2949
  await this.setup();
2944
2950
  }
2945
2951
  const serializedArgs = JSON.stringify(args);
2946
- return new Promise((resolve6, reject) => {
2947
- const child = (0, import_child_process2.spawn)(this.bunPath, ["run", this.toolPath, serializedArgs], {
2952
+ return new Promise((resolve7, reject) => {
2953
+ const child = (0, import_child_process2.spawn)(this.bunPath, ["run", this.harnessPath, this.toolPath, serializedArgs], {
2948
2954
  stdio: ["pipe", "pipe", "pipe"],
2949
2955
  env: { ...process.env }
2950
2956
  });
@@ -3009,12 +3015,12 @@ ${logBuffer.join("\n")}
3009
3015
  [Tool result]
3010
3016
  `;
3011
3017
  if (typeof result === "string") {
3012
- resolve6(logPrefix + result);
3018
+ resolve7(logPrefix + result);
3013
3019
  } else {
3014
- resolve6({ logs: logBuffer.join("\n"), result });
3020
+ resolve7({ logs: logBuffer.join("\n"), result });
3015
3021
  }
3016
3022
  } else {
3017
- resolve6(result);
3023
+ resolve7(result);
3018
3024
  }
3019
3025
  return;
3020
3026
  }
@@ -3024,7 +3030,7 @@ ${logBuffer.join("\n")}
3024
3030
  `Custom tool "${this.name}" exited with code ${code}. Output: ${combined || "(none)"}`
3025
3031
  ));
3026
3032
  } else {
3027
- resolve6(combined || `Tool "${this.name}" produced no output.`);
3033
+ resolve7(combined || `Tool "${this.name}" produced no output.`);
3028
3034
  }
3029
3035
  });
3030
3036
  child.on("error", (err) => {
@@ -3035,7 +3041,7 @@ ${logBuffer.join("\n")}
3035
3041
  });
3036
3042
  });
3037
3043
  }
3038
- // --- internal line parser ---
3044
+ // --- line parser: protocol is spoken by harness, interpreted here ---
3039
3045
  processLine(line, handlers) {
3040
3046
  const trimmed = line.trimEnd();
3041
3047
  if (!trimmed) return;
@@ -3048,8 +3054,6 @@ ${logBuffer.join("\n")}
3048
3054
  }
3049
3055
  } else if (trimmed.startsWith(TOOL_ERROR_PREFIX)) {
3050
3056
  handlers.onError(trimmed.slice(TOOL_ERROR_PREFIX.length).trim());
3051
- } else if (trimmed.startsWith(TOOL_OUTPUT_PREFIX)) {
3052
- handlers.onOutput(trimmed.slice(TOOL_OUTPUT_PREFIX.length).trim());
3053
3057
  } else {
3054
3058
  handlers.onOutput(trimmed);
3055
3059
  }
@@ -3058,7 +3062,7 @@ ${logBuffer.join("\n")}
3058
3062
 
3059
3063
  // src/tools/CustomToolScanner.ts
3060
3064
  var fs6 = __toESM(require("fs"));
3061
- var path6 = __toESM(require("path"));
3065
+ var path7 = __toESM(require("path"));
3062
3066
  var METADATA_REGEX = /export\s+const\s+TOOL_METADATA\s*=\s*(\{[\s\S]*?\});\s*$/m;
3063
3067
  function scanCustomTools(folderPath) {
3064
3068
  if (!fs6.existsSync(folderPath)) {
@@ -3070,10 +3074,10 @@ function scanCustomTools(folderPath) {
3070
3074
  const discovered = [];
3071
3075
  for (const entry of entries) {
3072
3076
  if (entry.isDirectory()) continue;
3073
- const ext = path6.extname(entry.name).toLowerCase();
3077
+ const ext = path7.extname(entry.name).toLowerCase();
3074
3078
  if (ext !== ".ts" && ext !== ".js") continue;
3075
- const filePath = path6.resolve(folderPath, entry.name);
3076
- const baseName = path6.basename(entry.name, ext);
3079
+ const filePath = path7.resolve(folderPath, entry.name);
3080
+ const baseName = path7.basename(entry.name, ext);
3077
3081
  let metadata;
3078
3082
  try {
3079
3083
  metadata = extractMetadata(filePath);
@@ -3141,7 +3145,7 @@ function loadCustomTools(folderPath) {
3141
3145
 
3142
3146
  // src/orchestrator/YAMLLoader.ts
3143
3147
  var fs7 = __toESM(require("fs"));
3144
- var path7 = __toESM(require("path"));
3148
+ var path8 = __toESM(require("path"));
3145
3149
  var import_yaml = __toESM(require("yaml"));
3146
3150
  var TOOL_REGISTRY = {
3147
3151
  read_file: ReadFileTool,
@@ -3173,7 +3177,7 @@ var YAMLLoader = class {
3173
3177
  * Load a workflow from a YAML file path.
3174
3178
  */
3175
3179
  loadFromFile(filePath) {
3176
- const absolutePath = path7.isAbsolute(filePath) ? filePath : path7.resolve(process.cwd(), filePath);
3180
+ const absolutePath = path8.isAbsolute(filePath) ? filePath : path8.resolve(process.cwd(), filePath);
3177
3181
  if (!fs7.existsSync(absolutePath)) {
3178
3182
  throw new Error(`Workflow file not found: ${absolutePath}`);
3179
3183
  }