@robota-sdk/agent-tools 3.0.0-beta.66 → 3.0.0-beta.68

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.
@@ -1,10 +1,11 @@
1
1
  import { chmod, mkdir, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
2
- import { basename, dirname, isAbsolute, join, posix, resolve } from "node:path";
2
+ import { basename, dirname, isAbsolute, join, posix, resolve, sep } from "node:path";
3
3
  import { ToolExecutionError, ValidationError, logger } from "@robota-sdk/agent-core";
4
4
  import { spawn } from "node:child_process";
5
5
  import { z } from "zod";
6
6
  import { randomBytes } from "node:crypto";
7
7
  import fg from "fast-glob";
8
+ import pLimit from "p-limit";
8
9
  //#region src/sandbox/e2b-sandbox-client.ts
9
10
  var E2BSandboxClient = class {
10
11
  sandbox;
@@ -638,281 +639,6 @@ function createZodFunctionTool(name, description, zodSchema, fn) {
638
639
  return new FunctionTool(schema, wrappedFn);
639
640
  }
640
641
  //#endregion
641
- //#region src/implementations/openapi-schema-converter.ts
642
- /**
643
- * HTTP methods to search when scanning OpenAPI paths
644
- */
645
- const HTTP_METHODS = [
646
- "get",
647
- "post",
648
- "put",
649
- "delete",
650
- "patch",
651
- "head",
652
- "options"
653
- ];
654
- /**
655
- * Find an operation in the OpenAPI spec by operationId
656
- */
657
- function findOperation(apiSpec, operationId) {
658
- for (const [path, pathItem] of Object.entries(apiSpec.paths || {})) {
659
- if (!pathItem) continue;
660
- for (const method of HTTP_METHODS) {
661
- const operation = pathItem[method];
662
- if (operation?.operationId === operationId) return {
663
- method,
664
- path,
665
- operation
666
- };
667
- }
668
- }
669
- }
670
- /**
671
- * Map OpenAPI type to JSON schema type
672
- */
673
- function mapOpenAPIType(type) {
674
- switch (type) {
675
- case "string": return "string";
676
- case "number": return "number";
677
- case "integer": return "integer";
678
- case "boolean": return "boolean";
679
- case "array": return "array";
680
- case "object": return "object";
681
- default: return "string";
682
- }
683
- }
684
- /**
685
- * Convert OpenAPI schema to parameter schema
686
- */
687
- function convertOpenAPISchemaToParameterSchema(schema) {
688
- if ("$ref" in schema) return { type: "object" };
689
- const result = { type: mapOpenAPIType(schema.type) };
690
- if (schema.description) result.description = schema.description;
691
- if (schema.enum) result.enum = schema.enum;
692
- if (schema.minimum !== void 0) result.minimum = schema.minimum;
693
- if (schema.maximum !== void 0) result.maximum = schema.maximum;
694
- if (schema.pattern) result.pattern = schema.pattern;
695
- if (schema.format) result.format = schema.format;
696
- if (schema.default !== void 0) result.default = schema.default;
697
- if (schema.type === "array" && schema.items) result.items = convertOpenAPISchemaToParameterSchema(schema.items);
698
- if (schema.type === "object" && schema.properties) {
699
- result.properties = {};
700
- for (const [propName, propSchema] of Object.entries(schema.properties)) result.properties[propName] = convertOpenAPISchemaToParameterSchema(propSchema);
701
- if (schema.required && schema.required.length > 0) result.required = schema.required;
702
- }
703
- return result;
704
- }
705
- /**
706
- * Convert OpenAPI parameter object to tool parameter schema
707
- */
708
- function convertOpenAPIParamToSchema(param) {
709
- const schema = param.schema;
710
- return convertOpenAPISchemaToParameterSchema(schema);
711
- }
712
- /**
713
- * Create a tool schema from an OpenAPI operation specification
714
- */
715
- function createSchemaFromOperation(operationId, opSpec) {
716
- const properties = {};
717
- const required = [];
718
- const params = opSpec.parameters || [];
719
- for (const param of params) {
720
- properties[param.name] = convertOpenAPIParamToSchema(param);
721
- if (param.required) required.push(param.name);
722
- }
723
- if (opSpec.requestBody) {
724
- const jsonContent = opSpec.requestBody.content?.["application/json"];
725
- if (jsonContent?.schema) {
726
- const bodySchema = convertOpenAPISchemaToParameterSchema(jsonContent.schema);
727
- if (bodySchema.type === "object" && bodySchema.properties) {
728
- Object.assign(properties, bodySchema.properties);
729
- const schemaWithRequired = bodySchema;
730
- if (schemaWithRequired.required) required.push(...schemaWithRequired.required);
731
- }
732
- }
733
- }
734
- const schemaParams = {
735
- type: "object",
736
- properties
737
- };
738
- if (required.length > 0) schemaParams.required = required;
739
- return {
740
- name: operationId,
741
- description: opSpec.summary || opSpec.description || `OpenAPI operation: ${operationId}`,
742
- parameters: schemaParams
743
- };
744
- }
745
- //#endregion
746
- //#region src/implementations/openapi-tool.ts
747
- /**
748
- * OpenAPI tool implementation
749
- * Executes API calls based on OpenAPI 3.0 specifications
750
- *
751
- * Implements ITool without extending AbstractTool to avoid
752
- * circular runtime dependency (tools → agents → tools).
753
- */
754
- var OpenAPITool = class {
755
- schema;
756
- apiSpec;
757
- operationId;
758
- baseURL;
759
- config;
760
- eventService;
761
- constructor(config) {
762
- this.config = config;
763
- if (typeof config.spec !== "object" || config.spec === null || typeof config.spec.openapi !== "string" || typeof config.spec.paths !== "object") throw new Error("Invalid OpenAPI spec: must contain \"openapi\" (string) and \"paths\" (object) fields");
764
- this.apiSpec = config.spec;
765
- this.operationId = config.operationId;
766
- this.baseURL = config.baseURL;
767
- this.schema = this.createSchemaFromOpenAPI();
768
- }
769
- /**
770
- * Execute the OpenAPI tool
771
- */
772
- async execute(parameters, context) {
773
- const toolName = this.schema.name;
774
- const validation = this.validateParameters(parameters);
775
- if (!validation.isValid) throw new ValidationError(`Invalid parameters for OpenAPI tool "${toolName}": ${validation.errors.join(", ")}`);
776
- try {
777
- const startTime = Date.now();
778
- return {
779
- success: true,
780
- data: await this.executeAPICall(parameters, context),
781
- metadata: {
782
- executionTime: Date.now() - startTime,
783
- toolName,
784
- operationId: this.operationId,
785
- baseURL: this.baseURL
786
- }
787
- };
788
- } catch (error) {
789
- if (error instanceof ToolExecutionError || error instanceof ValidationError) throw error;
790
- const safeError = error instanceof Error ? error : new Error(String(error));
791
- throw new ToolExecutionError(`OpenAPI tool execution failed: ${safeError.message}`, toolName, safeError, {
792
- operationId: this.operationId,
793
- baseURL: this.baseURL,
794
- parametersCount: Object.keys(parameters).length
795
- });
796
- }
797
- }
798
- /**
799
- * Validate tool parameters
800
- */
801
- validate(parameters) {
802
- return this.validateParameters(parameters).isValid;
803
- }
804
- /**
805
- * Validate tool parameters with detailed result
806
- */
807
- validateParameters(parameters) {
808
- const required = this.schema.parameters.required || [];
809
- const errors = [];
810
- for (const field of required) if (!(field in parameters)) errors.push(`Missing required parameter: ${field}`);
811
- return {
812
- isValid: errors.length === 0,
813
- errors
814
- };
815
- }
816
- /**
817
- * Get tool name
818
- */
819
- getName() {
820
- return this.schema.name;
821
- }
822
- /**
823
- * Set EventService for post-construction injection.
824
- */
825
- setEventService(eventService) {
826
- this.eventService = eventService;
827
- }
828
- /**
829
- * Get tool description
830
- */
831
- getDescription() {
832
- return this.schema.description;
833
- }
834
- /**
835
- * Execute the actual API call
836
- * @private
837
- */
838
- async executeAPICall(parameters, _context) {
839
- const operation = findOperation(this.apiSpec, this.operationId);
840
- if (!operation) throw new Error(`Operation ${this.operationId} not found in OpenAPI spec`);
841
- this.buildRequestConfig(operation, parameters);
842
- throw new Error("Not implemented: actual API execution is not yet available");
843
- }
844
- /**
845
- * Build HTTP request configuration from OpenAPI operation and parameters
846
- */
847
- buildRequestConfig(opInfo, parameters) {
848
- const { method, path, operation } = opInfo;
849
- let url = this.baseURL + path;
850
- const headers = {};
851
- let body;
852
- const params = operation.parameters || [];
853
- for (const param of params) {
854
- const value = parameters[param.name];
855
- if (value === void 0 && param.required) throw new Error(`Required parameter ${param.name} is missing`);
856
- if (value !== void 0) switch (param.in) {
857
- case "path":
858
- url = url.replace(`{${param.name}}`, encodeURIComponent(String(value)));
859
- break;
860
- case "query": {
861
- const separator = url.includes("?") ? "&" : "?";
862
- url += `${separator}${param.name}=${encodeURIComponent(String(value))}`;
863
- break;
864
- }
865
- case "header":
866
- headers[param.name] = String(value);
867
- break;
868
- }
869
- }
870
- if ([
871
- "post",
872
- "put",
873
- "patch"
874
- ].includes(method) && operation.requestBody) {
875
- if (operation.requestBody.content?.["application/json"]) {
876
- headers["Content-Type"] = "application/json";
877
- const bodyParams = {};
878
- for (const [key, value] of Object.entries(parameters)) if (!params.some((p) => p.name === key)) bodyParams[key] = value;
879
- body = JSON.stringify(bodyParams);
880
- }
881
- }
882
- if (this.config.auth) switch (this.config.auth.type) {
883
- case "bearer":
884
- headers["Authorization"] = `Bearer ${this.config.auth.token}`;
885
- break;
886
- case "apiKey": {
887
- const headerName = this.config.auth.header || "X-API-Key";
888
- headers[headerName] = this.config.auth.apiKey || "";
889
- break;
890
- }
891
- }
892
- const result = {
893
- method,
894
- url,
895
- headers
896
- };
897
- if (body !== void 0) result.body = body;
898
- return result;
899
- }
900
- /**
901
- * Create tool schema from OpenAPI operation specification
902
- */
903
- createSchemaFromOpenAPI() {
904
- const operation = findOperation(this.apiSpec, this.operationId);
905
- if (!operation) throw new Error(`[STRICT-POLICY][EMITTER-CONTRACT] OpenAPI operation not found: ${this.operationId}. Emitter contract must provide a valid operationId present in the OpenAPI document.`);
906
- return createSchemaFromOperation(this.operationId, operation.operation);
907
- }
908
- };
909
- /**
910
- * Factory function to create OpenAPI tools from specification
911
- */
912
- function createOpenAPITool(config) {
913
- return new OpenAPITool(config);
914
- }
915
- //#endregion
916
642
  //#region src/builtins/bash-tool.ts
917
643
  /**
918
644
  * BashTool — execute shell commands via child_process.spawn
@@ -932,7 +658,8 @@ const BashSchema = z.object({
932
658
  * Resolves with the TToolResult JSON string.
933
659
  */
934
660
  async function runBash(args, options = {}) {
935
- const { command, timeout = DEFAULT_TIMEOUT_MS$2, workingDirectory } = args;
661
+ const { command, timeout: rawTimeout = DEFAULT_TIMEOUT_MS$2, workingDirectory } = args;
662
+ const timeout = Math.min(rawTimeout, 6e5);
936
663
  if (options.sandboxClient) try {
937
664
  const sandboxResult = await options.sandboxClient.run(command, {
938
665
  timeoutMs: timeout,
@@ -1028,6 +755,25 @@ function createBashTool(options = {}) {
1028
755
  */
1029
756
  const bashTool = createBashTool();
1030
757
  //#endregion
758
+ //#region src/builtins/path-guard.ts
759
+ /**
760
+ * Returns a JSON-serialized TToolResult error when filePath is outside cwd.
761
+ * Returns undefined when the path is within cwd or cwd is not set.
762
+ */
763
+ function checkPathWithinCwd(filePath, cwd) {
764
+ if (cwd === void 0) return void 0;
765
+ const resolved = resolve(filePath);
766
+ const cwdResolved = resolve(cwd);
767
+ if (resolved !== cwdResolved && !resolved.startsWith(cwdResolved + sep)) {
768
+ const result = {
769
+ success: false,
770
+ output: "",
771
+ error: `Access denied: "${filePath}" is outside the working directory`
772
+ };
773
+ return JSON.stringify(result);
774
+ }
775
+ }
776
+ //#endregion
1031
777
  //#region src/builtins/read-tool.ts
1032
778
  /**
1033
779
  * ReadTool — read a file and return its contents with line numbers (cat -n style).
@@ -1087,6 +833,8 @@ async function readFileTool(args, options = {}) {
1087
833
  };
1088
834
  return JSON.stringify(result);
1089
835
  }
836
+ const pathError = checkPathWithinCwd(filePath, options.cwd);
837
+ if (pathError !== void 0) return pathError;
1090
838
  let fileStats;
1091
839
  try {
1092
840
  fileStats = await stat(filePath);
@@ -1185,6 +933,10 @@ const WriteSchema = z.object({
1185
933
  });
1186
934
  async function writeFileTool(args, options = {}) {
1187
935
  const { filePath, content } = args;
936
+ if (!options.sandboxClient) {
937
+ const pathError = checkPathWithinCwd(filePath, options.cwd);
938
+ if (pathError !== void 0) return pathError;
939
+ }
1188
940
  try {
1189
941
  if (options.sandboxClient) await options.sandboxClient.writeFile(filePath, content);
1190
942
  else await atomicWriteUtf8File(filePath, content);
@@ -1230,6 +982,10 @@ const EditSchema = z.object({
1230
982
  });
1231
983
  async function editFileTool(args, options = {}) {
1232
984
  const { filePath, oldString, newString, replaceAll = false } = args;
985
+ if (!options.sandboxClient) {
986
+ const pathError = checkPathWithinCwd(filePath, options.cwd);
987
+ if (pathError !== void 0) return pathError;
988
+ }
1233
989
  let content;
1234
990
  try {
1235
991
  content = options.sandboxClient ? await options.sandboxClient.readFile(filePath) : await readFile(filePath, "utf8");
@@ -1326,7 +1082,8 @@ async function globFileTool(args) {
1326
1082
  };
1327
1083
  return JSON.stringify(result);
1328
1084
  }
1329
- const withMtime = await Promise.all(matches.map(async (p) => {
1085
+ const limit = pLimit(100);
1086
+ const withMtime = await Promise.all(matches.map((p) => limit(async () => {
1330
1087
  const absPath = resolve(cwd, p);
1331
1088
  try {
1332
1089
  return {
@@ -1339,7 +1096,7 @@ async function globFileTool(args) {
1339
1096
  mtime: 0
1340
1097
  };
1341
1098
  }
1342
- }));
1099
+ })));
1343
1100
  withMtime.sort((a, b) => b.mtime - a.mtime);
1344
1101
  const maxResults = args.limit ?? DEFAULT_MAX_RESULTS;
1345
1102
  const totalMatches = withMtime.length;
@@ -1510,6 +1267,17 @@ const WebFetchSchema = z.object({
1510
1267
  function htmlToText(html) {
1511
1268
  return html.replace(/<script[\s\S]*?<\/script>/gi, "").replace(/<style[\s\S]*?<\/style>/gi, "").replace(/<[^>]+>/g, " ").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, "\"").replace(/&#39;/g, "'").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim();
1512
1269
  }
1270
+ function classifyFetchError(err) {
1271
+ if (!(err instanceof Error)) return String(err);
1272
+ if (err.name === "AbortError") return `Request timed out after ${DEFAULT_TIMEOUT_MS$1 / 1e3}s. The server did not respond in time.`;
1273
+ const code = err.code;
1274
+ if (code === "ENOTFOUND" || code === "EAI_AGAIN") return `Network error: DNS resolution failed for this host. The URL may be incorrect or the host does not exist. Do not retry with the same URL.`;
1275
+ if (code === "ECONNREFUSED") return `Network error: Connection refused. The server is not accepting connections at this address. Do not retry with the same URL.`;
1276
+ if (code === "ECONNRESET") return `Network error: Connection was reset by the server. The server may be temporarily unavailable.`;
1277
+ if (code === "ETIMEDOUT") return `Network error: Connection timed out. The server is not reachable within the expected time.`;
1278
+ if (code === "CERT_HAS_EXPIRED" || code === "UNABLE_TO_VERIFY_LEAF_SIGNATURE") return `Network error: SSL certificate error (${code}). The server's certificate is invalid. Do not retry with the same URL.`;
1279
+ return `Network error: ${err.message} Check that the URL is correct and the server is reachable.`;
1280
+ }
1513
1281
  async function runWebFetch(args) {
1514
1282
  const { url, headers } = args;
1515
1283
  try {
@@ -1518,7 +1286,7 @@ async function runWebFetch(args) {
1518
1286
  const result = {
1519
1287
  success: false,
1520
1288
  output: "",
1521
- error: `Invalid URL: ${url}`
1289
+ error: `Invalid URL: "${url}". Fix the URL format before retrying.`
1522
1290
  };
1523
1291
  return JSON.stringify(result);
1524
1292
  }
@@ -1535,10 +1303,11 @@ async function runWebFetch(args) {
1535
1303
  });
1536
1304
  clearTimeout(timeout);
1537
1305
  if (!response.ok) {
1306
+ const retryHint = response.status >= 500 ? " The server is temporarily unavailable — retrying may help." : " Do not retry with the same URL.";
1538
1307
  const result = {
1539
1308
  success: false,
1540
1309
  output: "",
1541
- error: `HTTP ${response.status} ${response.statusText}`
1310
+ error: `HTTP ${response.status} ${response.statusText}.${retryHint}`
1542
1311
  };
1543
1312
  return JSON.stringify(result);
1544
1313
  }
@@ -1548,7 +1317,7 @@ async function runWebFetch(args) {
1548
1317
  const result = {
1549
1318
  success: false,
1550
1319
  output: "",
1551
- error: `Response too large: ${buffer.byteLength} bytes (max ${MAX_RESPONSE_BYTES})`
1320
+ error: `Response too large: ${buffer.byteLength} bytes (max ${MAX_RESPONSE_BYTES}). Consider fetching a more specific URL or a paginated endpoint.`
1552
1321
  };
1553
1322
  return JSON.stringify(result);
1554
1323
  }
@@ -1562,7 +1331,7 @@ async function runWebFetch(args) {
1562
1331
  const result = {
1563
1332
  success: false,
1564
1333
  output: "",
1565
- error: err instanceof Error ? err.message : String(err)
1334
+ error: classifyFetchError(err)
1566
1335
  };
1567
1336
  return JSON.stringify(result);
1568
1337
  }
@@ -1635,6 +1404,6 @@ async function runWebSearch(args) {
1635
1404
  }
1636
1405
  const webSearchTool = createZodFunctionTool("WebSearch", "Search the web and return results with title, URL, and snippet.", WebSearchSchema, async (params) => runWebSearch(params));
1637
1406
  //#endregion
1638
- export { E2BSandboxClient, FunctionTool, InMemorySandboxClient, OpenAPITool, ToolRegistry, applyWorkspaceManifest, bashTool, createBashTool, createEditTool, createFunctionTool, createOpenAPITool, createReadTool, createWriteTool, createZodFunctionTool, editTool, globTool, grepTool, readTool, validateWorkspaceManifestPath, webFetchTool, webSearchTool, writeTool, zodToJsonSchema };
1407
+ export { E2BSandboxClient, FunctionTool, InMemorySandboxClient, ToolRegistry, applyWorkspaceManifest, bashTool, createBashTool, createEditTool, createFunctionTool, createReadTool, createWriteTool, createZodFunctionTool, editTool, globTool, grepTool, readTool, validateWorkspaceManifestPath, webFetchTool, webSearchTool, writeTool, zodToJsonSchema };
1639
1408
 
1640
1409
  //# sourceMappingURL=index.js.map