@robota-sdk/agent-tools 3.0.0-beta.6 → 3.0.0-beta.60
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 +38 -10
- package/dist/node/index.cjs +299 -228
- package/dist/node/index.d.cts +3 -24
- package/dist/node/index.d.ts +3 -24
- package/dist/node/index.js +308 -237
- package/package.json +14 -3
package/README.md
CHANGED
|
@@ -46,16 +46,20 @@ const agent = new Robota({
|
|
|
46
46
|
|
|
47
47
|
## Built-in Tools (8)
|
|
48
48
|
|
|
49
|
-
| Export | Tool Name | Description
|
|
50
|
-
| --------------- | --------- |
|
|
51
|
-
| `bashTool` | Bash | Execute shell commands
|
|
52
|
-
| `readTool` | Read | Read file contents with line numbers |
|
|
53
|
-
| `writeTool` | Write | Write content to a file
|
|
54
|
-
| `editTool` | Edit | Replace a specific string in a file
|
|
55
|
-
| `globTool` | Glob | Find files matching a glob pattern |
|
|
56
|
-
| `grepTool` | Grep | Search file contents with regex
|
|
57
|
-
| `webFetchTool` | WebFetch | Fetch URL content (HTML-to-text)
|
|
58
|
-
| `webSearchTool` | WebSearch | Web search via Brave Search API
|
|
49
|
+
| Export | Tool Name | Description |
|
|
50
|
+
| --------------- | --------- | ------------------------------------------------ |
|
|
51
|
+
| `bashTool` | Bash | Execute shell commands via `child_process.spawn` |
|
|
52
|
+
| `readTool` | Read | Read file contents with line numbers (cat -n) |
|
|
53
|
+
| `writeTool` | Write | Write content to a file (creates parent dirs) |
|
|
54
|
+
| `editTool` | Edit | Replace a specific string in a file |
|
|
55
|
+
| `globTool` | Glob | Find files matching a glob pattern (fast-glob) |
|
|
56
|
+
| `grepTool` | Grep | Search file contents with regex patterns |
|
|
57
|
+
| `webFetchTool` | WebFetch | Fetch URL content (HTML-to-text conversion) |
|
|
58
|
+
| `webSearchTool` | WebSearch | Web search via Brave Search API |
|
|
59
|
+
|
|
60
|
+
## Edit and Write Safety
|
|
61
|
+
|
|
62
|
+
Recent file tool updates keep write/edit behavior atomic and make Edit tool results easier for higher layers to display. Atomic replacements preserve existing target mode bits, so executable scripts remain executable after Write or Edit updates. The Edit tool returns line metadata for changed regions, allowing the CLI to render concise context hunks instead of dumping full files or opaque summaries.
|
|
59
63
|
|
|
60
64
|
## Tool Infrastructure
|
|
61
65
|
|
|
@@ -66,7 +70,31 @@ const agent = new Robota({
|
|
|
66
70
|
| `createFunctionTool` | Factory for creating function tools |
|
|
67
71
|
| `createZodFunctionTool` | Factory with Zod validation and JSON Schema conversion |
|
|
68
72
|
| `OpenAPITool` | Tool generated from OpenAPI specification |
|
|
73
|
+
| `createOpenAPITool` | Factory for creating OpenAPI tools |
|
|
69
74
|
| `zodToJsonSchema` | Converts Zod schemas to JSON Schema format |
|
|
75
|
+
| `TToolResult` | Result type for built-in CLI tool invocations |
|
|
76
|
+
|
|
77
|
+
## TToolResult Shape
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
interface TToolResult {
|
|
81
|
+
success: boolean;
|
|
82
|
+
output: string;
|
|
83
|
+
error?: string;
|
|
84
|
+
exitCode?: number;
|
|
85
|
+
startLine?: number; // Start line number of the edit in the original file (Edit tool only)
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
`TToolResult` is the inner result type used by built-in tools. It is serialized to JSON and placed inside the `IToolResult.data` field before being returned to the Robota execution loop.
|
|
90
|
+
|
|
91
|
+
## Dependencies
|
|
92
|
+
|
|
93
|
+
| Dependency | Kind | Purpose |
|
|
94
|
+
| ------------------------ | ---- | ------------------------------------------------------ |
|
|
95
|
+
| `@robota-sdk/agent-core` | Peer | Abstract tool base class, tool interfaces, event types |
|
|
96
|
+
| `fast-glob` | Prod | High-performance glob matching for the Glob tool |
|
|
97
|
+
| `zod` | Prod | Schema validation for function tool parameters |
|
|
70
98
|
|
|
71
99
|
## License
|
|
72
100
|
|
package/dist/node/index.cjs
CHANGED
|
@@ -215,7 +215,9 @@ function zodToJsonSchema(schema, options = {}) {
|
|
|
215
215
|
type: "object",
|
|
216
216
|
properties,
|
|
217
217
|
required,
|
|
218
|
-
...options.allowAdditionalProperties
|
|
218
|
+
...(options.allowAdditionalProperties || schemaDef.unknownKeys === "passthrough") && {
|
|
219
|
+
additionalProperties: true
|
|
220
|
+
}
|
|
219
221
|
};
|
|
220
222
|
}
|
|
221
223
|
function convertZodTypeToProperty(typeObj) {
|
|
@@ -294,6 +296,100 @@ function isRequiredField(typeObj) {
|
|
|
294
296
|
return typeDef.typeName !== "ZodOptional" && typeDef.typeName !== "ZodNullable" && typeDef.typeName !== "ZodDefault";
|
|
295
297
|
}
|
|
296
298
|
|
|
299
|
+
// src/implementations/function-tool/parameter-validator.ts
|
|
300
|
+
function validateParameterType(key, value, schema) {
|
|
301
|
+
const expectedType = schema["type"];
|
|
302
|
+
switch (expectedType) {
|
|
303
|
+
case "string":
|
|
304
|
+
if (typeof value !== "string") {
|
|
305
|
+
return `Parameter "${key}" must be a string, got ${typeof value}`;
|
|
306
|
+
}
|
|
307
|
+
break;
|
|
308
|
+
case "number":
|
|
309
|
+
if (typeof value !== "number" || isNaN(value)) {
|
|
310
|
+
return `Parameter "${key}" must be a number, got ${typeof value}`;
|
|
311
|
+
}
|
|
312
|
+
break;
|
|
313
|
+
case "boolean":
|
|
314
|
+
if (typeof value !== "boolean") {
|
|
315
|
+
return `Parameter "${key}" must be a boolean, got ${typeof value}`;
|
|
316
|
+
}
|
|
317
|
+
break;
|
|
318
|
+
case "array":
|
|
319
|
+
if (!Array.isArray(value)) {
|
|
320
|
+
return `Parameter "${key}" must be an array, got ${typeof value}`;
|
|
321
|
+
}
|
|
322
|
+
if (schema.items) {
|
|
323
|
+
for (let i = 0; i < value.length; i++) {
|
|
324
|
+
const itemError = validateParameterType(`${key}[${i}]`, value[i], schema.items);
|
|
325
|
+
if (itemError) {
|
|
326
|
+
return itemError;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
break;
|
|
331
|
+
case "object":
|
|
332
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
333
|
+
return `Parameter "${key}" must be an object, got ${typeof value}`;
|
|
334
|
+
}
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
if (schema.enum && schema.enum.length > 0) {
|
|
338
|
+
const enumValues = schema.enum;
|
|
339
|
+
let isValidEnum = false;
|
|
340
|
+
for (const enumValue of enumValues) {
|
|
341
|
+
if (value === enumValue) {
|
|
342
|
+
isValidEnum = true;
|
|
343
|
+
break;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
if (!isValidEnum) {
|
|
347
|
+
return `Parameter "${key}" must be one of: ${enumValues.join(", ")}, got ${value}`;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
return void 0;
|
|
351
|
+
}
|
|
352
|
+
function getValidationErrors(parameters, schemaRequired, schemaProperties, additionalProperties) {
|
|
353
|
+
const errors = [];
|
|
354
|
+
for (const field of schemaRequired) {
|
|
355
|
+
if (!(field in parameters)) {
|
|
356
|
+
errors.push(`Missing required parameter: ${field}`);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
360
|
+
const paramSchema = schemaProperties[key];
|
|
361
|
+
if (!paramSchema) {
|
|
362
|
+
if (additionalProperties === true) {
|
|
363
|
+
continue;
|
|
364
|
+
}
|
|
365
|
+
if (additionalProperties && typeof additionalProperties === "object") {
|
|
366
|
+
const additionalTypeError = validateParameterType(key, value, additionalProperties);
|
|
367
|
+
if (additionalTypeError) errors.push(additionalTypeError);
|
|
368
|
+
continue;
|
|
369
|
+
}
|
|
370
|
+
errors.push(`Unknown parameter: ${key}`);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const typeError = validateParameterType(key, value, paramSchema);
|
|
374
|
+
if (typeError) {
|
|
375
|
+
errors.push(typeError);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
return errors;
|
|
379
|
+
}
|
|
380
|
+
function validateToolParameters(parameters, schemaRequired, schemaProperties, additionalProperties) {
|
|
381
|
+
const errors = getValidationErrors(
|
|
382
|
+
parameters,
|
|
383
|
+
schemaRequired,
|
|
384
|
+
schemaProperties,
|
|
385
|
+
additionalProperties
|
|
386
|
+
);
|
|
387
|
+
return {
|
|
388
|
+
isValid: errors.length === 0,
|
|
389
|
+
errors
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
|
|
297
393
|
// src/implementations/function-tool.ts
|
|
298
394
|
var FunctionTool = class {
|
|
299
395
|
schema;
|
|
@@ -324,7 +420,12 @@ var FunctionTool = class {
|
|
|
324
420
|
async execute(parameters, context) {
|
|
325
421
|
const toolName = this.schema.name;
|
|
326
422
|
if (!this.validate(parameters)) {
|
|
327
|
-
const errors =
|
|
423
|
+
const errors = getValidationErrors(
|
|
424
|
+
parameters,
|
|
425
|
+
this.schema.parameters.required || [],
|
|
426
|
+
this.schema.parameters.properties || {},
|
|
427
|
+
this.schema.parameters.additionalProperties
|
|
428
|
+
);
|
|
328
429
|
throw new import_agent_core3.ValidationError(`Invalid parameters for tool "${toolName}": ${errors.join(", ")}`);
|
|
329
430
|
}
|
|
330
431
|
const startTime = Date.now();
|
|
@@ -360,17 +461,23 @@ var FunctionTool = class {
|
|
|
360
461
|
* Validate parameters (simple boolean result)
|
|
361
462
|
*/
|
|
362
463
|
validate(parameters) {
|
|
363
|
-
return
|
|
464
|
+
return getValidationErrors(
|
|
465
|
+
parameters,
|
|
466
|
+
this.schema.parameters.required || [],
|
|
467
|
+
this.schema.parameters.properties || {},
|
|
468
|
+
this.schema.parameters.additionalProperties
|
|
469
|
+
).length === 0;
|
|
364
470
|
}
|
|
365
471
|
/**
|
|
366
472
|
* Validate tool parameters with detailed result
|
|
367
473
|
*/
|
|
368
474
|
validateParameters(parameters) {
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
475
|
+
return validateToolParameters(
|
|
476
|
+
parameters,
|
|
477
|
+
this.schema.parameters.required || [],
|
|
478
|
+
this.schema.parameters.properties || {},
|
|
479
|
+
this.schema.parameters.additionalProperties
|
|
480
|
+
);
|
|
374
481
|
}
|
|
375
482
|
/**
|
|
376
483
|
* Get tool description
|
|
@@ -378,86 +485,6 @@ var FunctionTool = class {
|
|
|
378
485
|
getDescription() {
|
|
379
486
|
return this.schema.description;
|
|
380
487
|
}
|
|
381
|
-
/**
|
|
382
|
-
* Get detailed validation errors
|
|
383
|
-
*/
|
|
384
|
-
getValidationErrors(parameters) {
|
|
385
|
-
const errors = [];
|
|
386
|
-
const required = this.schema.parameters.required || [];
|
|
387
|
-
const properties = this.schema.parameters.properties || {};
|
|
388
|
-
for (const field of required) {
|
|
389
|
-
if (!(field in parameters)) {
|
|
390
|
-
errors.push(`Missing required parameter: ${field}`);
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
for (const [key, value] of Object.entries(parameters)) {
|
|
394
|
-
const paramSchema = properties[key];
|
|
395
|
-
if (!paramSchema) {
|
|
396
|
-
errors.push(`Unknown parameter: ${key}`);
|
|
397
|
-
continue;
|
|
398
|
-
}
|
|
399
|
-
const typeError = this.validateParameterType(key, value, paramSchema);
|
|
400
|
-
if (typeError) {
|
|
401
|
-
errors.push(typeError);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
return errors;
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Validate individual parameter type
|
|
408
|
-
*/
|
|
409
|
-
validateParameterType(key, value, schema) {
|
|
410
|
-
const expectedType = schema["type"];
|
|
411
|
-
switch (expectedType) {
|
|
412
|
-
case "string":
|
|
413
|
-
if (typeof value !== "string") {
|
|
414
|
-
return `Parameter "${key}" must be a string, got ${typeof value}`;
|
|
415
|
-
}
|
|
416
|
-
break;
|
|
417
|
-
case "number":
|
|
418
|
-
if (typeof value !== "number" || isNaN(value)) {
|
|
419
|
-
return `Parameter "${key}" must be a number, got ${typeof value}`;
|
|
420
|
-
}
|
|
421
|
-
break;
|
|
422
|
-
case "boolean":
|
|
423
|
-
if (typeof value !== "boolean") {
|
|
424
|
-
return `Parameter "${key}" must be a boolean, got ${typeof value}`;
|
|
425
|
-
}
|
|
426
|
-
break;
|
|
427
|
-
case "array":
|
|
428
|
-
if (!Array.isArray(value)) {
|
|
429
|
-
return `Parameter "${key}" must be an array, got ${typeof value}`;
|
|
430
|
-
}
|
|
431
|
-
if (schema.items) {
|
|
432
|
-
for (let i = 0; i < value.length; i++) {
|
|
433
|
-
const itemError = this.validateParameterType(`${key}[${i}]`, value[i], schema.items);
|
|
434
|
-
if (itemError) {
|
|
435
|
-
return itemError;
|
|
436
|
-
}
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
break;
|
|
440
|
-
case "object":
|
|
441
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
442
|
-
return `Parameter "${key}" must be an object, got ${typeof value}`;
|
|
443
|
-
}
|
|
444
|
-
break;
|
|
445
|
-
}
|
|
446
|
-
if (schema.enum && schema.enum.length > 0) {
|
|
447
|
-
const enumValues = schema.enum;
|
|
448
|
-
let isValidEnum = false;
|
|
449
|
-
for (const enumValue of enumValues) {
|
|
450
|
-
if (value === enumValue) {
|
|
451
|
-
isValidEnum = true;
|
|
452
|
-
break;
|
|
453
|
-
}
|
|
454
|
-
}
|
|
455
|
-
if (!isValidEnum) {
|
|
456
|
-
return `Parameter "${key}" must be one of: ${enumValues.join(", ")}, got ${value}`;
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
return void 0;
|
|
460
|
-
}
|
|
461
488
|
/**
|
|
462
489
|
* Validate constructor inputs
|
|
463
490
|
*/
|
|
@@ -501,6 +528,132 @@ function createZodFunctionTool(name, description, zodSchema, fn) {
|
|
|
501
528
|
|
|
502
529
|
// src/implementations/openapi-tool.ts
|
|
503
530
|
var import_agent_core4 = require("@robota-sdk/agent-core");
|
|
531
|
+
|
|
532
|
+
// src/implementations/openapi-schema-converter.ts
|
|
533
|
+
var HTTP_METHODS = [
|
|
534
|
+
"get",
|
|
535
|
+
"post",
|
|
536
|
+
"put",
|
|
537
|
+
"delete",
|
|
538
|
+
"patch",
|
|
539
|
+
"head",
|
|
540
|
+
"options"
|
|
541
|
+
];
|
|
542
|
+
function findOperation(apiSpec, operationId) {
|
|
543
|
+
for (const [path, pathItem] of Object.entries(apiSpec.paths || {})) {
|
|
544
|
+
if (!pathItem) continue;
|
|
545
|
+
for (const method of HTTP_METHODS) {
|
|
546
|
+
const operation = pathItem[method];
|
|
547
|
+
if (operation?.operationId === operationId) {
|
|
548
|
+
return { method, path, operation };
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return void 0;
|
|
553
|
+
}
|
|
554
|
+
function mapOpenAPIType(type) {
|
|
555
|
+
switch (type) {
|
|
556
|
+
case "string":
|
|
557
|
+
return "string";
|
|
558
|
+
case "number":
|
|
559
|
+
return "number";
|
|
560
|
+
case "integer":
|
|
561
|
+
return "integer";
|
|
562
|
+
case "boolean":
|
|
563
|
+
return "boolean";
|
|
564
|
+
case "array":
|
|
565
|
+
return "array";
|
|
566
|
+
case "object":
|
|
567
|
+
return "object";
|
|
568
|
+
default:
|
|
569
|
+
return "string";
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
function convertOpenAPISchemaToParameterSchema(schema) {
|
|
573
|
+
if ("$ref" in schema) {
|
|
574
|
+
return { type: "object" };
|
|
575
|
+
}
|
|
576
|
+
const result = {
|
|
577
|
+
type: mapOpenAPIType(schema.type)
|
|
578
|
+
};
|
|
579
|
+
if (schema.description) {
|
|
580
|
+
result.description = schema.description;
|
|
581
|
+
}
|
|
582
|
+
if (schema.enum) {
|
|
583
|
+
result.enum = schema.enum;
|
|
584
|
+
}
|
|
585
|
+
if (schema.minimum !== void 0) {
|
|
586
|
+
result.minimum = schema.minimum;
|
|
587
|
+
}
|
|
588
|
+
if (schema.maximum !== void 0) {
|
|
589
|
+
result.maximum = schema.maximum;
|
|
590
|
+
}
|
|
591
|
+
if (schema.pattern) {
|
|
592
|
+
result.pattern = schema.pattern;
|
|
593
|
+
}
|
|
594
|
+
if (schema.format) {
|
|
595
|
+
result.format = schema.format;
|
|
596
|
+
}
|
|
597
|
+
if (schema.default !== void 0) {
|
|
598
|
+
result.default = schema.default;
|
|
599
|
+
}
|
|
600
|
+
if (schema.type === "array" && schema.items) {
|
|
601
|
+
result.items = convertOpenAPISchemaToParameterSchema(schema.items);
|
|
602
|
+
}
|
|
603
|
+
if (schema.type === "object" && schema.properties) {
|
|
604
|
+
result.properties = {};
|
|
605
|
+
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
606
|
+
result.properties[propName] = convertOpenAPISchemaToParameterSchema(propSchema);
|
|
607
|
+
}
|
|
608
|
+
if (schema.required && schema.required.length > 0) {
|
|
609
|
+
result.required = schema.required;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
return result;
|
|
613
|
+
}
|
|
614
|
+
function convertOpenAPIParamToSchema(param) {
|
|
615
|
+
const schema = param.schema;
|
|
616
|
+
return convertOpenAPISchemaToParameterSchema(schema);
|
|
617
|
+
}
|
|
618
|
+
function createSchemaFromOperation(operationId, opSpec) {
|
|
619
|
+
const properties = {};
|
|
620
|
+
const required = [];
|
|
621
|
+
const params = opSpec.parameters || [];
|
|
622
|
+
for (const param of params) {
|
|
623
|
+
properties[param.name] = convertOpenAPIParamToSchema(param);
|
|
624
|
+
if (param.required) {
|
|
625
|
+
required.push(param.name);
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
if (opSpec.requestBody) {
|
|
629
|
+
const requestBody = opSpec.requestBody;
|
|
630
|
+
const jsonContent = requestBody.content?.["application/json"];
|
|
631
|
+
if (jsonContent?.schema) {
|
|
632
|
+
const bodySchema = convertOpenAPISchemaToParameterSchema(jsonContent.schema);
|
|
633
|
+
if (bodySchema.type === "object" && bodySchema.properties) {
|
|
634
|
+
Object.assign(properties, bodySchema.properties);
|
|
635
|
+
const schemaWithRequired = bodySchema;
|
|
636
|
+
if (schemaWithRequired.required) {
|
|
637
|
+
required.push(...schemaWithRequired.required);
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
const schemaParams = {
|
|
643
|
+
type: "object",
|
|
644
|
+
properties
|
|
645
|
+
};
|
|
646
|
+
if (required.length > 0) {
|
|
647
|
+
schemaParams.required = required;
|
|
648
|
+
}
|
|
649
|
+
return {
|
|
650
|
+
name: operationId,
|
|
651
|
+
description: opSpec.summary || opSpec.description || `OpenAPI operation: ${operationId}`,
|
|
652
|
+
parameters: schemaParams
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// src/implementations/openapi-tool.ts
|
|
504
657
|
var OpenAPITool = class {
|
|
505
658
|
schema;
|
|
506
659
|
apiSpec;
|
|
@@ -607,36 +760,13 @@ var OpenAPITool = class {
|
|
|
607
760
|
* @private
|
|
608
761
|
*/
|
|
609
762
|
async executeAPICall(parameters, _context) {
|
|
610
|
-
const operation = this.
|
|
763
|
+
const operation = findOperation(this.apiSpec, this.operationId);
|
|
611
764
|
if (!operation) {
|
|
612
765
|
throw new Error(`Operation ${this.operationId} not found in OpenAPI spec`);
|
|
613
766
|
}
|
|
614
|
-
|
|
767
|
+
this.buildRequestConfig(operation, parameters);
|
|
615
768
|
throw new Error("Not implemented: actual API execution is not yet available");
|
|
616
769
|
}
|
|
617
|
-
/**
|
|
618
|
-
* Find the operation in the OpenAPI specification
|
|
619
|
-
*/
|
|
620
|
-
findOperation() {
|
|
621
|
-
for (const [path, pathItem] of Object.entries(this.apiSpec.paths || {})) {
|
|
622
|
-
if (!pathItem) continue;
|
|
623
|
-
for (const method of [
|
|
624
|
-
"get",
|
|
625
|
-
"post",
|
|
626
|
-
"put",
|
|
627
|
-
"delete",
|
|
628
|
-
"patch",
|
|
629
|
-
"head",
|
|
630
|
-
"options"
|
|
631
|
-
]) {
|
|
632
|
-
const operation = pathItem[method];
|
|
633
|
-
if (operation?.operationId === this.operationId) {
|
|
634
|
-
return { method, path, operation };
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
return void 0;
|
|
639
|
-
}
|
|
640
770
|
/**
|
|
641
771
|
* Build HTTP request configuration from OpenAPI operation and parameters
|
|
642
772
|
*/
|
|
@@ -708,121 +838,13 @@ var OpenAPITool = class {
|
|
|
708
838
|
* Create tool schema from OpenAPI operation specification
|
|
709
839
|
*/
|
|
710
840
|
createSchemaFromOpenAPI() {
|
|
711
|
-
const operation = this.
|
|
841
|
+
const operation = findOperation(this.apiSpec, this.operationId);
|
|
712
842
|
if (!operation) {
|
|
713
843
|
throw new Error(
|
|
714
844
|
`[STRICT-POLICY][EMITTER-CONTRACT] OpenAPI operation not found: ${this.operationId}. Emitter contract must provide a valid operationId present in the OpenAPI document.`
|
|
715
845
|
);
|
|
716
846
|
}
|
|
717
|
-
|
|
718
|
-
const properties = {};
|
|
719
|
-
const required = [];
|
|
720
|
-
const params = opSpec.parameters || [];
|
|
721
|
-
for (const param of params) {
|
|
722
|
-
properties[param.name] = this.convertOpenAPIParamToSchema(param);
|
|
723
|
-
if (param.required) {
|
|
724
|
-
required.push(param.name);
|
|
725
|
-
}
|
|
726
|
-
}
|
|
727
|
-
if (opSpec.requestBody) {
|
|
728
|
-
const requestBody = opSpec.requestBody;
|
|
729
|
-
const jsonContent = requestBody.content?.["application/json"];
|
|
730
|
-
if (jsonContent?.schema) {
|
|
731
|
-
const bodySchema = this.convertOpenAPISchemaToParameterSchema(jsonContent.schema);
|
|
732
|
-
if (bodySchema.type === "object" && bodySchema.properties) {
|
|
733
|
-
Object.assign(properties, bodySchema.properties);
|
|
734
|
-
const schemaWithRequired = bodySchema;
|
|
735
|
-
if (schemaWithRequired.required) {
|
|
736
|
-
required.push(...schemaWithRequired.required);
|
|
737
|
-
}
|
|
738
|
-
}
|
|
739
|
-
}
|
|
740
|
-
}
|
|
741
|
-
const schemaParams = {
|
|
742
|
-
type: "object",
|
|
743
|
-
properties
|
|
744
|
-
};
|
|
745
|
-
if (required.length > 0) {
|
|
746
|
-
schemaParams.required = required;
|
|
747
|
-
}
|
|
748
|
-
return {
|
|
749
|
-
name: this.operationId,
|
|
750
|
-
description: opSpec.summary || opSpec.description || `OpenAPI operation: ${this.operationId}`,
|
|
751
|
-
parameters: schemaParams
|
|
752
|
-
};
|
|
753
|
-
}
|
|
754
|
-
/**
|
|
755
|
-
* Convert OpenAPI parameter to tool parameter schema
|
|
756
|
-
*/
|
|
757
|
-
convertOpenAPIParamToSchema(param) {
|
|
758
|
-
const schema = param.schema;
|
|
759
|
-
return this.convertOpenAPISchemaToParameterSchema(schema);
|
|
760
|
-
}
|
|
761
|
-
/**
|
|
762
|
-
* Convert OpenAPI schema to parameter schema
|
|
763
|
-
*/
|
|
764
|
-
convertOpenAPISchemaToParameterSchema(schema) {
|
|
765
|
-
if ("$ref" in schema) {
|
|
766
|
-
return { type: "object" };
|
|
767
|
-
}
|
|
768
|
-
const result = {
|
|
769
|
-
type: this.mapOpenAPIType(schema.type)
|
|
770
|
-
};
|
|
771
|
-
if (schema.description) {
|
|
772
|
-
result.description = schema.description;
|
|
773
|
-
}
|
|
774
|
-
if (schema.enum) {
|
|
775
|
-
result.enum = schema.enum;
|
|
776
|
-
}
|
|
777
|
-
if (schema.minimum !== void 0) {
|
|
778
|
-
result.minimum = schema.minimum;
|
|
779
|
-
}
|
|
780
|
-
if (schema.maximum !== void 0) {
|
|
781
|
-
result.maximum = schema.maximum;
|
|
782
|
-
}
|
|
783
|
-
if (schema.pattern) {
|
|
784
|
-
result.pattern = schema.pattern;
|
|
785
|
-
}
|
|
786
|
-
if (schema.format) {
|
|
787
|
-
result.format = schema.format;
|
|
788
|
-
}
|
|
789
|
-
if (schema.default !== void 0) {
|
|
790
|
-
result.default = schema.default;
|
|
791
|
-
}
|
|
792
|
-
if (schema.type === "array" && schema.items) {
|
|
793
|
-
result.items = this.convertOpenAPISchemaToParameterSchema(schema.items);
|
|
794
|
-
}
|
|
795
|
-
if (schema.type === "object" && schema.properties) {
|
|
796
|
-
result.properties = {};
|
|
797
|
-
for (const [propName, propSchema] of Object.entries(schema.properties)) {
|
|
798
|
-
result.properties[propName] = this.convertOpenAPISchemaToParameterSchema(propSchema);
|
|
799
|
-
}
|
|
800
|
-
if (schema.required && schema.required.length > 0) {
|
|
801
|
-
result.required = schema.required;
|
|
802
|
-
}
|
|
803
|
-
}
|
|
804
|
-
return result;
|
|
805
|
-
}
|
|
806
|
-
/**
|
|
807
|
-
* Map OpenAPI type to JSON schema type
|
|
808
|
-
*/
|
|
809
|
-
mapOpenAPIType(type) {
|
|
810
|
-
switch (type) {
|
|
811
|
-
case "string":
|
|
812
|
-
return "string";
|
|
813
|
-
case "number":
|
|
814
|
-
return "number";
|
|
815
|
-
case "integer":
|
|
816
|
-
return "integer";
|
|
817
|
-
case "boolean":
|
|
818
|
-
return "boolean";
|
|
819
|
-
case "array":
|
|
820
|
-
return "array";
|
|
821
|
-
case "object":
|
|
822
|
-
return "object";
|
|
823
|
-
default:
|
|
824
|
-
return "string";
|
|
825
|
-
}
|
|
847
|
+
return createSchemaFromOperation(this.operationId, operation.operation);
|
|
826
848
|
}
|
|
827
849
|
};
|
|
828
850
|
function createOpenAPITool(config) {
|
|
@@ -859,6 +881,11 @@ async function runBash(args) {
|
|
|
859
881
|
const timer = setTimeout(() => {
|
|
860
882
|
timedOut = true;
|
|
861
883
|
child.kill("SIGTERM");
|
|
884
|
+
settle({
|
|
885
|
+
success: false,
|
|
886
|
+
output: Buffer.concat(stdoutChunks).toString("utf8"),
|
|
887
|
+
error: `Command timed out after ${timeout}ms`
|
|
888
|
+
});
|
|
862
889
|
}, timeout);
|
|
863
890
|
function settle(result) {
|
|
864
891
|
if (settled) return;
|
|
@@ -1004,9 +1031,51 @@ var readTool = createZodFunctionTool(
|
|
|
1004
1031
|
);
|
|
1005
1032
|
|
|
1006
1033
|
// src/builtins/write-tool.ts
|
|
1034
|
+
var import_zod3 = require("zod");
|
|
1035
|
+
|
|
1036
|
+
// src/builtins/atomic-file-write.ts
|
|
1037
|
+
var import_node_crypto = require("crypto");
|
|
1007
1038
|
var import_promises2 = require("fs/promises");
|
|
1008
1039
|
var import_node_path = require("path");
|
|
1009
|
-
var
|
|
1040
|
+
var TEMP_RANDOM_BYTES = 6;
|
|
1041
|
+
var PRESERVED_MODE_BITS = 4095;
|
|
1042
|
+
var MISSING_FILE_ERROR_CODE = "ENOENT";
|
|
1043
|
+
function createTempFilePath(filePath) {
|
|
1044
|
+
const dir = (0, import_node_path.dirname)(filePath);
|
|
1045
|
+
const name = (0, import_node_path.basename)(filePath);
|
|
1046
|
+
const suffix = (0, import_node_crypto.randomBytes)(TEMP_RANDOM_BYTES).toString("hex");
|
|
1047
|
+
return (0, import_node_path.join)(dir, `.${name}.robota-tmp-${process.pid}-${Date.now()}-${suffix}`);
|
|
1048
|
+
}
|
|
1049
|
+
async function readExistingMode(filePath) {
|
|
1050
|
+
try {
|
|
1051
|
+
const fileStats = await (0, import_promises2.stat)(filePath);
|
|
1052
|
+
return fileStats.mode & PRESERVED_MODE_BITS;
|
|
1053
|
+
} catch (error) {
|
|
1054
|
+
if (error instanceof Error && hasErrorCode(error, MISSING_FILE_ERROR_CODE)) return void 0;
|
|
1055
|
+
throw error;
|
|
1056
|
+
}
|
|
1057
|
+
}
|
|
1058
|
+
function hasErrorCode(error, code) {
|
|
1059
|
+
return "code" in error && error.code === code;
|
|
1060
|
+
}
|
|
1061
|
+
async function atomicWriteUtf8File(filePath, content) {
|
|
1062
|
+
const dir = (0, import_node_path.dirname)(filePath);
|
|
1063
|
+
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
1064
|
+
const existingMode = await readExistingMode(filePath);
|
|
1065
|
+
const tempFilePath = createTempFilePath(filePath);
|
|
1066
|
+
try {
|
|
1067
|
+
await (0, import_promises2.writeFile)(tempFilePath, content, "utf8");
|
|
1068
|
+
if (existingMode !== void 0) {
|
|
1069
|
+
await (0, import_promises2.chmod)(tempFilePath, existingMode);
|
|
1070
|
+
}
|
|
1071
|
+
await (0, import_promises2.rename)(tempFilePath, filePath);
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
await (0, import_promises2.rm)(tempFilePath, { force: true }).catch(() => void 0);
|
|
1074
|
+
throw error;
|
|
1075
|
+
}
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
// src/builtins/write-tool.ts
|
|
1010
1079
|
var WriteSchema = import_zod3.z.object({
|
|
1011
1080
|
filePath: import_zod3.z.string().describe("The absolute path to the file to write"),
|
|
1012
1081
|
content: import_zod3.z.string().describe("The content to write to the file")
|
|
@@ -1014,8 +1083,7 @@ var WriteSchema = import_zod3.z.object({
|
|
|
1014
1083
|
async function writeFileTool(args) {
|
|
1015
1084
|
const { filePath, content } = args;
|
|
1016
1085
|
try {
|
|
1017
|
-
await (
|
|
1018
|
-
await (0, import_promises2.writeFile)(filePath, content, "utf8");
|
|
1086
|
+
await atomicWriteUtf8File(filePath, content);
|
|
1019
1087
|
const result = {
|
|
1020
1088
|
success: true,
|
|
1021
1089
|
output: `Written ${Buffer.byteLength(content, "utf8")} bytes to ${filePath}`
|
|
@@ -1086,7 +1154,7 @@ async function editFileTool(args) {
|
|
|
1086
1154
|
}
|
|
1087
1155
|
const updated = replaceAll ? content.split(oldString).join(newString) : content.slice(0, content.indexOf(oldString)) + newString + content.slice(content.indexOf(oldString) + oldString.length);
|
|
1088
1156
|
try {
|
|
1089
|
-
await (
|
|
1157
|
+
await atomicWriteUtf8File(filePath, updated);
|
|
1090
1158
|
} catch (err) {
|
|
1091
1159
|
const result2 = {
|
|
1092
1160
|
success: false,
|
|
@@ -1096,9 +1164,12 @@ async function editFileTool(args) {
|
|
|
1096
1164
|
return JSON.stringify(result2);
|
|
1097
1165
|
}
|
|
1098
1166
|
const count = replaceAll ? content.split(oldString).length - 1 : 1;
|
|
1167
|
+
const matchIdx = content.indexOf(oldString);
|
|
1168
|
+
const startLine = matchIdx >= 0 ? content.substring(0, matchIdx).split("\n").length : 1;
|
|
1099
1169
|
const result = {
|
|
1100
1170
|
success: true,
|
|
1101
|
-
output: `Replaced ${count} occurrence(s) in ${filePath}
|
|
1171
|
+
output: `Replaced ${count} occurrence(s) in ${filePath}`,
|
|
1172
|
+
startLine
|
|
1102
1173
|
};
|
|
1103
1174
|
return JSON.stringify(result);
|
|
1104
1175
|
}
|