mcp-from-openapi 2.6.0 → 2.7.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/README.md +25 -3
- package/annotations.d.ts +16 -1
- package/arazzo-expressions.d.ts +19 -0
- package/arazzo-types.d.ts +262 -0
- package/arazzo.d.ts +45 -0
- package/elicitation.d.ts +44 -0
- package/errors.d.ts +15 -0
- package/esm/index.mjs +2668 -93
- package/esm/package.json +5 -3
- package/generator.d.ts +22 -0
- package/index.d.ts +18 -2
- package/index.js +2681 -93
- package/lint.d.ts +33 -0
- package/naming-presets.d.ts +49 -0
- package/overlay.d.ts +43 -0
- package/package.json +5 -3
- package/schema-builder.d.ts +17 -0
- package/token-report.d.ts +65 -0
- package/type-signature.d.ts +43 -0
- package/types.d.ts +154 -4
- package/validator.d.ts +5 -0
package/esm/index.mjs
CHANGED
|
@@ -6,9 +6,24 @@ function isReferenceObject(obj) {
|
|
|
6
6
|
return obj && typeof obj === "object" && "$ref" in obj;
|
|
7
7
|
}
|
|
8
8
|
function toJsonSchema(schema) {
|
|
9
|
+
return convertSchema(schema, /* @__PURE__ */ new Set());
|
|
10
|
+
}
|
|
11
|
+
function convertSchema(schema, stack) {
|
|
9
12
|
if (isReferenceObject(schema)) {
|
|
10
13
|
return { $ref: schema.$ref };
|
|
11
14
|
}
|
|
15
|
+
if (stack.has(schema)) {
|
|
16
|
+
return {};
|
|
17
|
+
}
|
|
18
|
+
stack.add(schema);
|
|
19
|
+
try {
|
|
20
|
+
return convertSchemaInner(schema, stack);
|
|
21
|
+
} finally {
|
|
22
|
+
stack.delete(schema);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function convertSchemaInner(schema, stack) {
|
|
26
|
+
const recurse = (value) => convertSchema(value, stack);
|
|
12
27
|
const { exclusiveMaximum, exclusiveMinimum, maximum, minimum, ...rest } = schema;
|
|
13
28
|
const { nullable, example, ...cleanRest } = rest;
|
|
14
29
|
const result = { ...cleanRest };
|
|
@@ -60,34 +75,34 @@ function toJsonSchema(schema) {
|
|
|
60
75
|
if (result["properties"] && typeof result["properties"] === "object") {
|
|
61
76
|
const props = {};
|
|
62
77
|
for (const [key, value] of Object.entries(result["properties"])) {
|
|
63
|
-
props[key] =
|
|
78
|
+
props[key] = recurse(value);
|
|
64
79
|
}
|
|
65
80
|
result["properties"] = props;
|
|
66
81
|
}
|
|
67
82
|
if (result["items"]) {
|
|
68
83
|
if (Array.isArray(result["items"])) {
|
|
69
|
-
result["items"] = result["items"].map(
|
|
84
|
+
result["items"] = result["items"].map(recurse);
|
|
70
85
|
} else {
|
|
71
|
-
result["items"] =
|
|
86
|
+
result["items"] = recurse(result["items"]);
|
|
72
87
|
}
|
|
73
88
|
}
|
|
74
89
|
if (result["additionalProperties"] && typeof result["additionalProperties"] === "object") {
|
|
75
|
-
result["additionalProperties"] =
|
|
90
|
+
result["additionalProperties"] = recurse(result["additionalProperties"]);
|
|
76
91
|
}
|
|
77
92
|
for (const key of ["allOf", "anyOf", "oneOf"]) {
|
|
78
93
|
if (result[key] && Array.isArray(result[key])) {
|
|
79
|
-
result[key] = result[key].map(
|
|
94
|
+
result[key] = result[key].map(recurse);
|
|
80
95
|
}
|
|
81
96
|
}
|
|
82
97
|
if (result["not"]) {
|
|
83
|
-
result["not"] =
|
|
98
|
+
result["not"] = recurse(result["not"]);
|
|
84
99
|
}
|
|
85
100
|
for (const key of ["patternProperties", "$defs", "definitions", "dependentSchemas"]) {
|
|
86
101
|
const value = result[key];
|
|
87
102
|
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
88
103
|
const mapped = {};
|
|
89
104
|
for (const [name, sub] of Object.entries(value)) {
|
|
90
|
-
mapped[name] =
|
|
105
|
+
mapped[name] = recurse(sub);
|
|
91
106
|
}
|
|
92
107
|
result[key] = mapped;
|
|
93
108
|
}
|
|
@@ -104,11 +119,11 @@ function toJsonSchema(schema) {
|
|
|
104
119
|
]) {
|
|
105
120
|
const value = result[key];
|
|
106
121
|
if (value && typeof value === "object") {
|
|
107
|
-
result[key] =
|
|
122
|
+
result[key] = recurse(value);
|
|
108
123
|
}
|
|
109
124
|
}
|
|
110
125
|
if (Array.isArray(result["prefixItems"])) {
|
|
111
|
-
result["prefixItems"] = result["prefixItems"].map(
|
|
126
|
+
result["prefixItems"] = result["prefixItems"].map(recurse);
|
|
112
127
|
}
|
|
113
128
|
if (wrapNullable) {
|
|
114
129
|
const wrapper = {};
|
|
@@ -129,8 +144,11 @@ var ParameterResolver = class {
|
|
|
129
144
|
namingStrategy;
|
|
130
145
|
includeExamples;
|
|
131
146
|
constructor(namingStrategy, options) {
|
|
132
|
-
this.namingStrategy =
|
|
133
|
-
|
|
147
|
+
this.namingStrategy = {
|
|
148
|
+
...namingStrategy,
|
|
149
|
+
// Bind a supplied resolver to its own strategy object so class-based
|
|
150
|
+
// strategies keep their `this` (we invoke it off a spread clone).
|
|
151
|
+
conflictResolver: namingStrategy?.conflictResolver ? namingStrategy.conflictResolver.bind(namingStrategy) : this.defaultConflictResolver
|
|
134
152
|
};
|
|
135
153
|
this.includeExamples = options?.includeExamples ?? false;
|
|
136
154
|
}
|
|
@@ -312,6 +330,9 @@ var ParameterResolver = class {
|
|
|
312
330
|
schema["deprecated"] = true;
|
|
313
331
|
}
|
|
314
332
|
schema["x-parameter-location"] = param.location;
|
|
333
|
+
if (param.location === "header") {
|
|
334
|
+
schema["x-mcp-header"] = param.name;
|
|
335
|
+
}
|
|
315
336
|
if (param.style) {
|
|
316
337
|
schema["x-parameter-style"] = param.style;
|
|
317
338
|
}
|
|
@@ -406,6 +427,9 @@ var ParameterResolver = class {
|
|
|
406
427
|
});
|
|
407
428
|
const schemeInInput = includeInInput === true || Array.isArray(includeInInput) && includeInInput.includes(scheme);
|
|
408
429
|
if (schemeInInput) {
|
|
430
|
+
if (paramLocation === "header") {
|
|
431
|
+
schema["x-mcp-header"] = headerKey;
|
|
432
|
+
}
|
|
409
433
|
properties[inputKey] = schema;
|
|
410
434
|
required.push(inputKey);
|
|
411
435
|
}
|
|
@@ -930,6 +954,96 @@ var SchemaBuilder = class {
|
|
|
930
954
|
}
|
|
931
955
|
return copy;
|
|
932
956
|
}
|
|
957
|
+
// Copy-on-walk over every structural keyword (same key groups as
|
|
958
|
+
// truncateDepth): `visit` transforms each node top-down and must return a
|
|
959
|
+
// new node when it changes anything.
|
|
960
|
+
static walkCopy(node, visit, seen = /* @__PURE__ */ new Map()) {
|
|
961
|
+
if (!node || typeof node !== "object") return node;
|
|
962
|
+
const existing = seen.get(node);
|
|
963
|
+
if (existing) return existing;
|
|
964
|
+
const copy = visit({ ...node });
|
|
965
|
+
seen.set(node, copy);
|
|
966
|
+
for (const key of this.TRUNCATE_MAP_KEYS) {
|
|
967
|
+
const value = copy[key];
|
|
968
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
969
|
+
const mapped = {};
|
|
970
|
+
for (const [name, sub] of Object.entries(value)) {
|
|
971
|
+
mapped[name] = this.walkCopy(sub, visit, seen);
|
|
972
|
+
}
|
|
973
|
+
copy[key] = mapped;
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
for (const key of this.TRUNCATE_SCHEMA_KEYS) {
|
|
977
|
+
const value = copy[key];
|
|
978
|
+
if (Array.isArray(value)) {
|
|
979
|
+
copy[key] = value.map((item) => this.walkCopy(item, visit, seen));
|
|
980
|
+
} else if (value !== null && typeof value === "object") {
|
|
981
|
+
copy[key] = this.walkCopy(value, visit, seen);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
for (const key of this.TRUNCATE_LIST_KEYS) {
|
|
985
|
+
const value = copy[key];
|
|
986
|
+
if (Array.isArray(value)) {
|
|
987
|
+
copy[key] = value.map((member) => this.walkCopy(member, visit, seen));
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
return copy;
|
|
991
|
+
}
|
|
992
|
+
/**
|
|
993
|
+
* Limit every object node to its first `max` properties (declaration
|
|
994
|
+
* order). Dropped properties are pruned from `required` and counted in a
|
|
995
|
+
* note appended to the node's description.
|
|
996
|
+
*/
|
|
997
|
+
static limitProperties(schema, max) {
|
|
998
|
+
const bound = Number.isFinite(max) ? Math.max(1, Math.floor(max)) : Number.MAX_SAFE_INTEGER;
|
|
999
|
+
return this.walkCopy(schema, (node) => {
|
|
1000
|
+
const properties = node.properties;
|
|
1001
|
+
if (!properties || typeof properties !== "object") return node;
|
|
1002
|
+
const entries = Object.entries(properties);
|
|
1003
|
+
if (entries.length <= bound) return node;
|
|
1004
|
+
const kept = entries.slice(0, bound);
|
|
1005
|
+
const keptNames = new Set(kept.map(([name]) => name));
|
|
1006
|
+
const dropped = entries.length - bound;
|
|
1007
|
+
const note = `[${dropped} additional propert${dropped === 1 ? "y" : "ies"} omitted: exceeds maxProperties]`;
|
|
1008
|
+
const next = { ...node, properties: Object.fromEntries(kept) };
|
|
1009
|
+
if (Array.isArray(node.required)) {
|
|
1010
|
+
const required = node.required.filter((name) => keptNames.has(String(name)));
|
|
1011
|
+
if (required.length > 0) {
|
|
1012
|
+
next.required = required;
|
|
1013
|
+
} else {
|
|
1014
|
+
delete next.required;
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
next.description = node.description ? `${node.description} ${note}` : note;
|
|
1018
|
+
return next;
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
/**
|
|
1022
|
+
* Cap every description in the schema tree to `maxLength` characters,
|
|
1023
|
+
* truncating with an ellipsis.
|
|
1024
|
+
*/
|
|
1025
|
+
static capDescriptions(schema, maxLength) {
|
|
1026
|
+
const bound = Number.isFinite(maxLength) ? Math.max(1, Math.floor(maxLength)) : Number.MAX_SAFE_INTEGER;
|
|
1027
|
+
return this.walkCopy(schema, (node) => {
|
|
1028
|
+
if (typeof node.description === "string" && node.description.length > bound) {
|
|
1029
|
+
return { ...node, description: `${node.description.slice(0, bound - 1)}\u2026` };
|
|
1030
|
+
}
|
|
1031
|
+
return node;
|
|
1032
|
+
});
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Remove every `examples` array from the schema tree (a token-budget
|
|
1036
|
+
* trimming step — validation keywords are untouched).
|
|
1037
|
+
*/
|
|
1038
|
+
static stripExamples(schema) {
|
|
1039
|
+
return this.walkCopy(schema, (node) => {
|
|
1040
|
+
if ("examples" in node) {
|
|
1041
|
+
const { examples: _examples, ...rest } = node;
|
|
1042
|
+
return rest;
|
|
1043
|
+
}
|
|
1044
|
+
return node;
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
933
1047
|
/**
|
|
934
1048
|
* Simplify schema by removing unnecessary fields
|
|
935
1049
|
*/
|
|
@@ -988,9 +1102,63 @@ function mergeOverrides(base, layer) {
|
|
|
988
1102
|
...layer.description !== void 0 && { description: layer.description },
|
|
989
1103
|
...(base.annotations || layer.annotations) && {
|
|
990
1104
|
annotations: { ...base.annotations, ...layer.annotations }
|
|
991
|
-
}
|
|
1105
|
+
},
|
|
1106
|
+
...(base.meta || layer.meta) && { meta: { ...base.meta, ...layer.meta } },
|
|
1107
|
+
...layer.icons !== void 0 && { icons: layer.icons }
|
|
992
1108
|
};
|
|
993
1109
|
}
|
|
1110
|
+
function cleanseMeta(node, seen) {
|
|
1111
|
+
if (!node || typeof node !== "object") {
|
|
1112
|
+
return node;
|
|
1113
|
+
}
|
|
1114
|
+
if (seen.has(node)) {
|
|
1115
|
+
return void 0;
|
|
1116
|
+
}
|
|
1117
|
+
seen.add(node);
|
|
1118
|
+
try {
|
|
1119
|
+
if (Array.isArray(node)) {
|
|
1120
|
+
return node.map((item) => cleanseMeta(item, seen));
|
|
1121
|
+
}
|
|
1122
|
+
const out = {};
|
|
1123
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1124
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
1125
|
+
out[key] = cleanseMeta(value, seen);
|
|
1126
|
+
}
|
|
1127
|
+
return out;
|
|
1128
|
+
} finally {
|
|
1129
|
+
seen.delete(node);
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
function sanitizeMeta(value) {
|
|
1133
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
1134
|
+
return cleanseMeta(value, /* @__PURE__ */ new Set());
|
|
1135
|
+
}
|
|
1136
|
+
return void 0;
|
|
1137
|
+
}
|
|
1138
|
+
function isAllowedIconSrc(src) {
|
|
1139
|
+
const lower = src.toLowerCase();
|
|
1140
|
+
return lower.startsWith("https:") || lower.startsWith("data:");
|
|
1141
|
+
}
|
|
1142
|
+
function sanitizeIcons(value) {
|
|
1143
|
+
if (!Array.isArray(value)) {
|
|
1144
|
+
return void 0;
|
|
1145
|
+
}
|
|
1146
|
+
const icons = [];
|
|
1147
|
+
for (const entry of value) {
|
|
1148
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) continue;
|
|
1149
|
+
const raw = entry;
|
|
1150
|
+
if (typeof raw["src"] !== "string" || !isAllowedIconSrc(raw["src"])) continue;
|
|
1151
|
+
const icon = { src: raw["src"] };
|
|
1152
|
+
if (typeof raw["mimeType"] === "string") {
|
|
1153
|
+
icon.mimeType = raw["mimeType"];
|
|
1154
|
+
}
|
|
1155
|
+
if (Array.isArray(raw["sizes"]) && raw["sizes"].every((s) => typeof s === "string")) {
|
|
1156
|
+
icon.sizes = [...raw["sizes"]];
|
|
1157
|
+
}
|
|
1158
|
+
icons.push(icon);
|
|
1159
|
+
}
|
|
1160
|
+
return icons.length > 0 ? icons : void 0;
|
|
1161
|
+
}
|
|
994
1162
|
function readXMcp(node) {
|
|
995
1163
|
return node["x-mcp"];
|
|
996
1164
|
}
|
|
@@ -1039,16 +1207,24 @@ function extractExtensionOverrides(operation) {
|
|
|
1039
1207
|
name: typeof ext["name"] === "string" ? ext["name"] : void 0,
|
|
1040
1208
|
title: typeof ext["title"] === "string" ? ext["title"] : void 0,
|
|
1041
1209
|
description: typeof ext["description"] === "string" ? ext["description"] : void 0,
|
|
1042
|
-
annotations: pickAnnotations(ext["annotations"])
|
|
1210
|
+
annotations: pickAnnotations(ext["annotations"]),
|
|
1211
|
+
meta: sanitizeMeta(ext["meta"]),
|
|
1212
|
+
icons: sanitizeIcons(ext["icons"])
|
|
1043
1213
|
});
|
|
1044
1214
|
}
|
|
1045
1215
|
const frontmcp = op["x-frontmcp"];
|
|
1046
|
-
if (frontmcp && typeof frontmcp === "object"
|
|
1047
|
-
const
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1216
|
+
if (frontmcp && typeof frontmcp === "object") {
|
|
1217
|
+
const layer = {
|
|
1218
|
+
meta: sanitizeMeta(frontmcp.meta),
|
|
1219
|
+
icons: sanitizeIcons(frontmcp.icons)
|
|
1220
|
+
};
|
|
1221
|
+
if (frontmcp.annotations) {
|
|
1222
|
+
layer.annotations = pickAnnotations(frontmcp.annotations);
|
|
1223
|
+
if (typeof frontmcp.annotations.title === "string") {
|
|
1224
|
+
layer.title = frontmcp.annotations.title;
|
|
1225
|
+
}
|
|
1226
|
+
}
|
|
1227
|
+
result = mergeOverrides(result, layer);
|
|
1052
1228
|
}
|
|
1053
1229
|
return result;
|
|
1054
1230
|
}
|
|
@@ -1370,6 +1546,564 @@ function applyClientTarget(schema, target) {
|
|
|
1370
1546
|
return result;
|
|
1371
1547
|
}
|
|
1372
1548
|
|
|
1549
|
+
// src/errors.ts
|
|
1550
|
+
var OpenAPIToolError = class extends Error {
|
|
1551
|
+
context;
|
|
1552
|
+
constructor(message, context) {
|
|
1553
|
+
super(message);
|
|
1554
|
+
this.name = this.constructor.name;
|
|
1555
|
+
this.context = context;
|
|
1556
|
+
if (Error.captureStackTrace) {
|
|
1557
|
+
Error.captureStackTrace(this, this.constructor);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
};
|
|
1561
|
+
var LoadError = class extends OpenAPIToolError {
|
|
1562
|
+
constructor(message, context) {
|
|
1563
|
+
super(message, context);
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
var SsrfError = class extends LoadError {
|
|
1567
|
+
constructor(message, context) {
|
|
1568
|
+
super(message, context);
|
|
1569
|
+
}
|
|
1570
|
+
};
|
|
1571
|
+
var ParseError = class extends OpenAPIToolError {
|
|
1572
|
+
constructor(message, context) {
|
|
1573
|
+
super(message, context);
|
|
1574
|
+
}
|
|
1575
|
+
};
|
|
1576
|
+
var ValidationError = class extends OpenAPIToolError {
|
|
1577
|
+
errors;
|
|
1578
|
+
constructor(message, context) {
|
|
1579
|
+
super(message, context);
|
|
1580
|
+
this.errors = context?.["errors"];
|
|
1581
|
+
}
|
|
1582
|
+
};
|
|
1583
|
+
var GenerationError = class extends OpenAPIToolError {
|
|
1584
|
+
constructor(message, context) {
|
|
1585
|
+
super(message, context);
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
var OverlayError = class extends OpenAPIToolError {
|
|
1589
|
+
constructor(message, context) {
|
|
1590
|
+
super(message, context);
|
|
1591
|
+
}
|
|
1592
|
+
};
|
|
1593
|
+
var RequestBuildError = class extends OpenAPIToolError {
|
|
1594
|
+
constructor(message, context) {
|
|
1595
|
+
super(message, context);
|
|
1596
|
+
}
|
|
1597
|
+
};
|
|
1598
|
+
var ArazzoError = class extends OpenAPIToolError {
|
|
1599
|
+
path;
|
|
1600
|
+
constructor(message, context) {
|
|
1601
|
+
super(message, context);
|
|
1602
|
+
this.path = context?.["path"];
|
|
1603
|
+
}
|
|
1604
|
+
};
|
|
1605
|
+
var SchemaError = class extends OpenAPIToolError {
|
|
1606
|
+
constructor(message, context) {
|
|
1607
|
+
super(message, context);
|
|
1608
|
+
}
|
|
1609
|
+
};
|
|
1610
|
+
|
|
1611
|
+
// src/overlay.ts
|
|
1612
|
+
function parsePath(path) {
|
|
1613
|
+
if (typeof path !== "string" || !path.startsWith("$")) {
|
|
1614
|
+
throw new OverlayError(`Overlay target must be a JSONPath starting with '$'; received '${String(path)}'`, {
|
|
1615
|
+
target: path
|
|
1616
|
+
});
|
|
1617
|
+
}
|
|
1618
|
+
const segments = [];
|
|
1619
|
+
let rest = path.slice(1);
|
|
1620
|
+
while (rest.length > 0) {
|
|
1621
|
+
let recursive = false;
|
|
1622
|
+
if (rest.startsWith("..")) {
|
|
1623
|
+
recursive = true;
|
|
1624
|
+
rest = rest.slice(2);
|
|
1625
|
+
const bare = rest.match(/^([A-Za-z_][\w-]*)/);
|
|
1626
|
+
if (bare) {
|
|
1627
|
+
segments.push({ kind: "child", name: bare[1], recursive });
|
|
1628
|
+
rest = rest.slice(bare[0].length);
|
|
1629
|
+
continue;
|
|
1630
|
+
}
|
|
1631
|
+
} else if (rest.startsWith(".")) {
|
|
1632
|
+
rest = rest.slice(1);
|
|
1633
|
+
if (rest.startsWith("*")) {
|
|
1634
|
+
segments.push({ kind: "wildcard", recursive });
|
|
1635
|
+
rest = rest.slice(1);
|
|
1636
|
+
continue;
|
|
1637
|
+
}
|
|
1638
|
+
const bare = rest.match(/^([A-Za-z_][\w-]*)/);
|
|
1639
|
+
if (bare) {
|
|
1640
|
+
segments.push({ kind: "child", name: bare[1], recursive });
|
|
1641
|
+
rest = rest.slice(bare[0].length);
|
|
1642
|
+
continue;
|
|
1643
|
+
}
|
|
1644
|
+
throw new OverlayError(`Invalid JSONPath segment after '.' in '${path}'`, { target: path });
|
|
1645
|
+
}
|
|
1646
|
+
if (!rest.startsWith("[")) {
|
|
1647
|
+
throw new OverlayError(`Invalid JSONPath segment at '${rest}' in '${path}'`, { target: path });
|
|
1648
|
+
}
|
|
1649
|
+
const bracket = matchBracket(rest, path);
|
|
1650
|
+
const inner = bracket.inner.trim();
|
|
1651
|
+
rest = bracket.rest;
|
|
1652
|
+
if (inner === "*") {
|
|
1653
|
+
segments.push({ kind: "wildcard", recursive });
|
|
1654
|
+
} else if (/^-?\d+$/.test(inner)) {
|
|
1655
|
+
segments.push({ kind: "index", index: parseInt(inner, 10), recursive });
|
|
1656
|
+
} else if (/^'.*'$/.test(inner) || /^".*"$/.test(inner)) {
|
|
1657
|
+
segments.push({ kind: "child", name: inner.slice(1, -1), recursive });
|
|
1658
|
+
} else if (inner.startsWith("?(") && inner.endsWith(")")) {
|
|
1659
|
+
segments.push(parseFilter(inner.slice(2, -1).trim(), path, recursive));
|
|
1660
|
+
} else {
|
|
1661
|
+
throw new OverlayError(`Unsupported JSONPath selector '[${inner}]' in '${path}'`, { target: path });
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return segments;
|
|
1665
|
+
}
|
|
1666
|
+
function matchBracket(input, fullPath) {
|
|
1667
|
+
let quote = null;
|
|
1668
|
+
let depth = 0;
|
|
1669
|
+
for (let i = 1; i < input.length; i++) {
|
|
1670
|
+
const char = input[i];
|
|
1671
|
+
if (quote) {
|
|
1672
|
+
if (char === quote) quote = null;
|
|
1673
|
+
} else if (char === "'" || char === '"') {
|
|
1674
|
+
quote = char;
|
|
1675
|
+
} else if (char === "[") {
|
|
1676
|
+
depth++;
|
|
1677
|
+
} else if (char === "]") {
|
|
1678
|
+
if (depth === 0) {
|
|
1679
|
+
return { inner: input.slice(1, i), rest: input.slice(i + 1) };
|
|
1680
|
+
}
|
|
1681
|
+
depth--;
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
throw new OverlayError(`Unterminated '[' selector in '${fullPath}'`, { target: fullPath });
|
|
1685
|
+
}
|
|
1686
|
+
function parseFilter(expr, path, recursive) {
|
|
1687
|
+
const match = expr.match(/^@(?:\.([A-Za-z_][\w-]*)|\['([^']*)'\]|\["([^"]*)"\])\s*(?:(==|!=)\s*(.+))?$/);
|
|
1688
|
+
if (!match) {
|
|
1689
|
+
throw new OverlayError(`Unsupported filter expression '?(${expr})' in '${path}'`, { target: path });
|
|
1690
|
+
}
|
|
1691
|
+
const field = match[1] ?? match[2] ?? match[3];
|
|
1692
|
+
const op = match[4];
|
|
1693
|
+
if (!op) {
|
|
1694
|
+
return { kind: "filter", field, op: "exists", recursive };
|
|
1695
|
+
}
|
|
1696
|
+
const raw = match[5].trim();
|
|
1697
|
+
let literal;
|
|
1698
|
+
if (/^'.*'$/.test(raw) || /^".*"$/.test(raw)) {
|
|
1699
|
+
literal = raw.slice(1, -1);
|
|
1700
|
+
} else if (/^-?\d+(\.\d+)?$/.test(raw)) {
|
|
1701
|
+
literal = parseFloat(raw);
|
|
1702
|
+
} else if (raw === "true" || raw === "false") {
|
|
1703
|
+
literal = raw === "true";
|
|
1704
|
+
} else {
|
|
1705
|
+
throw new OverlayError(`Unsupported filter literal '${raw}' in '${path}'`, { target: path });
|
|
1706
|
+
}
|
|
1707
|
+
return { kind: "filter", field, op, literal, recursive };
|
|
1708
|
+
}
|
|
1709
|
+
function isContainer(value) {
|
|
1710
|
+
return value !== null && typeof value === "object";
|
|
1711
|
+
}
|
|
1712
|
+
var UNSAFE_KEYS = /* @__PURE__ */ new Set(["__proto__", "constructor", "prototype"]);
|
|
1713
|
+
function descendants(match) {
|
|
1714
|
+
const result = [];
|
|
1715
|
+
const walk = (node) => {
|
|
1716
|
+
if (!isContainer(node)) return;
|
|
1717
|
+
if (Array.isArray(node)) {
|
|
1718
|
+
node.forEach((item, index) => {
|
|
1719
|
+
result.push({ parent: node, key: index, value: item });
|
|
1720
|
+
walk(item);
|
|
1721
|
+
});
|
|
1722
|
+
} else {
|
|
1723
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1724
|
+
result.push({ parent: node, key, value });
|
|
1725
|
+
walk(value);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
};
|
|
1729
|
+
walk(match.value);
|
|
1730
|
+
return result;
|
|
1731
|
+
}
|
|
1732
|
+
function dedupeMatches(matches) {
|
|
1733
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1734
|
+
const result = [];
|
|
1735
|
+
for (const match of matches) {
|
|
1736
|
+
let keys = seen.get(match.parent);
|
|
1737
|
+
if (!keys) {
|
|
1738
|
+
keys = /* @__PURE__ */ new Set();
|
|
1739
|
+
seen.set(match.parent, keys);
|
|
1740
|
+
}
|
|
1741
|
+
if (keys.has(match.key)) continue;
|
|
1742
|
+
keys.add(match.key);
|
|
1743
|
+
result.push(match);
|
|
1744
|
+
}
|
|
1745
|
+
return result;
|
|
1746
|
+
}
|
|
1747
|
+
function applySegment(matches, segment) {
|
|
1748
|
+
const scope = segment.recursive ? matches.flatMap((m) => [m, ...descendants(m)]) : matches;
|
|
1749
|
+
const next = [];
|
|
1750
|
+
for (const match of scope) {
|
|
1751
|
+
const node = match.value;
|
|
1752
|
+
switch (segment.kind) {
|
|
1753
|
+
case "child": {
|
|
1754
|
+
if (isContainer(node) && !Array.isArray(node) && !UNSAFE_KEYS.has(segment.name) && Object.prototype.hasOwnProperty.call(node, segment.name)) {
|
|
1755
|
+
next.push({ parent: node, key: segment.name, value: node[segment.name] });
|
|
1756
|
+
}
|
|
1757
|
+
break;
|
|
1758
|
+
}
|
|
1759
|
+
case "wildcard": {
|
|
1760
|
+
if (Array.isArray(node)) {
|
|
1761
|
+
node.forEach((item, index) => next.push({ parent: node, key: index, value: item }));
|
|
1762
|
+
} else if (isContainer(node)) {
|
|
1763
|
+
for (const [key, value] of Object.entries(node)) {
|
|
1764
|
+
next.push({ parent: node, key, value });
|
|
1765
|
+
}
|
|
1766
|
+
}
|
|
1767
|
+
break;
|
|
1768
|
+
}
|
|
1769
|
+
case "index": {
|
|
1770
|
+
if (Array.isArray(node)) {
|
|
1771
|
+
const index = segment.index < 0 ? node.length + segment.index : segment.index;
|
|
1772
|
+
if (index >= 0 && index < node.length) {
|
|
1773
|
+
next.push({ parent: node, key: index, value: node[index] });
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
break;
|
|
1777
|
+
}
|
|
1778
|
+
case "filter": {
|
|
1779
|
+
const members = Array.isArray(node) ? node.map((item, index) => ({ parent: node, key: index, value: item })) : isContainer(node) ? Object.entries(node).map(([key, value]) => ({ parent: node, key, value })) : [];
|
|
1780
|
+
for (const member of members) {
|
|
1781
|
+
if (!isContainer(member.value) || Array.isArray(member.value)) continue;
|
|
1782
|
+
const fieldValue = member.value[segment.field];
|
|
1783
|
+
const keep = segment.op === "exists" ? fieldValue !== void 0 : segment.op === "==" ? fieldValue === segment.literal : fieldValue !== segment.literal;
|
|
1784
|
+
if (keep) next.push(member);
|
|
1785
|
+
}
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
}
|
|
1790
|
+
return next;
|
|
1791
|
+
}
|
|
1792
|
+
function deepMerge(target, update) {
|
|
1793
|
+
for (const [key, value] of Object.entries(update)) {
|
|
1794
|
+
if (key === "__proto__" || key === "constructor" || key === "prototype") continue;
|
|
1795
|
+
const existing = target[key];
|
|
1796
|
+
if (isContainer(value) && !Array.isArray(value) && isContainer(existing) && !Array.isArray(existing)) {
|
|
1797
|
+
deepMerge(existing, value);
|
|
1798
|
+
} else {
|
|
1799
|
+
target[key] = value;
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
}
|
|
1803
|
+
function applyOverlay(document, overlay) {
|
|
1804
|
+
if (!overlay || typeof overlay !== "object" || !Array.isArray(overlay.actions)) {
|
|
1805
|
+
throw new OverlayError("Overlay document must have an actions array", {});
|
|
1806
|
+
}
|
|
1807
|
+
const result = JSON.parse(JSON.stringify(document));
|
|
1808
|
+
for (const [index, action] of overlay.actions.entries()) {
|
|
1809
|
+
if (!action || typeof action !== "object" || typeof action.target !== "string") {
|
|
1810
|
+
throw new OverlayError(`Overlay action #${index} must have a string target`, { index });
|
|
1811
|
+
}
|
|
1812
|
+
if (action.update === void 0 && action.remove !== true) {
|
|
1813
|
+
throw new OverlayError(`Overlay action #${index} needs 'update' or 'remove: true'`, {
|
|
1814
|
+
index,
|
|
1815
|
+
target: action.target
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
const segments = parsePath(action.target);
|
|
1819
|
+
let matches = [{ parent: null, key: null, value: result }];
|
|
1820
|
+
for (const segment of segments) {
|
|
1821
|
+
matches = dedupeMatches(applySegment(matches, segment));
|
|
1822
|
+
}
|
|
1823
|
+
if (action.remove === true) {
|
|
1824
|
+
const arrayRemovals = /* @__PURE__ */ new Map();
|
|
1825
|
+
for (const match of matches) {
|
|
1826
|
+
if (match.parent === null) {
|
|
1827
|
+
throw new OverlayError("Overlay cannot remove the document root", { target: action.target });
|
|
1828
|
+
}
|
|
1829
|
+
if (Array.isArray(match.parent)) {
|
|
1830
|
+
const indices = arrayRemovals.get(match.parent) ?? [];
|
|
1831
|
+
indices.push(match.key);
|
|
1832
|
+
arrayRemovals.set(match.parent, indices);
|
|
1833
|
+
} else {
|
|
1834
|
+
delete match.parent[match.key];
|
|
1835
|
+
}
|
|
1836
|
+
}
|
|
1837
|
+
for (const [parent, indices] of arrayRemovals) {
|
|
1838
|
+
for (const index2 of indices.sort((a, b) => b - a)) {
|
|
1839
|
+
parent.splice(index2, 1);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
continue;
|
|
1843
|
+
}
|
|
1844
|
+
for (const match of matches) {
|
|
1845
|
+
const node = match.value;
|
|
1846
|
+
if (Array.isArray(node)) {
|
|
1847
|
+
node.push(action.update);
|
|
1848
|
+
} else if (isContainer(node) && isContainer(action.update) && !Array.isArray(action.update)) {
|
|
1849
|
+
deepMerge(node, action.update);
|
|
1850
|
+
} else {
|
|
1851
|
+
if (match.parent === null) {
|
|
1852
|
+
throw new OverlayError("Overlay cannot replace the document root with a non-object", {
|
|
1853
|
+
target: action.target
|
|
1854
|
+
});
|
|
1855
|
+
}
|
|
1856
|
+
match.parent[match.key] = action.update;
|
|
1857
|
+
}
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return result;
|
|
1861
|
+
}
|
|
1862
|
+
|
|
1863
|
+
// src/lint.ts
|
|
1864
|
+
var METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
1865
|
+
var PAGINATION_PARAM = /^(page|limit|offset|cursor|per_page|pagesize|page_size|after|before)$/i;
|
|
1866
|
+
var DEEP_SCHEMA_THRESHOLD = 8;
|
|
1867
|
+
var WIDE_SCHEMA_THRESHOLD = 30;
|
|
1868
|
+
function measureSchema(node, seen = /* @__PURE__ */ new Map()) {
|
|
1869
|
+
if (node === null || typeof node !== "object") {
|
|
1870
|
+
return { depth: 0, widestObject: 0, hasArray: false };
|
|
1871
|
+
}
|
|
1872
|
+
if (seen.has(node)) {
|
|
1873
|
+
return seen.get(node) ?? { depth: 0, widestObject: 0, hasArray: false };
|
|
1874
|
+
}
|
|
1875
|
+
seen.set(node, null);
|
|
1876
|
+
const record = node;
|
|
1877
|
+
let childDepth = 0;
|
|
1878
|
+
let widestObject = 0;
|
|
1879
|
+
let hasArray = record["type"] === "array" || Array.isArray(record["type"]) && record["type"].includes("array");
|
|
1880
|
+
const visit = (child) => {
|
|
1881
|
+
const shape2 = measureSchema(child, seen);
|
|
1882
|
+
childDepth = Math.max(childDepth, shape2.depth);
|
|
1883
|
+
widestObject = Math.max(widestObject, shape2.widestObject);
|
|
1884
|
+
hasArray = hasArray || shape2.hasArray;
|
|
1885
|
+
};
|
|
1886
|
+
const properties = record["properties"];
|
|
1887
|
+
if (properties && typeof properties === "object") {
|
|
1888
|
+
widestObject = Math.max(widestObject, Object.keys(properties).length);
|
|
1889
|
+
for (const child of Object.values(properties)) visit(child);
|
|
1890
|
+
}
|
|
1891
|
+
for (const key of ["items", "additionalProperties", "not", "contentSchema"]) {
|
|
1892
|
+
const value = record[key];
|
|
1893
|
+
if (value && typeof value === "object" && !Array.isArray(value)) visit(value);
|
|
1894
|
+
if (Array.isArray(value)) value.forEach(visit);
|
|
1895
|
+
}
|
|
1896
|
+
for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
|
|
1897
|
+
const value = record[key];
|
|
1898
|
+
if (Array.isArray(value)) value.forEach(visit);
|
|
1899
|
+
}
|
|
1900
|
+
const shape = { depth: childDepth + 1, widestObject, hasArray };
|
|
1901
|
+
seen.set(node, shape);
|
|
1902
|
+
return shape;
|
|
1903
|
+
}
|
|
1904
|
+
function schemaHasExample(node, seen = /* @__PURE__ */ new Set()) {
|
|
1905
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return false;
|
|
1906
|
+
seen.add(node);
|
|
1907
|
+
const record = node;
|
|
1908
|
+
if (record["example"] !== void 0 || record["examples"] !== void 0) return true;
|
|
1909
|
+
const properties = record["properties"];
|
|
1910
|
+
if (properties && typeof properties === "object") {
|
|
1911
|
+
if (Object.values(properties).some((child) => schemaHasExample(child, seen))) return true;
|
|
1912
|
+
}
|
|
1913
|
+
for (const key of ["items", "additionalProperties", "not", "contentSchema"]) {
|
|
1914
|
+
const value = record[key];
|
|
1915
|
+
if (value && typeof value === "object" && !Array.isArray(value) && schemaHasExample(value, seen)) return true;
|
|
1916
|
+
if (Array.isArray(value) && value.some((item) => schemaHasExample(item, seen))) return true;
|
|
1917
|
+
}
|
|
1918
|
+
for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
|
|
1919
|
+
const value = record[key];
|
|
1920
|
+
if (Array.isArray(value) && value.some((member) => schemaHasExample(member, seen))) return true;
|
|
1921
|
+
}
|
|
1922
|
+
return false;
|
|
1923
|
+
}
|
|
1924
|
+
function hasAnyExample(content) {
|
|
1925
|
+
if (!content) return false;
|
|
1926
|
+
return Object.values(content).some((media) => {
|
|
1927
|
+
if (!media || typeof media !== "object") return false;
|
|
1928
|
+
const record = media;
|
|
1929
|
+
if (record["example"] !== void 0 || record["examples"] !== void 0) return true;
|
|
1930
|
+
return schemaHasExample(record["schema"]);
|
|
1931
|
+
});
|
|
1932
|
+
}
|
|
1933
|
+
function lintDocument(document) {
|
|
1934
|
+
const findings = [];
|
|
1935
|
+
const operationIds = /* @__PURE__ */ new Map();
|
|
1936
|
+
const paths = document.paths ?? {};
|
|
1937
|
+
for (const [pathStr, pathItem] of Object.entries(paths).sort(([a], [b]) => a < b ? -1 : 1)) {
|
|
1938
|
+
if (!pathItem || "$ref" in pathItem) continue;
|
|
1939
|
+
const pathLevelParameters = (pathItem["parameters"] ?? []).filter(
|
|
1940
|
+
(param) => !isReferenceObject(param)
|
|
1941
|
+
);
|
|
1942
|
+
for (const method of METHODS) {
|
|
1943
|
+
const operation = pathItem[method];
|
|
1944
|
+
if (!operation) continue;
|
|
1945
|
+
const label = `${method.toUpperCase()} ${pathStr}`;
|
|
1946
|
+
if (!operation.operationId) {
|
|
1947
|
+
findings.push({
|
|
1948
|
+
severity: "warning",
|
|
1949
|
+
code: "missing-operation-id",
|
|
1950
|
+
message: "Operation has no operationId; the tool name will be generated from the method and path.",
|
|
1951
|
+
path: label,
|
|
1952
|
+
hint: "Add a short, action-oriented operationId (it becomes the tool name)."
|
|
1953
|
+
});
|
|
1954
|
+
} else {
|
|
1955
|
+
const existing = operationIds.get(operation.operationId) ?? [];
|
|
1956
|
+
existing.push(label);
|
|
1957
|
+
operationIds.set(operation.operationId, existing);
|
|
1958
|
+
if (operation.operationId.length > 64) {
|
|
1959
|
+
findings.push({
|
|
1960
|
+
severity: "info",
|
|
1961
|
+
code: "long-operation-id",
|
|
1962
|
+
message: `operationId '${operation.operationId.slice(0, 40)}\u2026' exceeds 64 characters and will be truncated with a hash suffix.`,
|
|
1963
|
+
path: label,
|
|
1964
|
+
hint: "Shorten the operationId below 64 characters to keep tool names readable."
|
|
1965
|
+
});
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
const prose = `${operation.summary ?? ""} ${operation.description ?? ""}`.trim();
|
|
1969
|
+
if (prose.length === 0) {
|
|
1970
|
+
findings.push({
|
|
1971
|
+
severity: "warning",
|
|
1972
|
+
code: "missing-description",
|
|
1973
|
+
message: "Operation has neither summary nor description; the model only sees the method and path.",
|
|
1974
|
+
path: label,
|
|
1975
|
+
hint: "Describe WHEN to use this operation and what it returns (or patch it in with an overlay)."
|
|
1976
|
+
});
|
|
1977
|
+
} else if (prose.length < 20) {
|
|
1978
|
+
findings.push({
|
|
1979
|
+
severity: "info",
|
|
1980
|
+
code: "vague-description",
|
|
1981
|
+
message: `Operation description is only ${prose.length} characters \u2014 likely too vague for reliable tool selection.`,
|
|
1982
|
+
path: label,
|
|
1983
|
+
hint: "Expand the description with the use case and key parameters."
|
|
1984
|
+
});
|
|
1985
|
+
}
|
|
1986
|
+
const parameters = [
|
|
1987
|
+
...pathLevelParameters,
|
|
1988
|
+
...(operation.parameters ?? []).filter((param) => !isReferenceObject(param))
|
|
1989
|
+
];
|
|
1990
|
+
const undescribed = parameters.filter((param) => !param.description).map((param) => param.name);
|
|
1991
|
+
if (undescribed.length > 0) {
|
|
1992
|
+
findings.push({
|
|
1993
|
+
severity: "info",
|
|
1994
|
+
code: "missing-parameter-description",
|
|
1995
|
+
message: `Parameter(s) without description: ${undescribed.join(", ")}.`,
|
|
1996
|
+
path: label,
|
|
1997
|
+
hint: "Describe each parameter \u2014 models mis-fill undocumented arguments."
|
|
1998
|
+
});
|
|
1999
|
+
}
|
|
2000
|
+
const responses = operation.responses ?? {};
|
|
2001
|
+
const successCodes = Object.keys(responses).filter((code) => /^2(\d\d|XX)$/i.test(code));
|
|
2002
|
+
if (successCodes.length === 0 && !responses["default"]) {
|
|
2003
|
+
findings.push({
|
|
2004
|
+
severity: "warning",
|
|
2005
|
+
code: "missing-success-response",
|
|
2006
|
+
message: "Operation declares no 2xx or default response; no output schema can be generated.",
|
|
2007
|
+
path: label,
|
|
2008
|
+
hint: "Add the success response with its schema."
|
|
2009
|
+
});
|
|
2010
|
+
}
|
|
2011
|
+
let responseShape = { depth: 0, widestObject: 0, hasArray: false };
|
|
2012
|
+
for (const code of [...successCodes, "default"]) {
|
|
2013
|
+
const response = responses[code];
|
|
2014
|
+
if (!response || typeof response !== "object" || isReferenceObject(response)) continue;
|
|
2015
|
+
const content = response["content"];
|
|
2016
|
+
if (!content) continue;
|
|
2017
|
+
for (const media of Object.values(content)) {
|
|
2018
|
+
const schema = media && typeof media === "object" ? media["schema"] : void 0;
|
|
2019
|
+
const shape = measureSchema(schema);
|
|
2020
|
+
responseShape = {
|
|
2021
|
+
depth: Math.max(responseShape.depth, shape.depth),
|
|
2022
|
+
widestObject: Math.max(responseShape.widestObject, shape.widestObject),
|
|
2023
|
+
hasArray: responseShape.hasArray || shape.hasArray
|
|
2024
|
+
};
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
if (method === "get" && responseShape.hasArray) {
|
|
2028
|
+
const hasPagination = parameters.some((param) => param.in === "query" && PAGINATION_PARAM.test(param.name));
|
|
2029
|
+
if (!hasPagination) {
|
|
2030
|
+
findings.push({
|
|
2031
|
+
severity: "warning",
|
|
2032
|
+
code: "unpaginated-list",
|
|
2033
|
+
message: "GET returns an array but declares no pagination parameter \u2014 responses can blow past client result limits (Claude Code caps tool results at 25K tokens).",
|
|
2034
|
+
path: label,
|
|
2035
|
+
hint: "Add limit/cursor/page parameters, or shape responses at the server."
|
|
2036
|
+
});
|
|
2037
|
+
}
|
|
2038
|
+
}
|
|
2039
|
+
const body = operation.requestBody;
|
|
2040
|
+
const bodyContent = body && !isReferenceObject(body) ? body.content : void 0;
|
|
2041
|
+
let requestShape = { depth: 0, widestObject: 0, hasArray: false };
|
|
2042
|
+
for (const media of Object.values(bodyContent ?? {})) {
|
|
2043
|
+
const schema = media && typeof media === "object" ? media["schema"] : void 0;
|
|
2044
|
+
const shape = measureSchema(schema);
|
|
2045
|
+
requestShape = {
|
|
2046
|
+
depth: Math.max(requestShape.depth, shape.depth),
|
|
2047
|
+
widestObject: Math.max(requestShape.widestObject, shape.widestObject),
|
|
2048
|
+
hasArray: requestShape.hasArray || shape.hasArray
|
|
2049
|
+
};
|
|
2050
|
+
}
|
|
2051
|
+
const maxDepth = Math.max(requestShape.depth, responseShape.depth);
|
|
2052
|
+
if (maxDepth > DEEP_SCHEMA_THRESHOLD) {
|
|
2053
|
+
findings.push({
|
|
2054
|
+
severity: "warning",
|
|
2055
|
+
code: "deep-schema",
|
|
2056
|
+
message: `Schema nesting reaches depth ${maxDepth} (threshold ${DEEP_SCHEMA_THRESHOLD}) \u2014 deep schemas cost tokens and reduce accuracy.`,
|
|
2057
|
+
path: label,
|
|
2058
|
+
hint: "Flatten the schema, or bound generation with maxSchemaDepth."
|
|
2059
|
+
});
|
|
2060
|
+
}
|
|
2061
|
+
const maxWidth = Math.max(requestShape.widestObject, responseShape.widestObject);
|
|
2062
|
+
if (maxWidth > WIDE_SCHEMA_THRESHOLD) {
|
|
2063
|
+
findings.push({
|
|
2064
|
+
severity: "info",
|
|
2065
|
+
code: "wide-schema",
|
|
2066
|
+
message: `An object schema declares ${maxWidth} properties (threshold ${WIDE_SCHEMA_THRESHOLD}).`,
|
|
2067
|
+
path: label,
|
|
2068
|
+
hint: "Split the payload, or bound generation with maxProperties."
|
|
2069
|
+
});
|
|
2070
|
+
}
|
|
2071
|
+
if (bodyContent && !hasAnyExample(bodyContent)) {
|
|
2072
|
+
findings.push({
|
|
2073
|
+
severity: "info",
|
|
2074
|
+
code: "missing-request-example",
|
|
2075
|
+
message: "Request body has no example \u2014 examples measurably improve complex-parameter accuracy.",
|
|
2076
|
+
path: label,
|
|
2077
|
+
hint: "Add a media-type example (and enable includeExamples), or patch one in with an overlay."
|
|
2078
|
+
});
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
for (const [operationId, labels] of operationIds) {
|
|
2083
|
+
if (labels.length > 1) {
|
|
2084
|
+
findings.push({
|
|
2085
|
+
severity: "error",
|
|
2086
|
+
code: "duplicate-operation-id",
|
|
2087
|
+
message: `operationId '${operationId}' is used by ${labels.length} operations: ${labels.join(", ")}.`,
|
|
2088
|
+
path: labels[0],
|
|
2089
|
+
hint: "Make operationIds unique \u2014 duplicates force hash-suffixed tool names."
|
|
2090
|
+
});
|
|
2091
|
+
}
|
|
2092
|
+
}
|
|
2093
|
+
const rank = { error: 0, warning: 1, info: 2 };
|
|
2094
|
+
findings.sort(
|
|
2095
|
+
(a, b) => rank[a.severity] - rank[b.severity] || (a.path < b.path ? -1 : a.path > b.path ? 1 : 0) || (a.code < b.code ? -1 : 1)
|
|
2096
|
+
);
|
|
2097
|
+
return {
|
|
2098
|
+
findings,
|
|
2099
|
+
counts: {
|
|
2100
|
+
error: findings.filter((f) => f.severity === "error").length,
|
|
2101
|
+
warning: findings.filter((f) => f.severity === "warning").length,
|
|
2102
|
+
info: findings.filter((f) => f.severity === "info").length
|
|
2103
|
+
}
|
|
2104
|
+
};
|
|
2105
|
+
}
|
|
2106
|
+
|
|
1373
2107
|
// src/validator.ts
|
|
1374
2108
|
var Validator = class {
|
|
1375
2109
|
/**
|
|
@@ -1420,7 +2154,8 @@ var Validator = class {
|
|
|
1420
2154
|
code: "NO_PATHS"
|
|
1421
2155
|
});
|
|
1422
2156
|
} else {
|
|
1423
|
-
|
|
2157
|
+
const componentParameters = document.components?.parameters ?? {};
|
|
2158
|
+
this.validatePaths(document.paths, componentParameters, errors, warnings);
|
|
1424
2159
|
}
|
|
1425
2160
|
if (!document.servers || document.servers.length === 0) {
|
|
1426
2161
|
warnings.push({
|
|
@@ -1451,7 +2186,18 @@ var Validator = class {
|
|
|
1451
2186
|
/**
|
|
1452
2187
|
* Validate paths
|
|
1453
2188
|
*/
|
|
1454
|
-
|
|
2189
|
+
/**
|
|
2190
|
+
* Resolve a local `#/components/parameters/<name>` reference (JSON Pointer
|
|
2191
|
+
* tokens decoded). Returns undefined for external or dangling references.
|
|
2192
|
+
*/
|
|
2193
|
+
resolveParameterRef(param, componentParameters) {
|
|
2194
|
+
if (!param || typeof param !== "object" || !("$ref" in param)) return param;
|
|
2195
|
+
const match = /^#\/components\/parameters\/(.+)$/.exec(String(param.$ref));
|
|
2196
|
+
if (!match) return void 0;
|
|
2197
|
+
const name = match[1].replace(/~1/g, "/").replace(/~0/g, "~");
|
|
2198
|
+
return componentParameters[name];
|
|
2199
|
+
}
|
|
2200
|
+
validatePaths(paths, componentParameters, errors, warnings) {
|
|
1455
2201
|
for (const [path, pathItem] of Object.entries(paths)) {
|
|
1456
2202
|
if (!pathItem) continue;
|
|
1457
2203
|
if (!path.startsWith("/")) {
|
|
@@ -1463,11 +2209,15 @@ var Validator = class {
|
|
|
1463
2209
|
}
|
|
1464
2210
|
const methods = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
1465
2211
|
let hasOperations = false;
|
|
2212
|
+
const pathLevelParameters = Array.isArray(pathItem.parameters) ? pathItem.parameters : [];
|
|
2213
|
+
if (pathLevelParameters.length > 0) {
|
|
2214
|
+
this.validateParameters(pathLevelParameters, `/paths/${path}/parameters`, errors, warnings);
|
|
2215
|
+
}
|
|
1466
2216
|
for (const method of methods) {
|
|
1467
2217
|
const operation = pathItem[method];
|
|
1468
2218
|
if (operation) {
|
|
1469
2219
|
hasOperations = true;
|
|
1470
|
-
this.validateOperation(operation, path, method, errors, warnings);
|
|
2220
|
+
this.validateOperation(operation, path, method, errors, warnings, pathLevelParameters, componentParameters);
|
|
1471
2221
|
}
|
|
1472
2222
|
}
|
|
1473
2223
|
if (!hasOperations && !pathItem.$ref) {
|
|
@@ -1482,7 +2232,7 @@ var Validator = class {
|
|
|
1482
2232
|
/**
|
|
1483
2233
|
* Validate an operation
|
|
1484
2234
|
*/
|
|
1485
|
-
validateOperation(operation, path, method, errors, warnings) {
|
|
2235
|
+
validateOperation(operation, path, method, errors, warnings, pathLevelParameters = [], componentParameters = {}) {
|
|
1486
2236
|
const basePath = `/paths/${path}/${method}`;
|
|
1487
2237
|
if (!operation.operationId) {
|
|
1488
2238
|
warnings.push({
|
|
@@ -1499,14 +2249,18 @@ var Validator = class {
|
|
|
1499
2249
|
});
|
|
1500
2250
|
}
|
|
1501
2251
|
if (operation.parameters) {
|
|
1502
|
-
this.validateParameters(operation.parameters,
|
|
2252
|
+
this.validateParameters(operation.parameters, `${basePath}/parameters`, errors, warnings);
|
|
1503
2253
|
}
|
|
1504
|
-
const
|
|
2254
|
+
const allParameters = [...pathLevelParameters, ...operation.parameters ?? []].map(
|
|
2255
|
+
(p) => this.resolveParameterRef(p, componentParameters)
|
|
2256
|
+
);
|
|
2257
|
+
const hasUnresolvableRefs = allParameters.some((p) => p === void 0);
|
|
2258
|
+
const pathParams = path.match(/\{([^{}]+)\}/g)?.map((p) => p.slice(1, -1)) ?? [];
|
|
1505
2259
|
const definedPathParams = new Set(
|
|
1506
|
-
|
|
2260
|
+
allParameters.filter((p) => p && p.in === "path").map((p) => p.name)
|
|
1507
2261
|
);
|
|
1508
2262
|
for (const param of pathParams) {
|
|
1509
|
-
if (!definedPathParams.has(param)) {
|
|
2263
|
+
if (!hasUnresolvableRefs && !definedPathParams.has(param)) {
|
|
1510
2264
|
errors.push({
|
|
1511
2265
|
message: `Path parameter '${param}' not defined in parameters: ${method.toUpperCase()} ${path}`,
|
|
1512
2266
|
path: `${basePath}/parameters`,
|
|
@@ -1518,11 +2272,13 @@ var Validator = class {
|
|
|
1518
2272
|
/**
|
|
1519
2273
|
* Validate parameters
|
|
1520
2274
|
*/
|
|
1521
|
-
validateParameters(parameters,
|
|
1522
|
-
const basePath = `/paths/${path}/${method}/parameters`;
|
|
2275
|
+
validateParameters(parameters, basePath, errors, warnings) {
|
|
1523
2276
|
for (let i = 0; i < parameters.length; i++) {
|
|
1524
2277
|
const param = parameters[i];
|
|
1525
2278
|
const paramPath = `${basePath}/${i}`;
|
|
2279
|
+
if (param && typeof param === "object" && "$ref" in param) {
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
1526
2282
|
if (!param.name) {
|
|
1527
2283
|
errors.push({
|
|
1528
2284
|
message: "Parameter missing name",
|
|
@@ -1561,56 +2317,6 @@ var Validator = class {
|
|
|
1561
2317
|
}
|
|
1562
2318
|
};
|
|
1563
2319
|
|
|
1564
|
-
// src/errors.ts
|
|
1565
|
-
var OpenAPIToolError = class extends Error {
|
|
1566
|
-
context;
|
|
1567
|
-
constructor(message, context) {
|
|
1568
|
-
super(message);
|
|
1569
|
-
this.name = this.constructor.name;
|
|
1570
|
-
this.context = context;
|
|
1571
|
-
if (Error.captureStackTrace) {
|
|
1572
|
-
Error.captureStackTrace(this, this.constructor);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
};
|
|
1576
|
-
var LoadError = class extends OpenAPIToolError {
|
|
1577
|
-
constructor(message, context) {
|
|
1578
|
-
super(message, context);
|
|
1579
|
-
}
|
|
1580
|
-
};
|
|
1581
|
-
var SsrfError = class extends LoadError {
|
|
1582
|
-
constructor(message, context) {
|
|
1583
|
-
super(message, context);
|
|
1584
|
-
}
|
|
1585
|
-
};
|
|
1586
|
-
var ParseError = class extends OpenAPIToolError {
|
|
1587
|
-
constructor(message, context) {
|
|
1588
|
-
super(message, context);
|
|
1589
|
-
}
|
|
1590
|
-
};
|
|
1591
|
-
var ValidationError = class extends OpenAPIToolError {
|
|
1592
|
-
errors;
|
|
1593
|
-
constructor(message, context) {
|
|
1594
|
-
super(message, context);
|
|
1595
|
-
this.errors = context?.["errors"];
|
|
1596
|
-
}
|
|
1597
|
-
};
|
|
1598
|
-
var GenerationError = class extends OpenAPIToolError {
|
|
1599
|
-
constructor(message, context) {
|
|
1600
|
-
super(message, context);
|
|
1601
|
-
}
|
|
1602
|
-
};
|
|
1603
|
-
var RequestBuildError = class extends OpenAPIToolError {
|
|
1604
|
-
constructor(message, context) {
|
|
1605
|
-
super(message, context);
|
|
1606
|
-
}
|
|
1607
|
-
};
|
|
1608
|
-
var SchemaError = class extends OpenAPIToolError {
|
|
1609
|
-
constructor(message, context) {
|
|
1610
|
-
super(message, context);
|
|
1611
|
-
}
|
|
1612
|
-
};
|
|
1613
|
-
|
|
1614
2320
|
// src/format-resolver.ts
|
|
1615
2321
|
var BUILTIN_FORMAT_RESOLVERS = {
|
|
1616
2322
|
// String formats
|
|
@@ -1720,6 +2426,376 @@ function resolveSchemaFormats(schema, resolvers) {
|
|
|
1720
2426
|
return result;
|
|
1721
2427
|
}
|
|
1722
2428
|
|
|
2429
|
+
// src/type-signature.ts
|
|
2430
|
+
var DEFAULT_MAX_DEPTH = 8;
|
|
2431
|
+
var IDENTIFIER = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
2432
|
+
function toPascalIdentifier(toolName) {
|
|
2433
|
+
const segments = toolName.split(/[^A-Za-z0-9]+/).filter((s) => s.length > 0);
|
|
2434
|
+
const joined = segments.map((s) => s[0].toUpperCase() + s.slice(1)).join("");
|
|
2435
|
+
if (joined === "") {
|
|
2436
|
+
return "Tool";
|
|
2437
|
+
}
|
|
2438
|
+
return /^[0-9]/.test(joined) ? `T${joined}` : joined;
|
|
2439
|
+
}
|
|
2440
|
+
function lowerFirst(name) {
|
|
2441
|
+
return name[0].toLowerCase() + name.slice(1);
|
|
2442
|
+
}
|
|
2443
|
+
function isSchemaRecord(value) {
|
|
2444
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2445
|
+
}
|
|
2446
|
+
function isNullSchema(value) {
|
|
2447
|
+
return isSchemaRecord(value) && value["type"] === "null";
|
|
2448
|
+
}
|
|
2449
|
+
function paren(expr) {
|
|
2450
|
+
return expr.includes(" | ") || expr.includes(" & ") ? `(${expr})` : expr;
|
|
2451
|
+
}
|
|
2452
|
+
function dedupe(parts) {
|
|
2453
|
+
return [...new Set(parts)];
|
|
2454
|
+
}
|
|
2455
|
+
function quoteKey(name) {
|
|
2456
|
+
return IDENTIFIER.test(name) ? name : JSON.stringify(name);
|
|
2457
|
+
}
|
|
2458
|
+
function literalOf(value) {
|
|
2459
|
+
if (value === null) {
|
|
2460
|
+
return "null";
|
|
2461
|
+
}
|
|
2462
|
+
const t = typeof value;
|
|
2463
|
+
if (t === "number") {
|
|
2464
|
+
return Number.isFinite(value) ? JSON.stringify(value) : "number";
|
|
2465
|
+
}
|
|
2466
|
+
if (t === "string" || t === "boolean") {
|
|
2467
|
+
return JSON.stringify(value);
|
|
2468
|
+
}
|
|
2469
|
+
return "unknown";
|
|
2470
|
+
}
|
|
2471
|
+
var RESERVED_WORDS = /* @__PURE__ */ new Set([
|
|
2472
|
+
"break",
|
|
2473
|
+
"case",
|
|
2474
|
+
"catch",
|
|
2475
|
+
"class",
|
|
2476
|
+
"const",
|
|
2477
|
+
"continue",
|
|
2478
|
+
"debugger",
|
|
2479
|
+
"default",
|
|
2480
|
+
"delete",
|
|
2481
|
+
"do",
|
|
2482
|
+
"else",
|
|
2483
|
+
"enum",
|
|
2484
|
+
"export",
|
|
2485
|
+
"extends",
|
|
2486
|
+
"false",
|
|
2487
|
+
"finally",
|
|
2488
|
+
"for",
|
|
2489
|
+
"function",
|
|
2490
|
+
"if",
|
|
2491
|
+
"import",
|
|
2492
|
+
"in",
|
|
2493
|
+
"instanceof",
|
|
2494
|
+
"new",
|
|
2495
|
+
"null",
|
|
2496
|
+
"return",
|
|
2497
|
+
"super",
|
|
2498
|
+
"switch",
|
|
2499
|
+
"this",
|
|
2500
|
+
"throw",
|
|
2501
|
+
"true",
|
|
2502
|
+
"try",
|
|
2503
|
+
"typeof",
|
|
2504
|
+
"var",
|
|
2505
|
+
"void",
|
|
2506
|
+
"while",
|
|
2507
|
+
"with",
|
|
2508
|
+
"implements",
|
|
2509
|
+
"interface",
|
|
2510
|
+
"let",
|
|
2511
|
+
"package",
|
|
2512
|
+
"private",
|
|
2513
|
+
"protected",
|
|
2514
|
+
"public",
|
|
2515
|
+
"static",
|
|
2516
|
+
"yield",
|
|
2517
|
+
"await"
|
|
2518
|
+
]);
|
|
2519
|
+
function escapeJsdoc(text) {
|
|
2520
|
+
return text.replace(/\*\//g, "*\\/");
|
|
2521
|
+
}
|
|
2522
|
+
function jsdocLines(prop) {
|
|
2523
|
+
if (!isSchemaRecord(prop)) {
|
|
2524
|
+
return [];
|
|
2525
|
+
}
|
|
2526
|
+
const lines = [];
|
|
2527
|
+
const description = prop["description"];
|
|
2528
|
+
if (typeof description === "string" && description !== "") {
|
|
2529
|
+
lines.push(...escapeJsdoc(description).split("\n"));
|
|
2530
|
+
}
|
|
2531
|
+
const format = prop["format"];
|
|
2532
|
+
if (typeof format === "string" && format !== "") {
|
|
2533
|
+
lines.push(`@format ${escapeJsdoc(format)}`);
|
|
2534
|
+
}
|
|
2535
|
+
if ("default" in prop && !(typeof prop["default"] === "number" && !Number.isFinite(prop["default"]))) {
|
|
2536
|
+
const rendered = JSON.stringify(prop["default"]);
|
|
2537
|
+
if (rendered !== void 0) {
|
|
2538
|
+
lines.push(`@default ${escapeJsdoc(rendered)}`);
|
|
2539
|
+
}
|
|
2540
|
+
}
|
|
2541
|
+
if (prop["deprecated"] === true) {
|
|
2542
|
+
lines.push("@deprecated");
|
|
2543
|
+
}
|
|
2544
|
+
return lines;
|
|
2545
|
+
}
|
|
2546
|
+
function renderJsdoc(lines, indent) {
|
|
2547
|
+
if (lines.length === 1) {
|
|
2548
|
+
return `${indent}/** ${lines[0]} */
|
|
2549
|
+
`;
|
|
2550
|
+
}
|
|
2551
|
+
return `${indent}/**
|
|
2552
|
+
${lines.map((l) => `${indent} * ${l}`).join("\n")}
|
|
2553
|
+
${indent} */
|
|
2554
|
+
`;
|
|
2555
|
+
}
|
|
2556
|
+
function hasObjectShape(r) {
|
|
2557
|
+
return r["type"] === "object" || r["type"] === void 0 && (r["properties"] !== void 0 || r["additionalProperties"] !== void 0 || r["patternProperties"] !== void 0);
|
|
2558
|
+
}
|
|
2559
|
+
function typeExpr(schema, ctx, depth, indent) {
|
|
2560
|
+
if (schema === true) {
|
|
2561
|
+
return "unknown";
|
|
2562
|
+
}
|
|
2563
|
+
if (schema === false) {
|
|
2564
|
+
return "never";
|
|
2565
|
+
}
|
|
2566
|
+
if (!isSchemaRecord(schema)) {
|
|
2567
|
+
return "unknown";
|
|
2568
|
+
}
|
|
2569
|
+
if (ctx.stack.has(schema)) {
|
|
2570
|
+
return "unknown";
|
|
2571
|
+
}
|
|
2572
|
+
if (depth >= ctx.maxDepth) {
|
|
2573
|
+
return "unknown";
|
|
2574
|
+
}
|
|
2575
|
+
if (schema["$ref"] !== void 0) {
|
|
2576
|
+
return "unknown";
|
|
2577
|
+
}
|
|
2578
|
+
ctx.stack.add(schema);
|
|
2579
|
+
try {
|
|
2580
|
+
return typeExprInner(schema, ctx, depth, indent);
|
|
2581
|
+
} finally {
|
|
2582
|
+
ctx.stack.delete(schema);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
function typeExprInner(r, ctx, depth, indent) {
|
|
2586
|
+
if ("const" in r) {
|
|
2587
|
+
const rendered = literalOf(r["const"]);
|
|
2588
|
+
if (rendered !== "unknown") {
|
|
2589
|
+
return rendered;
|
|
2590
|
+
}
|
|
2591
|
+
}
|
|
2592
|
+
const enumMembers = r["enum"];
|
|
2593
|
+
if (Array.isArray(enumMembers)) {
|
|
2594
|
+
if (enumMembers.length === 0) {
|
|
2595
|
+
return "unknown";
|
|
2596
|
+
}
|
|
2597
|
+
return dedupe(enumMembers.map(literalOf)).join(" | ");
|
|
2598
|
+
}
|
|
2599
|
+
const anyOf = r["anyOf"];
|
|
2600
|
+
if (Array.isArray(anyOf) && anyOf.length === 2) {
|
|
2601
|
+
const nullIdx = anyOf.findIndex(isNullSchema);
|
|
2602
|
+
if (nullIdx >= 0 && !isNullSchema(anyOf[1 - nullIdx])) {
|
|
2603
|
+
return `${paren(typeExpr(anyOf[1 - nullIdx], ctx, depth + 1, indent))} | null`;
|
|
2604
|
+
}
|
|
2605
|
+
}
|
|
2606
|
+
const allOf = r["allOf"];
|
|
2607
|
+
if (Array.isArray(allOf)) {
|
|
2608
|
+
const parts = allOf.map((m) => paren(typeExpr(m, ctx, depth + 1, indent)));
|
|
2609
|
+
if (r["properties"] !== void 0) {
|
|
2610
|
+
parts.push(paren(objectExpr(r, ctx, depth, indent)));
|
|
2611
|
+
}
|
|
2612
|
+
return parts.length === 0 ? "unknown" : dedupe(parts).join(" & ");
|
|
2613
|
+
}
|
|
2614
|
+
const union = Array.isArray(r["oneOf"]) ? r["oneOf"] : Array.isArray(anyOf) ? anyOf : void 0;
|
|
2615
|
+
if (union) {
|
|
2616
|
+
if (union.length === 0) {
|
|
2617
|
+
return "unknown";
|
|
2618
|
+
}
|
|
2619
|
+
return dedupe(union.map((m) => typeExpr(m, ctx, depth + 1, indent))).join(" | ");
|
|
2620
|
+
}
|
|
2621
|
+
const type = r["type"];
|
|
2622
|
+
if (Array.isArray(type)) {
|
|
2623
|
+
const parts = type.filter((t) => typeof t === "string").map((t) => typeExpr({ ...r, type: t }, ctx, depth, indent));
|
|
2624
|
+
return parts.length === 0 ? "unknown" : dedupe(parts).join(" | ");
|
|
2625
|
+
}
|
|
2626
|
+
switch (type) {
|
|
2627
|
+
case "string":
|
|
2628
|
+
return "string";
|
|
2629
|
+
case "number":
|
|
2630
|
+
case "integer":
|
|
2631
|
+
return "number";
|
|
2632
|
+
case "boolean":
|
|
2633
|
+
return "boolean";
|
|
2634
|
+
case "null":
|
|
2635
|
+
return "null";
|
|
2636
|
+
case "array":
|
|
2637
|
+
return arrayExpr(r, ctx, depth, indent);
|
|
2638
|
+
default:
|
|
2639
|
+
if (hasObjectShape(r)) {
|
|
2640
|
+
return objectExpr(r, ctx, depth, indent);
|
|
2641
|
+
}
|
|
2642
|
+
return "unknown";
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
function arrayExpr(r, ctx, depth, indent) {
|
|
2646
|
+
const items = r["items"];
|
|
2647
|
+
const prefix = Array.isArray(r["prefixItems"]) ? r["prefixItems"] : Array.isArray(items) ? items : void 0;
|
|
2648
|
+
if (prefix) {
|
|
2649
|
+
const parts = prefix.map((m) => typeExpr(m, ctx, depth + 1, indent));
|
|
2650
|
+
let rest = "";
|
|
2651
|
+
if (Array.isArray(r["prefixItems"]) && items !== void 0 && !Array.isArray(items)) {
|
|
2652
|
+
rest = `, ...${paren(typeExpr(items, ctx, depth + 1, indent))}[]`;
|
|
2653
|
+
}
|
|
2654
|
+
return `[${parts.join(", ")}${rest}]`;
|
|
2655
|
+
}
|
|
2656
|
+
if (items === void 0) {
|
|
2657
|
+
return "unknown[]";
|
|
2658
|
+
}
|
|
2659
|
+
return `${paren(typeExpr(items, ctx, depth + 1, indent))}[]`;
|
|
2660
|
+
}
|
|
2661
|
+
function objectExpr(r, ctx, depth, indent) {
|
|
2662
|
+
const properties = isSchemaRecord(r["properties"]) ? r["properties"] : {};
|
|
2663
|
+
const entries = Object.entries(properties);
|
|
2664
|
+
const required = new Set(Array.isArray(r["required"]) ? r["required"] : []);
|
|
2665
|
+
const extraTypes = [];
|
|
2666
|
+
const ap = r["additionalProperties"];
|
|
2667
|
+
if (ap === true) {
|
|
2668
|
+
extraTypes.push("unknown");
|
|
2669
|
+
} else if (isSchemaRecord(ap)) {
|
|
2670
|
+
extraTypes.push(typeExpr(ap, ctx, depth + 1, indent));
|
|
2671
|
+
}
|
|
2672
|
+
const patternProps = r["patternProperties"];
|
|
2673
|
+
if (isSchemaRecord(patternProps)) {
|
|
2674
|
+
for (const value of Object.values(patternProps)) {
|
|
2675
|
+
extraTypes.push(typeExpr(value, ctx, depth + 1, indent));
|
|
2676
|
+
}
|
|
2677
|
+
}
|
|
2678
|
+
const extra = extraTypes.length > 0 ? dedupe(extraTypes).join(" | ") : void 0;
|
|
2679
|
+
if (entries.length === 0) {
|
|
2680
|
+
if (extra !== void 0) {
|
|
2681
|
+
return `Record<string, ${extra}>`;
|
|
2682
|
+
}
|
|
2683
|
+
return ap === false ? "Record<string, never>" : "Record<string, unknown>";
|
|
2684
|
+
}
|
|
2685
|
+
const suffix = extra !== void 0 ? ` & Record<string, ${extra}>` : "";
|
|
2686
|
+
if (ctx.mode === "compact") {
|
|
2687
|
+
const members = entries.map(
|
|
2688
|
+
([key, prop]) => `${quoteKey(key)}${required.has(key) ? "" : "?"}: ${typeExpr(prop, ctx, depth + 1, indent)}`
|
|
2689
|
+
);
|
|
2690
|
+
return `{ ${members.join("; ")} }${suffix}`;
|
|
2691
|
+
}
|
|
2692
|
+
const inner = indent + " ";
|
|
2693
|
+
let body = "{\n";
|
|
2694
|
+
for (const [key, prop] of entries) {
|
|
2695
|
+
const doc = jsdocLines(prop);
|
|
2696
|
+
if (doc.length > 0) {
|
|
2697
|
+
body += renderJsdoc(doc, inner);
|
|
2698
|
+
}
|
|
2699
|
+
body += `${inner}${quoteKey(key)}${required.has(key) ? "" : "?"}: ${typeExpr(prop, ctx, depth + 1, inner)};
|
|
2700
|
+
`;
|
|
2701
|
+
}
|
|
2702
|
+
body += `${indent}}`;
|
|
2703
|
+
return `${body}${suffix}`;
|
|
2704
|
+
}
|
|
2705
|
+
function isPlainObjectBody(schema) {
|
|
2706
|
+
if (!isSchemaRecord(schema) || schema["$ref"] !== void 0) {
|
|
2707
|
+
return false;
|
|
2708
|
+
}
|
|
2709
|
+
if ("const" in schema && literalOf(schema["const"]) !== "unknown" || Array.isArray(schema["enum"])) {
|
|
2710
|
+
return false;
|
|
2711
|
+
}
|
|
2712
|
+
if (Array.isArray(schema["allOf"]) || Array.isArray(schema["oneOf"]) || Array.isArray(schema["anyOf"])) {
|
|
2713
|
+
return false;
|
|
2714
|
+
}
|
|
2715
|
+
if (Array.isArray(schema["type"]) || !hasObjectShape(schema)) {
|
|
2716
|
+
return false;
|
|
2717
|
+
}
|
|
2718
|
+
const properties = isSchemaRecord(schema["properties"]) ? schema["properties"] : {};
|
|
2719
|
+
if (Object.keys(properties).length === 0) {
|
|
2720
|
+
return false;
|
|
2721
|
+
}
|
|
2722
|
+
const ap = schema["additionalProperties"];
|
|
2723
|
+
if (ap === true || isSchemaRecord(ap) || isSchemaRecord(schema["patternProperties"])) {
|
|
2724
|
+
return false;
|
|
2725
|
+
}
|
|
2726
|
+
return true;
|
|
2727
|
+
}
|
|
2728
|
+
function namedRoot(name, schema, ctx) {
|
|
2729
|
+
const expr = typeExpr(schema, ctx, 0, "");
|
|
2730
|
+
return isPlainObjectBody(schema) ? `interface ${name} ${expr}` : `type ${name} = ${expr};`;
|
|
2731
|
+
}
|
|
2732
|
+
function paramList(inputSchema, typeText) {
|
|
2733
|
+
if (inputSchema === true) {
|
|
2734
|
+
return `(input?: ${typeText})`;
|
|
2735
|
+
}
|
|
2736
|
+
if (!isSchemaRecord(inputSchema)) {
|
|
2737
|
+
return "()";
|
|
2738
|
+
}
|
|
2739
|
+
const properties = isSchemaRecord(inputSchema["properties"]) ? inputSchema["properties"] : {};
|
|
2740
|
+
const keys = Object.keys(properties);
|
|
2741
|
+
if (keys.length === 0) {
|
|
2742
|
+
const ap = inputSchema["additionalProperties"];
|
|
2743
|
+
const hasExtra = ap === true || isSchemaRecord(ap) || isSchemaRecord(inputSchema["patternProperties"]);
|
|
2744
|
+
const objectish = inputSchema["type"] === "object" || inputSchema["type"] === void 0;
|
|
2745
|
+
const composed = Array.isArray(inputSchema["allOf"]) || Array.isArray(inputSchema["oneOf"]) || Array.isArray(inputSchema["anyOf"]) || Array.isArray(inputSchema["enum"]) || "const" in inputSchema;
|
|
2746
|
+
return objectish && !hasExtra && !composed ? "()" : `(input: ${typeText})`;
|
|
2747
|
+
}
|
|
2748
|
+
const required = new Set(Array.isArray(inputSchema["required"]) ? inputSchema["required"] : []);
|
|
2749
|
+
const allOptional = keys.every((k) => !required.has(k));
|
|
2750
|
+
return allOptional ? `(input?: ${typeText})` : `(input: ${typeText})`;
|
|
2751
|
+
}
|
|
2752
|
+
function outputVariantsDeclaration(name, variants, ctx) {
|
|
2753
|
+
const lines = variants.map((member) => {
|
|
2754
|
+
let comment = "";
|
|
2755
|
+
if (isSchemaRecord(member)) {
|
|
2756
|
+
const status = member["x-status-code"];
|
|
2757
|
+
if (typeof status === "number" || typeof status === "string") {
|
|
2758
|
+
const contentType = member["x-content-type"];
|
|
2759
|
+
const ct = typeof contentType === "string" ? ` (${escapeJsdoc(contentType)})` : "";
|
|
2760
|
+
comment = `/** status ${escapeJsdoc(String(status))}${ct} */ `;
|
|
2761
|
+
}
|
|
2762
|
+
}
|
|
2763
|
+
return ` | ${comment}${typeExpr(member, ctx, 1, " ")}`;
|
|
2764
|
+
});
|
|
2765
|
+
return `type ${name} =
|
|
2766
|
+
${lines.join("\n")};`;
|
|
2767
|
+
}
|
|
2768
|
+
function emitToolTypeScript(toolName, description, inputSchema, outputSchema, options = {}) {
|
|
2769
|
+
const maxDepth = typeof options.maxDepth === "number" && Number.isFinite(options.maxDepth) ? Math.max(1, Math.floor(options.maxDepth)) : DEFAULT_MAX_DEPTH;
|
|
2770
|
+
const compact = { mode: "compact", maxDepth, stack: /* @__PURE__ */ new Set() };
|
|
2771
|
+
const pretty = { mode: "pretty", maxDepth, stack: /* @__PURE__ */ new Set() };
|
|
2772
|
+
const inputCompact = typeExpr(inputSchema, compact, 0, "");
|
|
2773
|
+
const outputCompact = outputSchema === void 0 ? "unknown" : typeExpr(outputSchema, compact, 0, "");
|
|
2774
|
+
const signature = `${paramList(inputSchema, inputCompact)} => Promise<${outputCompact}>`;
|
|
2775
|
+
const base = toPascalIdentifier(toolName);
|
|
2776
|
+
const inputName = `${base}Input`;
|
|
2777
|
+
const outputName = `${base}Output`;
|
|
2778
|
+
const blocks = [];
|
|
2779
|
+
if (typeof description === "string" && description !== "") {
|
|
2780
|
+
blocks.push(renderJsdoc(escapeJsdoc(description).split("\n"), "").trimEnd());
|
|
2781
|
+
}
|
|
2782
|
+
blocks.push(namedRoot(inputName, inputSchema, pretty));
|
|
2783
|
+
const outputUnion = isSchemaRecord(outputSchema) && Array.isArray(outputSchema["oneOf"]) ? outputSchema["oneOf"] : void 0;
|
|
2784
|
+
if (outputSchema === void 0) {
|
|
2785
|
+
blocks.push(`type ${outputName} = unknown;`);
|
|
2786
|
+
} else if (outputUnion && outputUnion.some((m) => isSchemaRecord(m) && m["x-status-code"] !== void 0)) {
|
|
2787
|
+
blocks.push(outputVariantsDeclaration(outputName, outputUnion, pretty));
|
|
2788
|
+
} else {
|
|
2789
|
+
blocks.push(namedRoot(outputName, outputSchema, pretty));
|
|
2790
|
+
}
|
|
2791
|
+
let fnName = lowerFirst(base);
|
|
2792
|
+
if (RESERVED_WORDS.has(fnName)) {
|
|
2793
|
+
fnName = `${fnName}_`;
|
|
2794
|
+
}
|
|
2795
|
+
blocks.push(`declare function ${fnName}${paramList(inputSchema, inputName)}: Promise<${outputName}>;`);
|
|
2796
|
+
return { signature, declaration: blocks.join("\n\n") };
|
|
2797
|
+
}
|
|
2798
|
+
|
|
1723
2799
|
// src/ssrf.ts
|
|
1724
2800
|
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set([
|
|
1725
2801
|
"localhost",
|
|
@@ -2009,6 +3085,103 @@ function applySecureDefaults(options) {
|
|
|
2009
3085
|
}
|
|
2010
3086
|
};
|
|
2011
3087
|
}
|
|
3088
|
+
function hasUnboundedArray(node, seen = /* @__PURE__ */ new Set()) {
|
|
3089
|
+
if (node === null || typeof node !== "object" || seen.has(node)) return false;
|
|
3090
|
+
seen.add(node);
|
|
3091
|
+
const record = node;
|
|
3092
|
+
const type = record["type"];
|
|
3093
|
+
const isArray = type === "array" || Array.isArray(type) && type.includes("array");
|
|
3094
|
+
if (isArray && record["maxItems"] === void 0) return true;
|
|
3095
|
+
const children = [];
|
|
3096
|
+
const properties = record["properties"];
|
|
3097
|
+
if (properties && typeof properties === "object") children.push(...Object.values(properties));
|
|
3098
|
+
for (const key of ["items", "additionalProperties", "contentSchema"]) {
|
|
3099
|
+
const value = record[key];
|
|
3100
|
+
if (Array.isArray(value)) children.push(...value);
|
|
3101
|
+
else if (value && typeof value === "object") children.push(value);
|
|
3102
|
+
}
|
|
3103
|
+
for (const key of ["allOf", "anyOf", "oneOf", "prefixItems"]) {
|
|
3104
|
+
if (Array.isArray(record[key])) children.push(...record[key]);
|
|
3105
|
+
}
|
|
3106
|
+
return children.some((child) => hasUnboundedArray(child, seen));
|
|
3107
|
+
}
|
|
3108
|
+
function detectResponseHints(outputSchema, mapper) {
|
|
3109
|
+
const paginationParams = [
|
|
3110
|
+
...new Set(mapper.filter((m) => m.type === "query" && !m.security && PAGINATION_PARAM.test(m.key)).map((m) => m.key))
|
|
3111
|
+
];
|
|
3112
|
+
const unboundedArray = outputSchema !== void 0 && hasUnboundedArray(outputSchema);
|
|
3113
|
+
if (!unboundedArray && paginationParams.length === 0) return void 0;
|
|
3114
|
+
return {
|
|
3115
|
+
...unboundedArray && { unboundedArray: true },
|
|
3116
|
+
...paginationParams.length > 0 && { paginationParams },
|
|
3117
|
+
...unboundedArray && paginationParams.length === 0 && { largeResponseRisk: true }
|
|
3118
|
+
};
|
|
3119
|
+
}
|
|
3120
|
+
function composeDescription(operation, method, pathStr, strategy) {
|
|
3121
|
+
const fallback = `${method.toUpperCase()} ${pathStr}`;
|
|
3122
|
+
const summary = operation.summary?.trim();
|
|
3123
|
+
const description = operation.description?.trim();
|
|
3124
|
+
switch (strategy) {
|
|
3125
|
+
case "descriptionOnly":
|
|
3126
|
+
return description || summary || fallback;
|
|
3127
|
+
case "combined":
|
|
3128
|
+
if (summary && description && summary !== description) {
|
|
3129
|
+
return `${summary}
|
|
3130
|
+
|
|
3131
|
+
${description}`;
|
|
3132
|
+
}
|
|
3133
|
+
return summary || description || fallback;
|
|
3134
|
+
case "full": {
|
|
3135
|
+
const parts = [];
|
|
3136
|
+
if (summary) parts.push(summary);
|
|
3137
|
+
if (description && description !== summary) parts.push(description);
|
|
3138
|
+
if (operation.operationId) parts.push(`Operation: ${operation.operationId}`);
|
|
3139
|
+
parts.push(fallback);
|
|
3140
|
+
return parts.join("\n\n");
|
|
3141
|
+
}
|
|
3142
|
+
default:
|
|
3143
|
+
return summary || description || fallback;
|
|
3144
|
+
}
|
|
3145
|
+
}
|
|
3146
|
+
function propertyNames(schema, cap = 8) {
|
|
3147
|
+
const properties = schema["properties"];
|
|
3148
|
+
if (!properties || typeof properties !== "object") return "";
|
|
3149
|
+
const names = Object.keys(properties);
|
|
3150
|
+
const listed = names.slice(0, cap).join(", ");
|
|
3151
|
+
return names.length > cap ? `${listed}, \u2026` : listed;
|
|
3152
|
+
}
|
|
3153
|
+
function summarizeOutputSchema(schema) {
|
|
3154
|
+
const record = schema;
|
|
3155
|
+
const variants = record["oneOf"];
|
|
3156
|
+
if (Array.isArray(variants) && variants.length > 0) {
|
|
3157
|
+
const first = variants[0];
|
|
3158
|
+
const firstSummary = first && typeof first === "object" ? summarizeOutputSchema(first) : void 0;
|
|
3159
|
+
return firstSummary ? `${firstSummary} (${variants.length} response variants)` : void 0;
|
|
3160
|
+
}
|
|
3161
|
+
const type = record["type"];
|
|
3162
|
+
if (type === "object" || type === void 0 && record["properties"]) {
|
|
3163
|
+
const names = propertyNames(record);
|
|
3164
|
+
return names ? `object with fields: ${names}` : "object";
|
|
3165
|
+
}
|
|
3166
|
+
if (type === "array") {
|
|
3167
|
+
const items = record["items"];
|
|
3168
|
+
if (items && typeof items === "object" && !Array.isArray(items)) {
|
|
3169
|
+
const itemRecord = items;
|
|
3170
|
+
if (itemRecord["type"] === "object" || itemRecord["properties"]) {
|
|
3171
|
+
const names = propertyNames(itemRecord);
|
|
3172
|
+
return names ? `array of objects with fields: ${names}` : "array of objects";
|
|
3173
|
+
}
|
|
3174
|
+
if (typeof itemRecord["type"] === "string") {
|
|
3175
|
+
return `array of ${itemRecord["type"]}`;
|
|
3176
|
+
}
|
|
3177
|
+
}
|
|
3178
|
+
return "array";
|
|
3179
|
+
}
|
|
3180
|
+
if (typeof type === "string" && type !== "null") {
|
|
3181
|
+
return type;
|
|
3182
|
+
}
|
|
3183
|
+
return void 0;
|
|
3184
|
+
}
|
|
2012
3185
|
function globToRegExp(glob) {
|
|
2013
3186
|
let pattern = "^";
|
|
2014
3187
|
for (let i = 0; i < glob.length; i++) {
|
|
@@ -2031,6 +3204,32 @@ function globToRegExp(glob) {
|
|
|
2031
3204
|
function matchesAnyGlob(path, globs) {
|
|
2032
3205
|
return globs.some((glob) => globToRegExp(glob).test(path));
|
|
2033
3206
|
}
|
|
3207
|
+
function iconsFromInfoLogo(info) {
|
|
3208
|
+
if (!info || typeof info !== "object") {
|
|
3209
|
+
return void 0;
|
|
3210
|
+
}
|
|
3211
|
+
const logo = info["x-logo"];
|
|
3212
|
+
let src;
|
|
3213
|
+
if (typeof logo === "string") {
|
|
3214
|
+
src = logo;
|
|
3215
|
+
} else if (logo && typeof logo === "object" && !Array.isArray(logo)) {
|
|
3216
|
+
const url = logo["url"];
|
|
3217
|
+
if (typeof url === "string") {
|
|
3218
|
+
src = url;
|
|
3219
|
+
}
|
|
3220
|
+
}
|
|
3221
|
+
if (src !== void 0 && isAllowedIconSrc(src)) {
|
|
3222
|
+
return [{ src }];
|
|
3223
|
+
}
|
|
3224
|
+
return void 0;
|
|
3225
|
+
}
|
|
3226
|
+
function trimUnderscores(value) {
|
|
3227
|
+
let start = 0;
|
|
3228
|
+
let end = value.length;
|
|
3229
|
+
while (start < end && value[start] === "_") start++;
|
|
3230
|
+
while (end > start && value[end - 1] === "_") end--;
|
|
3231
|
+
return value.slice(start, end);
|
|
3232
|
+
}
|
|
2034
3233
|
function fnv1aHex(input) {
|
|
2035
3234
|
let hash = 2166136261;
|
|
2036
3235
|
for (let i = 0; i < input.length; i++) {
|
|
@@ -2041,7 +3240,7 @@ function fnv1aHex(input) {
|
|
|
2041
3240
|
}
|
|
2042
3241
|
function normalizeToolName(raw, maxLength, fallbackSeed) {
|
|
2043
3242
|
let hashSeed = raw;
|
|
2044
|
-
let name = raw.replace(/[^A-Za-z0-9_.-]/g, "_").replace(/_+/g, "_")
|
|
3243
|
+
let name = trimUnderscores(raw.replace(/[^A-Za-z0-9_.-]/g, "_").replace(/_+/g, "_"));
|
|
2045
3244
|
if (name.length === 0) {
|
|
2046
3245
|
hashSeed = fallbackSeed;
|
|
2047
3246
|
name = `tool_${fnv1aHex(fallbackSeed)}`;
|
|
@@ -2074,8 +3273,15 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2074
3273
|
validate: options.validate ?? true,
|
|
2075
3274
|
followRedirects: options.followRedirects ?? true,
|
|
2076
3275
|
refResolution: options.refResolution ?? {},
|
|
2077
|
-
secureDefaults: options.secureDefaults ?? false
|
|
3276
|
+
secureDefaults: options.secureDefaults ?? false,
|
|
3277
|
+
overlays: options.overlays
|
|
2078
3278
|
};
|
|
3279
|
+
if (this.options.overlays) {
|
|
3280
|
+
const overlays = Array.isArray(this.options.overlays) ? this.options.overlays : [this.options.overlays];
|
|
3281
|
+
for (const overlay of overlays) {
|
|
3282
|
+
this.document = applyOverlay(this.document, overlay);
|
|
3283
|
+
}
|
|
3284
|
+
}
|
|
2079
3285
|
}
|
|
2080
3286
|
/**
|
|
2081
3287
|
* Create generator from a URL
|
|
@@ -2105,7 +3311,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2105
3311
|
}
|
|
2106
3312
|
return new _OpenAPIToolGenerator(document, options);
|
|
2107
3313
|
} catch (error) {
|
|
2108
|
-
if (error instanceof LoadError) {
|
|
3314
|
+
if (error instanceof LoadError || error instanceof OverlayError) {
|
|
2109
3315
|
throw error;
|
|
2110
3316
|
}
|
|
2111
3317
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
@@ -2138,6 +3344,9 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2138
3344
|
}
|
|
2139
3345
|
return new _OpenAPIToolGenerator(document, options);
|
|
2140
3346
|
} catch (error) {
|
|
3347
|
+
if (error instanceof OverlayError) {
|
|
3348
|
+
throw error;
|
|
3349
|
+
}
|
|
2141
3350
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2142
3351
|
throw new LoadError(`Failed to load OpenAPI spec from file: ${errorMessage}`, {
|
|
2143
3352
|
filePath,
|
|
@@ -2153,6 +3362,9 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2153
3362
|
const document = yaml.parse(yamlString);
|
|
2154
3363
|
return new _OpenAPIToolGenerator(document, options);
|
|
2155
3364
|
} catch (error) {
|
|
3365
|
+
if (error instanceof OverlayError) {
|
|
3366
|
+
throw error;
|
|
3367
|
+
}
|
|
2156
3368
|
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
2157
3369
|
throw new ParseError(`Failed to parse YAML: ${errorMessage}`, {
|
|
2158
3370
|
originalError: error
|
|
@@ -2179,6 +3391,16 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2179
3391
|
const validator = new Validator();
|
|
2180
3392
|
return validator.validate(this.document);
|
|
2181
3393
|
}
|
|
3394
|
+
/**
|
|
3395
|
+
* Lint the loaded document for agent-readiness (missing operationIds,
|
|
3396
|
+
* vague descriptions, unpaginated lists, oversized schemas, ...). Runs
|
|
3397
|
+
* after overlays and dereferencing so findings reflect what tools would
|
|
3398
|
+
* actually be generated from.
|
|
3399
|
+
*/
|
|
3400
|
+
async lint() {
|
|
3401
|
+
await this.initialize(false);
|
|
3402
|
+
return lintDocument(this.getDocument());
|
|
3403
|
+
}
|
|
2182
3404
|
// NOTE: internal/private-address blocking + IPv4-mapped-IPv6 decoding now live
|
|
2183
3405
|
// in `ssrf.ts` (`isBlockedHostname` / `isBlockedAddress` / `decodeIpv4MappedIpv6`),
|
|
2184
3406
|
// shared by the spec-URL fetch (`fromURL`) and the `$ref` resolver below, and
|
|
@@ -2316,7 +3538,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2316
3538
|
/**
|
|
2317
3539
|
* Initialize the generator (dereference if needed, then validate)
|
|
2318
3540
|
*/
|
|
2319
|
-
async initialize() {
|
|
3541
|
+
async initialize(runValidation = this.options.validate) {
|
|
2320
3542
|
if (this.options.dereference && !this.dereferencedDocument) {
|
|
2321
3543
|
const cloned = JSON.parse(JSON.stringify(this.document));
|
|
2322
3544
|
if (!_OpenAPIToolGenerator.hasExternalRefs(cloned)) {
|
|
@@ -2334,7 +3556,7 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2334
3556
|
}
|
|
2335
3557
|
}
|
|
2336
3558
|
}
|
|
2337
|
-
if (
|
|
3559
|
+
if (runValidation) {
|
|
2338
3560
|
const validator = new Validator();
|
|
2339
3561
|
const documentToValidate = this.dereferencedDocument ?? this.document;
|
|
2340
3562
|
const result = await validator.validate(documentToValidate);
|
|
@@ -2383,6 +3605,14 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2383
3605
|
attempts++;
|
|
2384
3606
|
}
|
|
2385
3607
|
tool = { ...tool, name: deduped };
|
|
3608
|
+
if (tool.metadata.typescript) {
|
|
3609
|
+
tool.metadata = {
|
|
3610
|
+
...tool.metadata,
|
|
3611
|
+
typescript: emitToolTypeScript(deduped, tool.description, tool.inputSchema, tool.outputSchema, {
|
|
3612
|
+
maxDepth: Math.max(1, options.maxSchemaDepth ?? 10)
|
|
3613
|
+
})
|
|
3614
|
+
};
|
|
3615
|
+
}
|
|
2386
3616
|
}
|
|
2387
3617
|
usedNames.add(tool.name);
|
|
2388
3618
|
tools.push(tool);
|
|
@@ -2431,8 +3661,14 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2431
3661
|
const responseBuilder = new ResponseBuilder(options);
|
|
2432
3662
|
const outputSchema = responseBuilder.build(operation.responses);
|
|
2433
3663
|
const overrides = extractExtensionOverrides(operation);
|
|
2434
|
-
const name = this.generateToolName(
|
|
2435
|
-
|
|
3664
|
+
const name = this.generateToolName(
|
|
3665
|
+
pathStr,
|
|
3666
|
+
method,
|
|
3667
|
+
overrides.name ?? operation.operationId,
|
|
3668
|
+
options,
|
|
3669
|
+
operation
|
|
3670
|
+
);
|
|
3671
|
+
const description = overrides.description ?? composeDescription(operation, method, pathStr, options.descriptionStrategy ?? "summaryOnly");
|
|
2436
3672
|
const title = overrides.title ?? operation.summary;
|
|
2437
3673
|
const inferred = options.inferAnnotations !== false ? inferAnnotationsFromMethod(method.toLowerCase()) : void 0;
|
|
2438
3674
|
const annotations = inferred || overrides.annotations ? { ...inferred, ...overrides.annotations } : void 0;
|
|
@@ -2449,17 +3685,95 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2449
3685
|
if (resolvedOutputSchema) {
|
|
2450
3686
|
resolvedOutputSchema = SchemaBuilder.truncateDepth(resolvedOutputSchema, maxSchemaDepth);
|
|
2451
3687
|
}
|
|
3688
|
+
const applyTrim = (schema, isInputRoot) => {
|
|
3689
|
+
let trimmed = schema;
|
|
3690
|
+
if (options.stripExamples) trimmed = SchemaBuilder.stripExamples(trimmed);
|
|
3691
|
+
if (options.maxDescriptionLength !== void 0) {
|
|
3692
|
+
trimmed = SchemaBuilder.capDescriptions(trimmed, options.maxDescriptionLength);
|
|
3693
|
+
}
|
|
3694
|
+
if (options.maxProperties !== void 0) {
|
|
3695
|
+
if (isInputRoot) {
|
|
3696
|
+
const properties = trimmed.properties;
|
|
3697
|
+
if (properties && typeof properties === "object") {
|
|
3698
|
+
const limited = {};
|
|
3699
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
3700
|
+
limited[key] = SchemaBuilder.limitProperties(value, options.maxProperties);
|
|
3701
|
+
}
|
|
3702
|
+
trimmed = { ...trimmed, properties: limited };
|
|
3703
|
+
}
|
|
3704
|
+
} else {
|
|
3705
|
+
trimmed = SchemaBuilder.limitProperties(trimmed, options.maxProperties);
|
|
3706
|
+
}
|
|
3707
|
+
}
|
|
3708
|
+
return trimmed;
|
|
3709
|
+
};
|
|
3710
|
+
if (options.stripExamples || options.maxProperties !== void 0 || options.maxDescriptionLength !== void 0) {
|
|
3711
|
+
resolvedInputSchema = applyTrim(resolvedInputSchema, true);
|
|
3712
|
+
if (resolvedOutputSchema) {
|
|
3713
|
+
resolvedOutputSchema = applyTrim(resolvedOutputSchema, false);
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
2452
3716
|
if (options.target) {
|
|
2453
3717
|
resolvedInputSchema = applyClientTarget(resolvedInputSchema, options.target);
|
|
2454
3718
|
if (resolvedOutputSchema) {
|
|
2455
3719
|
resolvedOutputSchema = applyClientTarget(resolvedOutputSchema, options.target);
|
|
2456
3720
|
}
|
|
2457
3721
|
}
|
|
3722
|
+
const responseHints = detectResponseHints(resolvedOutputSchema, mapper);
|
|
3723
|
+
if (responseHints) {
|
|
3724
|
+
metadata.responseHints = responseHints;
|
|
3725
|
+
}
|
|
3726
|
+
let finalDescription = description;
|
|
3727
|
+
if (options.appendResponseSummary && resolvedOutputSchema) {
|
|
3728
|
+
const summary = summarizeOutputSchema(resolvedOutputSchema);
|
|
3729
|
+
if (summary) {
|
|
3730
|
+
finalDescription = `${finalDescription}
|
|
3731
|
+
|
|
3732
|
+
Returns: ${summary}`;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
if (options.emitTypeSignatures) {
|
|
3736
|
+
metadata.typescript = emitToolTypeScript(name, finalDescription, resolvedInputSchema, resolvedOutputSchema, {
|
|
3737
|
+
// Print at least as deep as the schemas were truncated, so the
|
|
3738
|
+
// emitted types never collapse levels the schema still carries.
|
|
3739
|
+
maxDepth: Math.max(1, options.maxSchemaDepth ?? 10)
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
let toolMeta;
|
|
3743
|
+
if (overrides.meta) {
|
|
3744
|
+
toolMeta = {};
|
|
3745
|
+
for (const [key, value] of Object.entries(overrides.meta)) {
|
|
3746
|
+
if (!key.startsWith("dev.agentfront.openapi/")) {
|
|
3747
|
+
toolMeta[key] = value;
|
|
3748
|
+
}
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
if (options.emitMeta) {
|
|
3752
|
+
const info = document.info;
|
|
3753
|
+
toolMeta = {
|
|
3754
|
+
...toolMeta,
|
|
3755
|
+
"dev.agentfront.openapi/operation": {
|
|
3756
|
+
path: pathStr,
|
|
3757
|
+
method,
|
|
3758
|
+
...operation.operationId !== void 0 && { operationId: operation.operationId },
|
|
3759
|
+
...operation.tags && { tags: [...operation.tags] },
|
|
3760
|
+
...operation.deprecated !== void 0 && { deprecated: operation.deprecated },
|
|
3761
|
+
...typeof info?.["title"] === "string" && { specTitle: info["title"] },
|
|
3762
|
+
...typeof info?.["version"] === "string" && { specVersion: info["version"] }
|
|
3763
|
+
}
|
|
3764
|
+
};
|
|
3765
|
+
}
|
|
3766
|
+
if (toolMeta && Object.keys(toolMeta).length === 0) {
|
|
3767
|
+
toolMeta = void 0;
|
|
3768
|
+
}
|
|
3769
|
+
const icons = overrides.icons ?? (options.inheritDocumentIcons ? iconsFromInfoLogo(document.info) : void 0);
|
|
2458
3770
|
return {
|
|
2459
3771
|
name,
|
|
2460
3772
|
...title !== void 0 && { title },
|
|
2461
|
-
description,
|
|
3773
|
+
description: finalDescription,
|
|
2462
3774
|
...annotations && { annotations },
|
|
3775
|
+
...toolMeta && { _meta: toolMeta },
|
|
3776
|
+
...icons && { icons },
|
|
2463
3777
|
inputSchema: resolvedInputSchema,
|
|
2464
3778
|
outputSchema: resolvedOutputSchema,
|
|
2465
3779
|
mapper,
|
|
@@ -2527,14 +3841,16 @@ var OpenAPIToolGenerator = class _OpenAPIToolGenerator {
|
|
|
2527
3841
|
/**
|
|
2528
3842
|
* Generate a tool name
|
|
2529
3843
|
*/
|
|
2530
|
-
generateToolName(path, method, operationId, options = {}) {
|
|
3844
|
+
generateToolName(path, method, operationId, options = {}, operation) {
|
|
2531
3845
|
let rawName;
|
|
2532
3846
|
if (options.namingStrategy?.toolNameGenerator) {
|
|
2533
|
-
rawName = options.namingStrategy.toolNameGenerator(path, method, operationId);
|
|
3847
|
+
rawName = options.namingStrategy.toolNameGenerator(path, method, operationId, operation);
|
|
2534
3848
|
} else if (operationId) {
|
|
2535
3849
|
rawName = operationId;
|
|
2536
3850
|
} else {
|
|
2537
|
-
const sanitized =
|
|
3851
|
+
const sanitized = trimUnderscores(
|
|
3852
|
+
path.replace(/\{([^{}]+)\}/g, "By_$1").replace(/[^a-zA-Z0-9_]/g, "_").replace(/_+/g, "_")
|
|
3853
|
+
);
|
|
2538
3854
|
rawName = `${method}_${sanitized}`;
|
|
2539
3855
|
}
|
|
2540
3856
|
return normalizeToolName(
|
|
@@ -2749,7 +4065,7 @@ var SecurityResolver = class {
|
|
|
2749
4065
|
resolveDigestAuth(context) {
|
|
2750
4066
|
const digest = context.digest;
|
|
2751
4067
|
if (!digest) return void 0;
|
|
2752
|
-
const quoted = (v) => String(v).replace(/[\r\n]/g, "").replace(/"/g, '\\"');
|
|
4068
|
+
const quoted = (v) => String(v).replace(/[\r\n]/g, "").replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
2753
4069
|
const token = (v) => String(v).replace(/[\r\n",]/g, "");
|
|
2754
4070
|
const parts = [
|
|
2755
4071
|
`username="${quoted(digest.username)}"`,
|
|
@@ -2866,6 +4182,1213 @@ function createSecurityContext(auth) {
|
|
|
2866
4182
|
};
|
|
2867
4183
|
}
|
|
2868
4184
|
|
|
4185
|
+
// src/naming-presets.ts
|
|
4186
|
+
var CODECALL_RESERVED_NAMESPACES = [
|
|
4187
|
+
"console",
|
|
4188
|
+
"Math",
|
|
4189
|
+
"JSON",
|
|
4190
|
+
"Object",
|
|
4191
|
+
"Promise",
|
|
4192
|
+
"Array",
|
|
4193
|
+
"String",
|
|
4194
|
+
"Number",
|
|
4195
|
+
"Boolean",
|
|
4196
|
+
"Date",
|
|
4197
|
+
"RegExp",
|
|
4198
|
+
"Error",
|
|
4199
|
+
"Symbol",
|
|
4200
|
+
"Map",
|
|
4201
|
+
"Set",
|
|
4202
|
+
"WeakMap",
|
|
4203
|
+
"WeakSet",
|
|
4204
|
+
"globalThis",
|
|
4205
|
+
"global",
|
|
4206
|
+
"window",
|
|
4207
|
+
"self",
|
|
4208
|
+
"undefined",
|
|
4209
|
+
"null",
|
|
4210
|
+
"true",
|
|
4211
|
+
"false",
|
|
4212
|
+
"NaN",
|
|
4213
|
+
"Infinity",
|
|
4214
|
+
"callTool",
|
|
4215
|
+
"getTool",
|
|
4216
|
+
"mcpLog",
|
|
4217
|
+
"mcpNotify"
|
|
4218
|
+
];
|
|
4219
|
+
function sanitizeIdentifier(value) {
|
|
4220
|
+
if (value === void 0) {
|
|
4221
|
+
return "";
|
|
4222
|
+
}
|
|
4223
|
+
let out = value.replace(/[^A-Za-z0-9_]+/g, "_").replace(/_+/g, "_");
|
|
4224
|
+
let start = 0;
|
|
4225
|
+
let end = out.length;
|
|
4226
|
+
while (start < end && out[start] === "_") start++;
|
|
4227
|
+
while (end > start && out[end - 1] === "_") end--;
|
|
4228
|
+
out = out.slice(start, end);
|
|
4229
|
+
if (out === "") {
|
|
4230
|
+
return "";
|
|
4231
|
+
}
|
|
4232
|
+
return /^[0-9]/.test(out) ? `_${out}` : out;
|
|
4233
|
+
}
|
|
4234
|
+
function firstPathSegment(path) {
|
|
4235
|
+
for (const segment of path.split("/")) {
|
|
4236
|
+
if (segment !== "" && !segment.startsWith("{")) {
|
|
4237
|
+
return sanitizeIdentifier(segment);
|
|
4238
|
+
}
|
|
4239
|
+
}
|
|
4240
|
+
return "";
|
|
4241
|
+
}
|
|
4242
|
+
function pathMethodHalf(method, path, ns) {
|
|
4243
|
+
const segments = path.split("/").filter((s) => s !== "").map((s) => {
|
|
4244
|
+
const templated = s.replace(/\{([^{}]+)\}/g, "by_$1");
|
|
4245
|
+
return sanitizeIdentifier(templated);
|
|
4246
|
+
}).filter((s) => s !== "");
|
|
4247
|
+
if (segments.length > 0 && segments[0] === ns) {
|
|
4248
|
+
segments.shift();
|
|
4249
|
+
}
|
|
4250
|
+
const joined = segments.join("_");
|
|
4251
|
+
return joined === "" ? method : `${method}_${joined}`;
|
|
4252
|
+
}
|
|
4253
|
+
function dottedNaming(options = {}) {
|
|
4254
|
+
const namespaceFrom = options.namespaceFrom ?? "tag";
|
|
4255
|
+
const reserved = /* @__PURE__ */ new Set([...CODECALL_RESERVED_NAMESPACES, ...options.reservedNamespaces ?? []]);
|
|
4256
|
+
return {
|
|
4257
|
+
toolNameGenerator: (path, method, operationId, operation) => {
|
|
4258
|
+
let ns = "";
|
|
4259
|
+
if (namespaceFrom === "tag") {
|
|
4260
|
+
ns = sanitizeIdentifier(operation?.tags?.[0]);
|
|
4261
|
+
}
|
|
4262
|
+
if (ns === "") {
|
|
4263
|
+
ns = firstPathSegment(path);
|
|
4264
|
+
}
|
|
4265
|
+
if (ns === "") {
|
|
4266
|
+
ns = "api";
|
|
4267
|
+
}
|
|
4268
|
+
if (ns.startsWith("_")) {
|
|
4269
|
+
ns = `n${ns.slice(1)}`;
|
|
4270
|
+
}
|
|
4271
|
+
if (reserved.has(ns)) {
|
|
4272
|
+
ns = `${ns}_`;
|
|
4273
|
+
}
|
|
4274
|
+
const methodHalf = sanitizeIdentifier(operationId) || pathMethodHalf(method, path, ns);
|
|
4275
|
+
return `${ns}.${methodHalf}`;
|
|
4276
|
+
}
|
|
4277
|
+
};
|
|
4278
|
+
}
|
|
4279
|
+
|
|
4280
|
+
// src/elicitation.ts
|
|
4281
|
+
function buildElicitation(source) {
|
|
4282
|
+
const { scheme, type } = source;
|
|
4283
|
+
if (type === "http") {
|
|
4284
|
+
const httpScheme = (source.httpScheme ?? "bearer").toLowerCase();
|
|
4285
|
+
if (httpScheme === "basic" || httpScheme === "digest") {
|
|
4286
|
+
return {
|
|
4287
|
+
scheme,
|
|
4288
|
+
message: `Provide HTTP ${httpScheme} credentials for "${scheme}".`,
|
|
4289
|
+
requestedSchema: {
|
|
4290
|
+
type: "object",
|
|
4291
|
+
properties: {
|
|
4292
|
+
username: { type: "string", title: "Username" },
|
|
4293
|
+
password: { type: "string", title: "Password", description: "Handled as a secret \u2014 never logged." }
|
|
4294
|
+
},
|
|
4295
|
+
required: ["username", "password"]
|
|
4296
|
+
}
|
|
4297
|
+
};
|
|
4298
|
+
}
|
|
4299
|
+
const format = source.bearerFormat ? ` (${source.bearerFormat})` : "";
|
|
4300
|
+
return {
|
|
4301
|
+
scheme,
|
|
4302
|
+
message: `Provide the ${httpScheme} token for "${scheme}".`,
|
|
4303
|
+
requestedSchema: {
|
|
4304
|
+
type: "object",
|
|
4305
|
+
properties: {
|
|
4306
|
+
token: { type: "string", title: "Token", description: `HTTP ${httpScheme} authentication token${format}.` }
|
|
4307
|
+
},
|
|
4308
|
+
required: ["token"]
|
|
4309
|
+
}
|
|
4310
|
+
};
|
|
4311
|
+
}
|
|
4312
|
+
if (type === "apiKey") {
|
|
4313
|
+
const keyName = source.apiKeyName ?? scheme;
|
|
4314
|
+
const location = source.apiKeyIn ?? "header";
|
|
4315
|
+
return {
|
|
4316
|
+
scheme,
|
|
4317
|
+
message: `Provide the API key for "${scheme}".`,
|
|
4318
|
+
requestedSchema: {
|
|
4319
|
+
type: "object",
|
|
4320
|
+
properties: {
|
|
4321
|
+
apiKey: { type: "string", title: "API key", description: `API key "${keyName}" sent via ${location}.` }
|
|
4322
|
+
},
|
|
4323
|
+
required: ["apiKey"]
|
|
4324
|
+
}
|
|
4325
|
+
};
|
|
4326
|
+
}
|
|
4327
|
+
if (type === "oauth2" || type === "openIdConnect") {
|
|
4328
|
+
const scopes = source.scopes && source.scopes.length > 0 ? ` Scopes: ${source.scopes.join(", ")}.` : "";
|
|
4329
|
+
return {
|
|
4330
|
+
scheme,
|
|
4331
|
+
message: `Provide an OAuth2 access token for "${scheme}".${scopes}`,
|
|
4332
|
+
requestedSchema: {
|
|
4333
|
+
type: "object",
|
|
4334
|
+
properties: {
|
|
4335
|
+
accessToken: { type: "string", title: "Access token", description: `OAuth2 access token.${scopes}` }
|
|
4336
|
+
},
|
|
4337
|
+
required: ["accessToken"]
|
|
4338
|
+
}
|
|
4339
|
+
};
|
|
4340
|
+
}
|
|
4341
|
+
return void 0;
|
|
4342
|
+
}
|
|
4343
|
+
function deriveSecurityElicitations(tool) {
|
|
4344
|
+
const sources = [];
|
|
4345
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4346
|
+
for (const entry of tool.mapper) {
|
|
4347
|
+
const security = entry.security;
|
|
4348
|
+
if (security && !seen.has(security.scheme)) {
|
|
4349
|
+
seen.add(security.scheme);
|
|
4350
|
+
sources.push(security);
|
|
4351
|
+
}
|
|
4352
|
+
}
|
|
4353
|
+
if (sources.length === 0 && tool.metadata.security) {
|
|
4354
|
+
for (const requirement of tool.metadata.security) {
|
|
4355
|
+
if (!seen.has(requirement.scheme)) {
|
|
4356
|
+
seen.add(requirement.scheme);
|
|
4357
|
+
sources.push({
|
|
4358
|
+
scheme: requirement.scheme,
|
|
4359
|
+
type: requirement.type,
|
|
4360
|
+
httpScheme: requirement.httpScheme,
|
|
4361
|
+
bearerFormat: requirement.bearerFormat,
|
|
4362
|
+
scopes: requirement.scopes,
|
|
4363
|
+
apiKeyName: requirement.name,
|
|
4364
|
+
apiKeyIn: requirement.in
|
|
4365
|
+
});
|
|
4366
|
+
}
|
|
4367
|
+
}
|
|
4368
|
+
}
|
|
4369
|
+
const result = [];
|
|
4370
|
+
for (const source of sources) {
|
|
4371
|
+
const elicitation = buildElicitation(source);
|
|
4372
|
+
if (elicitation) {
|
|
4373
|
+
result.push(elicitation);
|
|
4374
|
+
}
|
|
4375
|
+
}
|
|
4376
|
+
return result;
|
|
4377
|
+
}
|
|
4378
|
+
|
|
4379
|
+
// src/arazzo-expressions.ts
|
|
4380
|
+
var EXACT_ROOTS = {
|
|
4381
|
+
$url: "url",
|
|
4382
|
+
$method: "method",
|
|
4383
|
+
$statusCode: "statusCode"
|
|
4384
|
+
};
|
|
4385
|
+
var DOTTED_ROOTS = {
|
|
4386
|
+
$inputs: "inputs",
|
|
4387
|
+
$outputs: "outputs",
|
|
4388
|
+
$steps: "steps",
|
|
4389
|
+
$workflows: "workflows",
|
|
4390
|
+
$sourceDescriptions: "sourceDescriptions",
|
|
4391
|
+
$components: "components"
|
|
4392
|
+
};
|
|
4393
|
+
var KNOWN_ROOT = /^\$(?:(?:url|method|statusCode)$|(?:request|response|message)\.|(?:inputs|outputs|steps|workflows|sourceDescriptions|components)\.)/;
|
|
4394
|
+
function fail(message, docPath, expression) {
|
|
4395
|
+
throw new ArazzoError(message, { path: docPath, expression });
|
|
4396
|
+
}
|
|
4397
|
+
var TOKEN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
4398
|
+
function parseSourceRef(prefix, rest, raw, docPath) {
|
|
4399
|
+
if (rest.startsWith("header.")) {
|
|
4400
|
+
const name = rest.slice("header.".length);
|
|
4401
|
+
if (name === "" || !TOKEN.test(name)) {
|
|
4402
|
+
fail(`Invalid header name in runtime expression "${raw}"`, docPath, raw);
|
|
4403
|
+
}
|
|
4404
|
+
return { type: prefix, raw, path: [], source: "header", name };
|
|
4405
|
+
}
|
|
4406
|
+
if (rest.startsWith("query.") || rest.startsWith("path.")) {
|
|
4407
|
+
const source = rest.startsWith("query.") ? "query" : "path";
|
|
4408
|
+
const name = rest.slice(source.length + 1);
|
|
4409
|
+
if (name === "") {
|
|
4410
|
+
fail(`Empty ${source} parameter name in runtime expression "${raw}"`, docPath, raw);
|
|
4411
|
+
}
|
|
4412
|
+
return { type: prefix, raw, path: [], source, name };
|
|
4413
|
+
}
|
|
4414
|
+
if (rest === "body" || rest.startsWith("body#")) {
|
|
4415
|
+
const node = { type: prefix, raw, path: [], source: "body" };
|
|
4416
|
+
if (rest.startsWith("body#")) {
|
|
4417
|
+
const pointer = rest.slice("body#".length);
|
|
4418
|
+
if (pointer !== "" && !pointer.startsWith("/")) {
|
|
4419
|
+
fail(`JSON Pointer in "${raw}" must be empty or start with "/"`, docPath, raw);
|
|
4420
|
+
}
|
|
4421
|
+
node.pointer = pointer;
|
|
4422
|
+
}
|
|
4423
|
+
return node;
|
|
4424
|
+
}
|
|
4425
|
+
fail(`Invalid $${prefix} reference "${raw}" \u2014 expected header.<name>, query.<name>, path.<name>, or body[#<pointer>]`, docPath, raw);
|
|
4426
|
+
}
|
|
4427
|
+
function parseRuntimeExpression(raw, docPath = "") {
|
|
4428
|
+
const exact = EXACT_ROOTS[raw];
|
|
4429
|
+
if (exact) {
|
|
4430
|
+
return { type: exact, raw, path: [] };
|
|
4431
|
+
}
|
|
4432
|
+
for (const key of Object.keys(EXACT_ROOTS)) {
|
|
4433
|
+
if (raw.startsWith(key) && raw !== key) {
|
|
4434
|
+
fail(`Unexpected characters after "${key}" in runtime expression "${raw}"`, docPath, raw);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
for (const prefix of ["request", "response", "message"]) {
|
|
4438
|
+
if (raw.startsWith(`$${prefix}.`)) {
|
|
4439
|
+
return parseSourceRef(prefix, raw.slice(prefix.length + 2), raw, docPath);
|
|
4440
|
+
}
|
|
4441
|
+
}
|
|
4442
|
+
const dot = raw.indexOf(".");
|
|
4443
|
+
const rootToken = dot === -1 ? raw : raw.slice(0, dot);
|
|
4444
|
+
const root = DOTTED_ROOTS[rootToken];
|
|
4445
|
+
if (root) {
|
|
4446
|
+
const rest = dot === -1 ? "" : raw.slice(dot + 1);
|
|
4447
|
+
if (rest === "") {
|
|
4448
|
+
fail(`Runtime expression "${raw}" is missing a name after "${rootToken}."`, docPath, raw);
|
|
4449
|
+
}
|
|
4450
|
+
const path = rest.split(".");
|
|
4451
|
+
if (path.some((segment) => segment === "" || /\s/.test(segment))) {
|
|
4452
|
+
fail(`Runtime expression "${raw}" contains an empty or whitespace path segment`, docPath, raw);
|
|
4453
|
+
}
|
|
4454
|
+
return { type: root, raw, path };
|
|
4455
|
+
}
|
|
4456
|
+
fail(`Invalid runtime expression "${raw}"`, docPath, raw);
|
|
4457
|
+
}
|
|
4458
|
+
function parseExpressionValue(value, docPath = "") {
|
|
4459
|
+
if (typeof value !== "string") {
|
|
4460
|
+
return { kind: "literal", value };
|
|
4461
|
+
}
|
|
4462
|
+
if (value.startsWith("$")) {
|
|
4463
|
+
if (KNOWN_ROOT.test(value)) {
|
|
4464
|
+
return { kind: "expression", expression: parseRuntimeExpression(value, docPath) };
|
|
4465
|
+
}
|
|
4466
|
+
return { kind: "literal", value };
|
|
4467
|
+
}
|
|
4468
|
+
if (!value.includes("{$")) {
|
|
4469
|
+
return { kind: "literal", value };
|
|
4470
|
+
}
|
|
4471
|
+
const parts = [];
|
|
4472
|
+
let cursor = 0;
|
|
4473
|
+
while (cursor < value.length) {
|
|
4474
|
+
const open = value.indexOf("{$", cursor);
|
|
4475
|
+
if (open === -1) {
|
|
4476
|
+
parts.push(value.slice(cursor));
|
|
4477
|
+
break;
|
|
4478
|
+
}
|
|
4479
|
+
if (open > cursor) {
|
|
4480
|
+
parts.push(value.slice(cursor, open));
|
|
4481
|
+
}
|
|
4482
|
+
const close = value.indexOf("}", open);
|
|
4483
|
+
if (close === -1) {
|
|
4484
|
+
fail(`Unterminated "{$" template expression in "${value}"`, docPath, value);
|
|
4485
|
+
}
|
|
4486
|
+
parts.push(parseRuntimeExpression(value.slice(open + 1, close), docPath));
|
|
4487
|
+
cursor = close + 1;
|
|
4488
|
+
}
|
|
4489
|
+
return { kind: "template", raw: value, parts };
|
|
4490
|
+
}
|
|
4491
|
+
function escapePointerSegment(segment) {
|
|
4492
|
+
return segment.replace(/~/g, "~0").replace(/\//g, "~1");
|
|
4493
|
+
}
|
|
4494
|
+
function collectPayloadExpressions(payload, docPath = "") {
|
|
4495
|
+
const found = [];
|
|
4496
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4497
|
+
const visit = (node, pointer) => {
|
|
4498
|
+
if (typeof node === "string") {
|
|
4499
|
+
const value = parseExpressionValue(node, docPath);
|
|
4500
|
+
if (value.kind !== "literal") {
|
|
4501
|
+
found.push({ pointer, value });
|
|
4502
|
+
}
|
|
4503
|
+
return;
|
|
4504
|
+
}
|
|
4505
|
+
if (!node || typeof node !== "object") {
|
|
4506
|
+
return;
|
|
4507
|
+
}
|
|
4508
|
+
if (seen.has(node)) {
|
|
4509
|
+
return;
|
|
4510
|
+
}
|
|
4511
|
+
seen.add(node);
|
|
4512
|
+
if (Array.isArray(node)) {
|
|
4513
|
+
node.forEach((item, index) => visit(item, `${pointer}/${index}`));
|
|
4514
|
+
return;
|
|
4515
|
+
}
|
|
4516
|
+
for (const [key, value] of Object.entries(node)) {
|
|
4517
|
+
visit(value, `${pointer}/${escapePointerSegment(key)}`);
|
|
4518
|
+
}
|
|
4519
|
+
};
|
|
4520
|
+
visit(payload, "");
|
|
4521
|
+
return found;
|
|
4522
|
+
}
|
|
4523
|
+
|
|
4524
|
+
// src/arazzo.ts
|
|
4525
|
+
import * as yaml2 from "yaml";
|
|
4526
|
+
var ID_PATTERN = /^[A-Za-z0-9_-]+$/;
|
|
4527
|
+
var OUTPUT_KEY_PATTERN = /^[a-zA-Z0-9.\-_]+$/;
|
|
4528
|
+
var VERSION_PATTERN = /^1\.0\.\d+$/;
|
|
4529
|
+
var HTTP_METHODS = ["get", "post", "put", "patch", "delete", "head", "options", "trace"];
|
|
4530
|
+
var PARAMETER_LOCATIONS = ["path", "query", "header", "cookie"];
|
|
4531
|
+
var OUTPUT_DERIVATION_MAX_DEPTH = 8;
|
|
4532
|
+
function err(message, path, extra) {
|
|
4533
|
+
throw new ArazzoError(message, { path, ...extra });
|
|
4534
|
+
}
|
|
4535
|
+
function toPlainJson(value) {
|
|
4536
|
+
try {
|
|
4537
|
+
return JSON.parse(JSON.stringify(value));
|
|
4538
|
+
} catch (error) {
|
|
4539
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4540
|
+
throw new ArazzoError(`Arazzo document must be JSON-serializable (acyclic, bounded depth): ${message}`, {
|
|
4541
|
+
path: ""
|
|
4542
|
+
});
|
|
4543
|
+
}
|
|
4544
|
+
}
|
|
4545
|
+
function parseArazzoInput(input) {
|
|
4546
|
+
if (typeof input === "string") {
|
|
4547
|
+
let parsed;
|
|
4548
|
+
try {
|
|
4549
|
+
parsed = yaml2.parse(input);
|
|
4550
|
+
} catch (error) {
|
|
4551
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4552
|
+
throw new ArazzoError(`Failed to parse Arazzo document: ${message}`, { path: "" });
|
|
4553
|
+
}
|
|
4554
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
4555
|
+
err("Arazzo document must be an object", "");
|
|
4556
|
+
}
|
|
4557
|
+
return toPlainJson(parsed);
|
|
4558
|
+
}
|
|
4559
|
+
if (!input || typeof input !== "object" || Array.isArray(input)) {
|
|
4560
|
+
err("Arazzo document must be an object", "");
|
|
4561
|
+
}
|
|
4562
|
+
return toPlainJson(input);
|
|
4563
|
+
}
|
|
4564
|
+
function validateCriteria(criteria, path) {
|
|
4565
|
+
if (criteria === void 0) return;
|
|
4566
|
+
if (!Array.isArray(criteria)) {
|
|
4567
|
+
err("successCriteria/criteria must be an array", path);
|
|
4568
|
+
}
|
|
4569
|
+
criteria.forEach((criterion, index) => {
|
|
4570
|
+
const cPath = `${path}/${index}`;
|
|
4571
|
+
if (!criterion || typeof criterion !== "object") {
|
|
4572
|
+
err("Criterion must be an object", cPath);
|
|
4573
|
+
}
|
|
4574
|
+
if (typeof criterion.condition !== "string" || criterion.condition === "") {
|
|
4575
|
+
err('Criterion requires a non-empty string "condition"', cPath);
|
|
4576
|
+
}
|
|
4577
|
+
const type = criterion.type;
|
|
4578
|
+
let effectiveType;
|
|
4579
|
+
if (type !== void 0) {
|
|
4580
|
+
if (typeof type === "string") {
|
|
4581
|
+
if (!["simple", "regex", "jsonpath", "xpath"].includes(type)) {
|
|
4582
|
+
err(`Unknown criterion type "${type}"`, cPath);
|
|
4583
|
+
}
|
|
4584
|
+
effectiveType = type;
|
|
4585
|
+
} else if (type && typeof type === "object") {
|
|
4586
|
+
if (type.type !== "jsonpath" && type.type !== "xpath" || typeof type.version !== "string") {
|
|
4587
|
+
err('Criterion Expression Type Object requires "type" (jsonpath|xpath) and "version"', cPath);
|
|
4588
|
+
}
|
|
4589
|
+
effectiveType = type.type;
|
|
4590
|
+
} else {
|
|
4591
|
+
err('Criterion "type" must be a string or a Criterion Expression Type Object', cPath);
|
|
4592
|
+
}
|
|
4593
|
+
}
|
|
4594
|
+
if (criterion.context !== void 0 && typeof criterion.context !== "string") {
|
|
4595
|
+
err('Criterion "context" must be a runtime expression string', cPath);
|
|
4596
|
+
}
|
|
4597
|
+
if (effectiveType !== void 0 && effectiveType !== "simple" && criterion.context === void 0) {
|
|
4598
|
+
err(`Criterion of type "${effectiveType}" requires a "context" expression`, cPath);
|
|
4599
|
+
}
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
function validateActions(actions, kind, path) {
|
|
4603
|
+
if (actions === void 0) return;
|
|
4604
|
+
if (!Array.isArray(actions)) {
|
|
4605
|
+
err("Actions must be an array", path);
|
|
4606
|
+
}
|
|
4607
|
+
actions.forEach((action, index) => {
|
|
4608
|
+
const aPath = `${path}/${index}`;
|
|
4609
|
+
if (!action || typeof action !== "object") {
|
|
4610
|
+
err("Action must be an object", aPath);
|
|
4611
|
+
}
|
|
4612
|
+
if ("reference" in action) {
|
|
4613
|
+
return;
|
|
4614
|
+
}
|
|
4615
|
+
validateActionObject(action, kind, aPath);
|
|
4616
|
+
});
|
|
4617
|
+
}
|
|
4618
|
+
function validateActionObject(action, kind, aPath) {
|
|
4619
|
+
const act = action;
|
|
4620
|
+
if (typeof act.name !== "string" || act.name === "") {
|
|
4621
|
+
err('Action requires a non-empty string "name"', aPath);
|
|
4622
|
+
}
|
|
4623
|
+
const allowed = kind === "success" ? ["end", "goto"] : ["end", "retry", "goto"];
|
|
4624
|
+
if (!allowed.includes(act.type)) {
|
|
4625
|
+
err(`Invalid ${kind}-action type "${String(act.type)}" (allowed: ${allowed.join(", ")})`, aPath);
|
|
4626
|
+
}
|
|
4627
|
+
const targets = [act.workflowId, act.stepId].filter((t) => t !== void 0).length;
|
|
4628
|
+
if (act.type === "goto" && targets !== 1) {
|
|
4629
|
+
err('A "goto" action requires exactly one of "workflowId" or "stepId"', aPath);
|
|
4630
|
+
}
|
|
4631
|
+
if (act.type === "end" && targets !== 0) {
|
|
4632
|
+
err('An "end" action must not specify "workflowId" or "stepId"', aPath);
|
|
4633
|
+
}
|
|
4634
|
+
if (act.retryAfter !== void 0 && (typeof act.retryAfter !== "number" || act.retryAfter < 0)) {
|
|
4635
|
+
err('"retryAfter" must be a non-negative number', aPath);
|
|
4636
|
+
}
|
|
4637
|
+
if (act.retryLimit !== void 0 && (typeof act.retryLimit !== "number" || !Number.isInteger(act.retryLimit) || act.retryLimit < 0)) {
|
|
4638
|
+
err('"retryLimit" must be a non-negative integer', aPath);
|
|
4639
|
+
}
|
|
4640
|
+
validateCriteria(act.criteria, `${aPath}/criteria`);
|
|
4641
|
+
}
|
|
4642
|
+
function validateParameters(parameters, requireIn, path) {
|
|
4643
|
+
if (parameters === void 0) return;
|
|
4644
|
+
if (!Array.isArray(parameters)) {
|
|
4645
|
+
err("Parameters must be an array", path);
|
|
4646
|
+
}
|
|
4647
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4648
|
+
parameters.forEach((parameter, index) => {
|
|
4649
|
+
const pPath = `${path}/${index}`;
|
|
4650
|
+
if (!parameter || typeof parameter !== "object") {
|
|
4651
|
+
err("Parameter must be an object", pPath);
|
|
4652
|
+
}
|
|
4653
|
+
if ("reference" in parameter) {
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4656
|
+
validateParameterObject(parameter, requireIn, pPath);
|
|
4657
|
+
const param = parameter;
|
|
4658
|
+
const key = `${param.name} ${param.in ?? ""}`;
|
|
4659
|
+
if (seen.has(key)) {
|
|
4660
|
+
err(`Duplicate parameter "${param.name}"${param.in ? ` (in: ${param.in})` : ""}`, pPath);
|
|
4661
|
+
}
|
|
4662
|
+
seen.add(key);
|
|
4663
|
+
});
|
|
4664
|
+
}
|
|
4665
|
+
function validateParameterObject(param, requireIn, pPath) {
|
|
4666
|
+
if (typeof param.name !== "string" || param.name === "") {
|
|
4667
|
+
err('Parameter requires a non-empty string "name"', pPath);
|
|
4668
|
+
}
|
|
4669
|
+
const paramName = param.name;
|
|
4670
|
+
if (!("value" in param)) {
|
|
4671
|
+
err(`Parameter "${paramName}" requires a "value"`, pPath);
|
|
4672
|
+
}
|
|
4673
|
+
if (param.in !== void 0 && !PARAMETER_LOCATIONS.includes(param.in)) {
|
|
4674
|
+
err(`Invalid parameter location "${String(param.in)}"`, pPath);
|
|
4675
|
+
}
|
|
4676
|
+
if (requireIn === true && param.in === void 0) {
|
|
4677
|
+
err(`Parameter "${param.name}" on an operation step requires "in"`, pPath);
|
|
4678
|
+
}
|
|
4679
|
+
if (requireIn === false && param.in !== void 0) {
|
|
4680
|
+
err(`Parameter "${param.name}" on a workflowId step must not specify "in"`, pPath);
|
|
4681
|
+
}
|
|
4682
|
+
}
|
|
4683
|
+
function validateOutputs(outputs, path) {
|
|
4684
|
+
if (outputs === void 0) return;
|
|
4685
|
+
if (!outputs || typeof outputs !== "object" || Array.isArray(outputs)) {
|
|
4686
|
+
err('"outputs" must be an object of name \u2192 runtime expression', path);
|
|
4687
|
+
}
|
|
4688
|
+
for (const [key, value] of Object.entries(outputs)) {
|
|
4689
|
+
if (!OUTPUT_KEY_PATTERN.test(key)) {
|
|
4690
|
+
err(`Invalid output name "${key}"`, `${path}/${key}`);
|
|
4691
|
+
}
|
|
4692
|
+
if (typeof value !== "string") {
|
|
4693
|
+
err(`Output "${key}" must be a runtime expression string`, `${path}/${key}`);
|
|
4694
|
+
}
|
|
4695
|
+
}
|
|
4696
|
+
}
|
|
4697
|
+
function validateDocument(doc) {
|
|
4698
|
+
if (typeof doc.arazzo !== "string" || !VERSION_PATTERN.test(doc.arazzo)) {
|
|
4699
|
+
err(`Unsupported arazzo version "${String(doc.arazzo)}" (expected 1.0.x)`, "/arazzo");
|
|
4700
|
+
}
|
|
4701
|
+
if (!doc.info || typeof doc.info !== "object" || typeof doc.info.title !== "string" || typeof doc.info.version !== "string") {
|
|
4702
|
+
err('"info" requires string "title" and "version"', "/info");
|
|
4703
|
+
}
|
|
4704
|
+
if (!Array.isArray(doc.sourceDescriptions) || doc.sourceDescriptions.length === 0) {
|
|
4705
|
+
err('"sourceDescriptions" must be a non-empty array', "/sourceDescriptions");
|
|
4706
|
+
}
|
|
4707
|
+
const sourceNames = /* @__PURE__ */ new Set();
|
|
4708
|
+
doc.sourceDescriptions.forEach((source, index) => {
|
|
4709
|
+
const sPath = `/sourceDescriptions/${index}`;
|
|
4710
|
+
if (!source || typeof source !== "object" || typeof source.name !== "string" || !ID_PATTERN.test(source.name)) {
|
|
4711
|
+
err('Source description requires a "name" matching [A-Za-z0-9_-]+', sPath);
|
|
4712
|
+
}
|
|
4713
|
+
if (typeof source.url !== "string" || source.url === "") {
|
|
4714
|
+
err(`Source "${source.name}" requires a string "url"`, sPath);
|
|
4715
|
+
}
|
|
4716
|
+
if (source.type !== void 0 && source.type !== "openapi" && source.type !== "arazzo") {
|
|
4717
|
+
err(`Source "${source.name}" has invalid type "${String(source.type)}"`, sPath);
|
|
4718
|
+
}
|
|
4719
|
+
if (sourceNames.has(source.name)) {
|
|
4720
|
+
err(`Duplicate source description name "${source.name}"`, sPath);
|
|
4721
|
+
}
|
|
4722
|
+
sourceNames.add(source.name);
|
|
4723
|
+
});
|
|
4724
|
+
if (!Array.isArray(doc.workflows) || doc.workflows.length === 0) {
|
|
4725
|
+
err('"workflows" must be a non-empty array', "/workflows");
|
|
4726
|
+
}
|
|
4727
|
+
const workflowIds = /* @__PURE__ */ new Set();
|
|
4728
|
+
doc.workflows.forEach((workflow, wIndex) => {
|
|
4729
|
+
const wPath = `/workflows/${wIndex}`;
|
|
4730
|
+
if (!workflow || typeof workflow !== "object" || typeof workflow.workflowId !== "string" || !ID_PATTERN.test(workflow.workflowId)) {
|
|
4731
|
+
err('Workflow requires a "workflowId" matching [A-Za-z0-9_-]+', wPath);
|
|
4732
|
+
}
|
|
4733
|
+
if (workflowIds.has(workflow.workflowId)) {
|
|
4734
|
+
err(`Duplicate workflowId "${workflow.workflowId}"`, wPath);
|
|
4735
|
+
}
|
|
4736
|
+
workflowIds.add(workflow.workflowId);
|
|
4737
|
+
if (!Array.isArray(workflow.steps) || workflow.steps.length === 0) {
|
|
4738
|
+
err(`Workflow "${workflow.workflowId}" requires a non-empty "steps" array`, `${wPath}/steps`);
|
|
4739
|
+
}
|
|
4740
|
+
validateParameters(workflow.parameters, void 0, `${wPath}/parameters`);
|
|
4741
|
+
validateActions(workflow.successActions, "success", `${wPath}/successActions`);
|
|
4742
|
+
validateActions(workflow.failureActions, "failure", `${wPath}/failureActions`);
|
|
4743
|
+
validateOutputs(workflow.outputs, `${wPath}/outputs`);
|
|
4744
|
+
const stepIds = /* @__PURE__ */ new Set();
|
|
4745
|
+
workflow.steps.forEach((step, sIndex) => {
|
|
4746
|
+
const sPath = `${wPath}/steps/${sIndex}`;
|
|
4747
|
+
if (!step || typeof step !== "object" || typeof step.stepId !== "string" || !ID_PATTERN.test(step.stepId)) {
|
|
4748
|
+
err('Step requires a "stepId" matching [A-Za-z0-9_-]+', sPath);
|
|
4749
|
+
}
|
|
4750
|
+
if (stepIds.has(step.stepId)) {
|
|
4751
|
+
err(`Duplicate stepId "${step.stepId}" in workflow "${workflow.workflowId}"`, sPath);
|
|
4752
|
+
}
|
|
4753
|
+
stepIds.add(step.stepId);
|
|
4754
|
+
const kinds = [step.operationId, step.operationPath, step.workflowId].filter((k) => k !== void 0).length;
|
|
4755
|
+
if (kinds !== 1) {
|
|
4756
|
+
err(`Step "${step.stepId}" requires exactly one of "operationId", "operationPath", or "workflowId"`, sPath);
|
|
4757
|
+
}
|
|
4758
|
+
validateParameters(step.parameters, step.workflowId !== void 0 ? false : true, `${sPath}/parameters`);
|
|
4759
|
+
validateCriteria(step.successCriteria, `${sPath}/successCriteria`);
|
|
4760
|
+
validateActions(step.onSuccess, "success", `${sPath}/onSuccess`);
|
|
4761
|
+
validateActions(step.onFailure, "failure", `${sPath}/onFailure`);
|
|
4762
|
+
validateOutputs(step.outputs, `${sPath}/outputs`);
|
|
4763
|
+
});
|
|
4764
|
+
});
|
|
4765
|
+
}
|
|
4766
|
+
function ownComponent(group, name) {
|
|
4767
|
+
if (!group || !Object.prototype.hasOwnProperty.call(group, name)) {
|
|
4768
|
+
return void 0;
|
|
4769
|
+
}
|
|
4770
|
+
const value = group[name];
|
|
4771
|
+
return value !== null && typeof value === "object" ? value : void 0;
|
|
4772
|
+
}
|
|
4773
|
+
function resolveReusable(entry, components, expectedGroup, path) {
|
|
4774
|
+
if (!entry || typeof entry !== "object" || !("reference" in entry)) {
|
|
4775
|
+
return entry;
|
|
4776
|
+
}
|
|
4777
|
+
const reusable = entry;
|
|
4778
|
+
if (typeof reusable.reference !== "string") {
|
|
4779
|
+
err('Reusable Object "reference" must be a string', path);
|
|
4780
|
+
}
|
|
4781
|
+
const ast = parseRuntimeExpression(reusable.reference, path);
|
|
4782
|
+
if (ast.type !== "components" || ast.path.length < 2 || ast.path[0] !== expectedGroup) {
|
|
4783
|
+
err(`Reference "${reusable.reference}" must point at $components.${expectedGroup}.<name>`, path);
|
|
4784
|
+
}
|
|
4785
|
+
const name = ast.path.slice(1).join(".");
|
|
4786
|
+
const target = ownComponent(components?.[expectedGroup], name);
|
|
4787
|
+
if (!target) {
|
|
4788
|
+
err(`Unknown reference "$components.${expectedGroup}.${name}"`, path);
|
|
4789
|
+
}
|
|
4790
|
+
const resolved = JSON.parse(JSON.stringify(target));
|
|
4791
|
+
if (expectedGroup === "parameters" && "value" in reusable) {
|
|
4792
|
+
resolved.value = reusable.value;
|
|
4793
|
+
}
|
|
4794
|
+
return resolved;
|
|
4795
|
+
}
|
|
4796
|
+
function resolveInputRefs(node, components, path, seen) {
|
|
4797
|
+
if (Array.isArray(node)) {
|
|
4798
|
+
return node.map((item) => resolveInputRefs(item, components, path, seen));
|
|
4799
|
+
}
|
|
4800
|
+
if (!node || typeof node !== "object") {
|
|
4801
|
+
return node;
|
|
4802
|
+
}
|
|
4803
|
+
const record = node;
|
|
4804
|
+
const ref = record["$ref"];
|
|
4805
|
+
if (typeof ref === "string") {
|
|
4806
|
+
const prefix = "#/components/inputs/";
|
|
4807
|
+
if (!ref.startsWith(prefix)) {
|
|
4808
|
+
err(`Unsupported $ref "${ref}" in workflow inputs (only ${prefix}<name> is resolvable)`, path);
|
|
4809
|
+
}
|
|
4810
|
+
const name = ref.slice(prefix.length);
|
|
4811
|
+
const target = ownComponent(components?.inputs, name);
|
|
4812
|
+
if (!target) {
|
|
4813
|
+
err(`Unknown workflow inputs reference "${ref}"`, path);
|
|
4814
|
+
}
|
|
4815
|
+
if (seen.has(name)) {
|
|
4816
|
+
err(`Cyclic workflow inputs reference "${ref}"`, path);
|
|
4817
|
+
}
|
|
4818
|
+
seen.add(name);
|
|
4819
|
+
const resolved = resolveInputRefs(target, components, path, seen);
|
|
4820
|
+
seen.delete(name);
|
|
4821
|
+
return resolved;
|
|
4822
|
+
}
|
|
4823
|
+
const out = {};
|
|
4824
|
+
for (const [key, value] of Object.entries(record)) {
|
|
4825
|
+
out[key] = resolveInputRefs(value, components, path, seen);
|
|
4826
|
+
}
|
|
4827
|
+
return out;
|
|
4828
|
+
}
|
|
4829
|
+
async function prepareSources(doc, options) {
|
|
4830
|
+
const declared = new Map(doc.sourceDescriptions.map((s) => [s.name, s]));
|
|
4831
|
+
const generators = /* @__PURE__ */ new Map();
|
|
4832
|
+
const sourceTypes = /* @__PURE__ */ new Map();
|
|
4833
|
+
for (const [name, source] of Object.entries(options.sources ?? {})) {
|
|
4834
|
+
if (!declared.has(name)) {
|
|
4835
|
+
err(`options.sources contains "${name}", which is not a declared source description`, "/sourceDescriptions", {
|
|
4836
|
+
declared: [...declared.keys()]
|
|
4837
|
+
});
|
|
4838
|
+
}
|
|
4839
|
+
if (source instanceof OpenAPIToolGenerator) {
|
|
4840
|
+
generators.set(name, source);
|
|
4841
|
+
} else {
|
|
4842
|
+
generators.set(name, await OpenAPIToolGenerator.fromJSON(source, options.loadOptions));
|
|
4843
|
+
}
|
|
4844
|
+
}
|
|
4845
|
+
for (const [name, source] of declared) {
|
|
4846
|
+
sourceTypes.set(name, source.type ?? "openapi");
|
|
4847
|
+
}
|
|
4848
|
+
const operationIndex = /* @__PURE__ */ new Map();
|
|
4849
|
+
for (const [name, generator] of generators) {
|
|
4850
|
+
const document = generator.getDocument();
|
|
4851
|
+
for (const [pathStr, pathItem] of Object.entries(document.paths ?? {})) {
|
|
4852
|
+
if (!pathItem || typeof pathItem !== "object") continue;
|
|
4853
|
+
for (const method of HTTP_METHODS) {
|
|
4854
|
+
const operation = pathItem[method];
|
|
4855
|
+
if (!operation || typeof operation !== "object") continue;
|
|
4856
|
+
const operationId = operation["operationId"];
|
|
4857
|
+
if (typeof operationId !== "string") continue;
|
|
4858
|
+
const hits = operationIndex.get(operationId) ?? [];
|
|
4859
|
+
hits.push({ source: name, path: pathStr, method });
|
|
4860
|
+
operationIndex.set(operationId, hits);
|
|
4861
|
+
}
|
|
4862
|
+
}
|
|
4863
|
+
}
|
|
4864
|
+
return { generators, operationIndex, sourceTypes };
|
|
4865
|
+
}
|
|
4866
|
+
function requireGenerator(ctx, source, path) {
|
|
4867
|
+
if (ctx.sourceTypes.get(source) === "arazzo") {
|
|
4868
|
+
err(`Source "${source}" has type "arazzo" \u2014 nested Arazzo sources are not supported`, path);
|
|
4869
|
+
}
|
|
4870
|
+
const generator = ctx.generators.get(source);
|
|
4871
|
+
if (!generator) {
|
|
4872
|
+
err(`No document supplied for source "${source}" (add it to options.sources)`, path, {
|
|
4873
|
+
supplied: [...ctx.generators.keys()]
|
|
4874
|
+
});
|
|
4875
|
+
}
|
|
4876
|
+
return generator;
|
|
4877
|
+
}
|
|
4878
|
+
function parseOperationPath(value, path) {
|
|
4879
|
+
if (!value.startsWith("{")) {
|
|
4880
|
+
err(`operationPath "${value}" must start with a "{$sourceDescriptions...}" expression`, path);
|
|
4881
|
+
}
|
|
4882
|
+
const close = value.indexOf("}");
|
|
4883
|
+
if (close === -1) {
|
|
4884
|
+
err(`operationPath "${value}" is missing "}"`, path);
|
|
4885
|
+
}
|
|
4886
|
+
const ast = parseRuntimeExpression(value.slice(1, close), path);
|
|
4887
|
+
if (ast.type !== "sourceDescriptions" || ast.path.length !== 2 || ast.path[1] !== "url") {
|
|
4888
|
+
err(`operationPath "${value}" must reference $sourceDescriptions.<name>.url`, path);
|
|
4889
|
+
}
|
|
4890
|
+
const source = ast.path[0];
|
|
4891
|
+
const rest = value.slice(close + 1);
|
|
4892
|
+
if (!rest.startsWith("#/")) {
|
|
4893
|
+
err(`operationPath "${value}" requires a "#/paths/..." JSON Pointer after the source expression`, path);
|
|
4894
|
+
}
|
|
4895
|
+
const segments = rest.slice(2).split("/").map((segment) => segment.replace(/~1/g, "/").replace(/~0/g, "~"));
|
|
4896
|
+
if (segments.length !== 3 || segments[0] !== "paths") {
|
|
4897
|
+
err(`operationPath pointer in "${value}" must have the shape #/paths/<path>/<method>`, path);
|
|
4898
|
+
}
|
|
4899
|
+
const method = segments[2].toLowerCase();
|
|
4900
|
+
if (!HTTP_METHODS.includes(method)) {
|
|
4901
|
+
err(`operationPath "${value}" ends in unknown HTTP method "${segments[2]}"`, path);
|
|
4902
|
+
}
|
|
4903
|
+
return { source, path: segments[1], method };
|
|
4904
|
+
}
|
|
4905
|
+
function resolveOperationRef(step, ctx, path) {
|
|
4906
|
+
if (step.operationPath !== void 0) {
|
|
4907
|
+
return parseOperationPath(step.operationPath, path);
|
|
4908
|
+
}
|
|
4909
|
+
const ref = step.operationId;
|
|
4910
|
+
if (ref.startsWith("$")) {
|
|
4911
|
+
const ast = parseRuntimeExpression(ref, path);
|
|
4912
|
+
if (ast.type !== "sourceDescriptions" || ast.path.length < 2) {
|
|
4913
|
+
err(`operationId expression "${ref}" must be $sourceDescriptions.<name>.<operationId>`, path);
|
|
4914
|
+
}
|
|
4915
|
+
const source = ast.path[0];
|
|
4916
|
+
const operationId = ast.path.slice(1).join(".");
|
|
4917
|
+
const hits2 = (ctx.operationIndex.get(operationId) ?? []).filter((h) => h.source === source);
|
|
4918
|
+
if (hits2.length === 0) {
|
|
4919
|
+
requireGenerator(ctx, source, path);
|
|
4920
|
+
err(`operationId "${operationId}" not found in source "${source}"`, path);
|
|
4921
|
+
}
|
|
4922
|
+
if (hits2.length > 1) {
|
|
4923
|
+
err(`operationId "${operationId}" is duplicated inside source "${source}"`, path, { hits: hits2 });
|
|
4924
|
+
}
|
|
4925
|
+
return { ...hits2[0], operationId };
|
|
4926
|
+
}
|
|
4927
|
+
const hits = ctx.operationIndex.get(ref) ?? [];
|
|
4928
|
+
if (hits.length === 0) {
|
|
4929
|
+
err(`operationId "${ref}" not found in any supplied source (${[...ctx.generators.keys()].join(", ") || "none"})`, path);
|
|
4930
|
+
}
|
|
4931
|
+
if (hits.length > 1) {
|
|
4932
|
+
err(
|
|
4933
|
+
`operationId "${ref}" is ambiguous across sources (${hits.map((h) => h.source).join(", ")}) \u2014 pin it with $sourceDescriptions.<name>.${ref}`,
|
|
4934
|
+
path,
|
|
4935
|
+
{ hits }
|
|
4936
|
+
);
|
|
4937
|
+
}
|
|
4938
|
+
return { ...hits[0], operationId: ref };
|
|
4939
|
+
}
|
|
4940
|
+
function checkCycles(edges, kind) {
|
|
4941
|
+
const state = /* @__PURE__ */ new Map();
|
|
4942
|
+
for (const start of edges.keys()) {
|
|
4943
|
+
if (state.get(start) === "done") continue;
|
|
4944
|
+
const stack = [{ node: start, next: 0 }];
|
|
4945
|
+
state.set(start, "visiting");
|
|
4946
|
+
while (stack.length > 0) {
|
|
4947
|
+
const frame = stack[stack.length - 1];
|
|
4948
|
+
const targets = edges.get(frame.node) ?? [];
|
|
4949
|
+
if (frame.next >= targets.length) {
|
|
4950
|
+
state.set(frame.node, "done");
|
|
4951
|
+
stack.pop();
|
|
4952
|
+
continue;
|
|
4953
|
+
}
|
|
4954
|
+
const target = targets[frame.next++];
|
|
4955
|
+
const targetState = state.get(target);
|
|
4956
|
+
if (targetState === "visiting") {
|
|
4957
|
+
const cycle = [...stack.map((f) => f.node), target];
|
|
4958
|
+
err(`Cyclic ${kind}: ${cycle.slice(cycle.indexOf(target)).join(" -> ")}`, "/workflows");
|
|
4959
|
+
}
|
|
4960
|
+
if (targetState !== "done") {
|
|
4961
|
+
state.set(target, "visiting");
|
|
4962
|
+
stack.push({ node: target, next: 0 });
|
|
4963
|
+
}
|
|
4964
|
+
}
|
|
4965
|
+
}
|
|
4966
|
+
}
|
|
4967
|
+
function toCriterionIR(criterion, path) {
|
|
4968
|
+
const ir = {
|
|
4969
|
+
condition: criterion.condition,
|
|
4970
|
+
type: "simple"
|
|
4971
|
+
};
|
|
4972
|
+
if (criterion.context !== void 0) {
|
|
4973
|
+
ir.context = parseRuntimeExpression(criterion.context, path);
|
|
4974
|
+
}
|
|
4975
|
+
if (typeof criterion.type === "string") {
|
|
4976
|
+
ir.type = criterion.type;
|
|
4977
|
+
} else if (criterion.type) {
|
|
4978
|
+
ir.type = criterion.type.type;
|
|
4979
|
+
ir.version = criterion.type.version;
|
|
4980
|
+
}
|
|
4981
|
+
return ir;
|
|
4982
|
+
}
|
|
4983
|
+
function toActionIR(action, kind, path) {
|
|
4984
|
+
const failure = action;
|
|
4985
|
+
return {
|
|
4986
|
+
name: action.name,
|
|
4987
|
+
kind,
|
|
4988
|
+
type: action.type,
|
|
4989
|
+
...action.workflowId !== void 0 && { workflowId: action.workflowId },
|
|
4990
|
+
...action.stepId !== void 0 && { stepId: action.stepId },
|
|
4991
|
+
...failure.retryAfter !== void 0 && { retryAfter: failure.retryAfter },
|
|
4992
|
+
...failure.retryLimit !== void 0 && { retryLimit: failure.retryLimit },
|
|
4993
|
+
...action.criteria && { criteria: action.criteria.map((c, i) => toCriterionIR(c, `${path}/criteria/${i}`)) }
|
|
4994
|
+
};
|
|
4995
|
+
}
|
|
4996
|
+
function resolveActions(actions, kind, components, path) {
|
|
4997
|
+
const group = kind === "success" ? "successActions" : "failureActions";
|
|
4998
|
+
return actions.map((action, index) => {
|
|
4999
|
+
const aPath = `${path}/${index}`;
|
|
5000
|
+
const concrete = resolveReusable(action, components, group, aPath);
|
|
5001
|
+
validateActionObject(concrete, kind, aPath);
|
|
5002
|
+
return toActionIR(concrete, kind, aPath);
|
|
5003
|
+
});
|
|
5004
|
+
}
|
|
5005
|
+
function resolveParameters(parameters, components, requireIn, path) {
|
|
5006
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5007
|
+
return parameters.map((parameter, index) => {
|
|
5008
|
+
const pPath = `${path}/${index}`;
|
|
5009
|
+
const concrete = resolveReusable(parameter, components, "parameters", pPath);
|
|
5010
|
+
validateParameterObject(concrete, requireIn, pPath);
|
|
5011
|
+
const key = `${concrete.name} ${concrete.in ?? ""}`;
|
|
5012
|
+
if (seen.has(key)) {
|
|
5013
|
+
err(`Duplicate parameter "${concrete.name}"${concrete.in ? ` (in: ${concrete.in})` : ""}`, pPath);
|
|
5014
|
+
}
|
|
5015
|
+
seen.add(key);
|
|
5016
|
+
return {
|
|
5017
|
+
name: concrete.name,
|
|
5018
|
+
...concrete.in !== void 0 && { in: concrete.in },
|
|
5019
|
+
value: parseExpressionValue(concrete.value, pPath)
|
|
5020
|
+
};
|
|
5021
|
+
});
|
|
5022
|
+
}
|
|
5023
|
+
function parseOutputs(outputs, path) {
|
|
5024
|
+
if (!outputs) return void 0;
|
|
5025
|
+
const parsed = {};
|
|
5026
|
+
for (const [name, expression] of Object.entries(outputs)) {
|
|
5027
|
+
parsed[name] = parseRuntimeExpression(expression, `${path}/${name}`);
|
|
5028
|
+
}
|
|
5029
|
+
return parsed;
|
|
5030
|
+
}
|
|
5031
|
+
async function resolveStepOperation(ref, ctx, docPath) {
|
|
5032
|
+
const key = `${ref.source} ${ref.method} ${ref.path}`;
|
|
5033
|
+
let cached = ctx.operationCache.get(key);
|
|
5034
|
+
if (!cached) {
|
|
5035
|
+
const generator = requireGenerator(ctx.sources, ref.source, docPath);
|
|
5036
|
+
cached = generator.generateTool(ref.path, ref.method, ctx.generateOptions).catch((error) => {
|
|
5037
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5038
|
+
throw new ArazzoError(
|
|
5039
|
+
`Failed to resolve ${ref.method.toUpperCase()} ${ref.path} from source "${ref.source}": ${message}`,
|
|
5040
|
+
{ path: docPath, source: ref.source }
|
|
5041
|
+
);
|
|
5042
|
+
});
|
|
5043
|
+
ctx.operationCache.set(key, cached);
|
|
5044
|
+
}
|
|
5045
|
+
return cached;
|
|
5046
|
+
}
|
|
5047
|
+
async function buildStepIR(step, ctx, path) {
|
|
5048
|
+
const components = ctx.doc.components;
|
|
5049
|
+
const base = {
|
|
5050
|
+
stepId: step.stepId,
|
|
5051
|
+
...step.description !== void 0 && { description: step.description },
|
|
5052
|
+
...step.parameters && {
|
|
5053
|
+
parameters: resolveParameters(
|
|
5054
|
+
step.parameters,
|
|
5055
|
+
components,
|
|
5056
|
+
step.workflowId !== void 0 ? false : true,
|
|
5057
|
+
`${path}/parameters`
|
|
5058
|
+
)
|
|
5059
|
+
},
|
|
5060
|
+
...step.successCriteria && {
|
|
5061
|
+
successCriteria: step.successCriteria.map((c, i) => toCriterionIR(c, `${path}/successCriteria/${i}`))
|
|
5062
|
+
},
|
|
5063
|
+
...step.onSuccess && { onSuccess: resolveActions(step.onSuccess, "success", components, `${path}/onSuccess`) },
|
|
5064
|
+
...step.onFailure && { onFailure: resolveActions(step.onFailure, "failure", components, `${path}/onFailure`) },
|
|
5065
|
+
...step.outputs && { outputs: parseOutputs(step.outputs, `${path}/outputs`) }
|
|
5066
|
+
};
|
|
5067
|
+
if (step.workflowId !== void 0) {
|
|
5068
|
+
if (step.requestBody !== void 0) {
|
|
5069
|
+
err(`Step "${step.stepId}" invokes a workflow and must not declare a requestBody`, `${path}/requestBody`);
|
|
5070
|
+
}
|
|
5071
|
+
if (step.workflowId.startsWith("$")) {
|
|
5072
|
+
err(`Step "${step.stepId}" invokes a workflow in another Arazzo document \u2014 nested Arazzo sources are not supported`, path);
|
|
5073
|
+
}
|
|
5074
|
+
if (!ctx.workflowIds.has(step.workflowId)) {
|
|
5075
|
+
err(`Step "${step.stepId}" references unknown workflow "${step.workflowId}"`, path);
|
|
5076
|
+
}
|
|
5077
|
+
const ir2 = { kind: "workflow", workflowId: step.workflowId, ...base };
|
|
5078
|
+
return ir2;
|
|
5079
|
+
}
|
|
5080
|
+
const ref = resolveOperationRef(step, ctx.sources, path);
|
|
5081
|
+
const tool = await resolveStepOperation(ref, ctx, path);
|
|
5082
|
+
const operation = {
|
|
5083
|
+
inputSchema: tool.inputSchema,
|
|
5084
|
+
outputSchema: tool.outputSchema,
|
|
5085
|
+
mapper: tool.mapper,
|
|
5086
|
+
...tool.metadata.security && { security: tool.metadata.security },
|
|
5087
|
+
...tool.metadata.servers && { servers: tool.metadata.servers }
|
|
5088
|
+
};
|
|
5089
|
+
let requestBody;
|
|
5090
|
+
if (step.requestBody !== void 0) {
|
|
5091
|
+
if (!step.requestBody || typeof step.requestBody !== "object") {
|
|
5092
|
+
err(`Step "${step.stepId}" requestBody must be an object`, `${path}/requestBody`);
|
|
5093
|
+
}
|
|
5094
|
+
requestBody = {
|
|
5095
|
+
...step.requestBody.contentType !== void 0 && { contentType: step.requestBody.contentType },
|
|
5096
|
+
...step.requestBody.payload !== void 0 && { payload: step.requestBody.payload }
|
|
5097
|
+
};
|
|
5098
|
+
const expressions = collectPayloadExpressions(step.requestBody.payload, `${path}/requestBody/payload`);
|
|
5099
|
+
if (expressions.length > 0) {
|
|
5100
|
+
requestBody.payloadExpressions = expressions;
|
|
5101
|
+
}
|
|
5102
|
+
if (step.requestBody.replacements !== void 0) {
|
|
5103
|
+
if (!Array.isArray(step.requestBody.replacements)) {
|
|
5104
|
+
err(`Step "${step.stepId}" requestBody.replacements must be an array`, `${path}/requestBody/replacements`);
|
|
5105
|
+
}
|
|
5106
|
+
requestBody.replacements = step.requestBody.replacements.map((replacement, index) => {
|
|
5107
|
+
const rPath = `${path}/requestBody/replacements/${index}`;
|
|
5108
|
+
if (!replacement || typeof replacement !== "object" || typeof replacement.target !== "string") {
|
|
5109
|
+
err('Replacement requires a string "target"', rPath);
|
|
5110
|
+
}
|
|
5111
|
+
return { target: replacement.target, value: parseExpressionValue(replacement.value, rPath) };
|
|
5112
|
+
});
|
|
5113
|
+
}
|
|
5114
|
+
}
|
|
5115
|
+
const ir = {
|
|
5116
|
+
kind: "operation",
|
|
5117
|
+
source: ref.source,
|
|
5118
|
+
path: ref.path,
|
|
5119
|
+
method: ref.method,
|
|
5120
|
+
...ref.operationId !== void 0 && { operationId: ref.operationId },
|
|
5121
|
+
operation,
|
|
5122
|
+
...requestBody && { requestBody },
|
|
5123
|
+
...base
|
|
5124
|
+
};
|
|
5125
|
+
return ir;
|
|
5126
|
+
}
|
|
5127
|
+
function isRecord(value) {
|
|
5128
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5129
|
+
}
|
|
5130
|
+
function walkPointer(schema, pointer) {
|
|
5131
|
+
if (pointer === void 0 || pointer === "") {
|
|
5132
|
+
return schema;
|
|
5133
|
+
}
|
|
5134
|
+
let node = schema;
|
|
5135
|
+
for (const rawSegment of pointer.slice(1).split("/")) {
|
|
5136
|
+
const segment = rawSegment.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
5137
|
+
if (!isRecord(node)) return void 0;
|
|
5138
|
+
const properties = node["properties"];
|
|
5139
|
+
if (isRecord(properties) && properties[segment] !== void 0) {
|
|
5140
|
+
node = properties[segment];
|
|
5141
|
+
continue;
|
|
5142
|
+
}
|
|
5143
|
+
if (/^\d+$/.test(segment) && node["items"] !== void 0 && !Array.isArray(node["items"])) {
|
|
5144
|
+
node = node["items"];
|
|
5145
|
+
continue;
|
|
5146
|
+
}
|
|
5147
|
+
return void 0;
|
|
5148
|
+
}
|
|
5149
|
+
return node;
|
|
5150
|
+
}
|
|
5151
|
+
function primaryResponseSchema(outputSchema) {
|
|
5152
|
+
if (isRecord(outputSchema) && Array.isArray(outputSchema["oneOf"])) {
|
|
5153
|
+
const variants = outputSchema["oneOf"];
|
|
5154
|
+
if (variants.length > 0 && variants.every((v) => isRecord(v) && v["x-status-code"] !== void 0)) {
|
|
5155
|
+
return variants[0];
|
|
5156
|
+
}
|
|
5157
|
+
}
|
|
5158
|
+
return outputSchema;
|
|
5159
|
+
}
|
|
5160
|
+
function deriveOutputSchema(ast, steps, inputSchema, depth, stepContext) {
|
|
5161
|
+
if (depth >= OUTPUT_DERIVATION_MAX_DEPTH) {
|
|
5162
|
+
return {};
|
|
5163
|
+
}
|
|
5164
|
+
if (ast.type === "statusCode") {
|
|
5165
|
+
return { type: "number" };
|
|
5166
|
+
}
|
|
5167
|
+
if (ast.type === "url" || ast.type === "method") {
|
|
5168
|
+
return { type: "string" };
|
|
5169
|
+
}
|
|
5170
|
+
if (ast.type === "response") {
|
|
5171
|
+
if (ast.source !== "body") {
|
|
5172
|
+
return { type: "string" };
|
|
5173
|
+
}
|
|
5174
|
+
if (!stepContext) {
|
|
5175
|
+
return {};
|
|
5176
|
+
}
|
|
5177
|
+
const body = primaryResponseSchema(stepContext.operation.outputSchema);
|
|
5178
|
+
const target = walkPointer(body, ast.pointer);
|
|
5179
|
+
return isRecord(target) ? target : {};
|
|
5180
|
+
}
|
|
5181
|
+
if (ast.type === "inputs") {
|
|
5182
|
+
const properties = isRecord(inputSchema) ? inputSchema["properties"] : void 0;
|
|
5183
|
+
const target = isRecord(properties) ? properties[ast.path.join(".")] : void 0;
|
|
5184
|
+
return isRecord(target) ? target : {};
|
|
5185
|
+
}
|
|
5186
|
+
if (ast.type === "steps" && ast.path.length >= 3 && ast.path[1] === "outputs") {
|
|
5187
|
+
const step = steps.get(ast.path[0]);
|
|
5188
|
+
if (step?.kind === "operation") {
|
|
5189
|
+
const stepOutput = step.outputs?.[ast.path.slice(2).join(".")];
|
|
5190
|
+
if (stepOutput) {
|
|
5191
|
+
return deriveOutputSchema(stepOutput, steps, inputSchema, depth + 1, step);
|
|
5192
|
+
}
|
|
5193
|
+
}
|
|
5194
|
+
return {};
|
|
5195
|
+
}
|
|
5196
|
+
return {};
|
|
5197
|
+
}
|
|
5198
|
+
function deriveOutputsSchema(outputs, steps, inputSchema) {
|
|
5199
|
+
if (!outputs) {
|
|
5200
|
+
return void 0;
|
|
5201
|
+
}
|
|
5202
|
+
const stepMap = new Map(steps.map((s) => [s.stepId, s]));
|
|
5203
|
+
const properties = {};
|
|
5204
|
+
for (const [name, ast] of Object.entries(outputs)) {
|
|
5205
|
+
const derived = deriveOutputSchema(ast, stepMap, inputSchema, 0);
|
|
5206
|
+
const copied = JSON.parse(JSON.stringify(derived));
|
|
5207
|
+
properties[name] = { ...copied, description: `Arazzo output: ${ast.raw}` };
|
|
5208
|
+
}
|
|
5209
|
+
return { type: "object", properties };
|
|
5210
|
+
}
|
|
5211
|
+
function applySchemaPipeline(schema, options, isInputRoot) {
|
|
5212
|
+
const formatResolvers = {
|
|
5213
|
+
...options.resolveFormats ? BUILTIN_FORMAT_RESOLVERS : {},
|
|
5214
|
+
...options.formatResolvers
|
|
5215
|
+
};
|
|
5216
|
+
let resolved = Object.keys(formatResolvers).length > 0 ? resolveSchemaFormats(schema, formatResolvers) : schema;
|
|
5217
|
+
resolved = SchemaBuilder.truncateDepth(resolved, Math.max(1, options.maxSchemaDepth ?? 10));
|
|
5218
|
+
if (options.stripExamples) resolved = SchemaBuilder.stripExamples(resolved);
|
|
5219
|
+
if (options.maxDescriptionLength !== void 0) {
|
|
5220
|
+
resolved = SchemaBuilder.capDescriptions(resolved, options.maxDescriptionLength);
|
|
5221
|
+
}
|
|
5222
|
+
if (options.maxProperties !== void 0) {
|
|
5223
|
+
if (isInputRoot) {
|
|
5224
|
+
const properties = resolved.properties;
|
|
5225
|
+
if (properties && typeof properties === "object") {
|
|
5226
|
+
const limited = {};
|
|
5227
|
+
for (const [key, value] of Object.entries(properties)) {
|
|
5228
|
+
limited[key] = SchemaBuilder.limitProperties(value, options.maxProperties);
|
|
5229
|
+
}
|
|
5230
|
+
resolved = { ...resolved, properties: limited };
|
|
5231
|
+
}
|
|
5232
|
+
} else {
|
|
5233
|
+
resolved = SchemaBuilder.limitProperties(resolved, options.maxProperties);
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
if (options.target) {
|
|
5237
|
+
resolved = applyClientTarget(resolved, options.target);
|
|
5238
|
+
}
|
|
5239
|
+
return resolved;
|
|
5240
|
+
}
|
|
5241
|
+
function buildWorkflowTool(workflow, stepIRs, ctx, wPath) {
|
|
5242
|
+
const options = ctx.generateOptions;
|
|
5243
|
+
let inputSchema;
|
|
5244
|
+
let rawInputSchema;
|
|
5245
|
+
if (workflow.inputs !== void 0) {
|
|
5246
|
+
const resolved = resolveInputRefs(workflow.inputs, ctx.doc.components, `${wPath}/inputs`, /* @__PURE__ */ new Set());
|
|
5247
|
+
rawInputSchema = toJsonSchema(resolved);
|
|
5248
|
+
inputSchema = applySchemaPipeline(rawInputSchema, options, true);
|
|
5249
|
+
} else {
|
|
5250
|
+
inputSchema = { type: "object", properties: {} };
|
|
5251
|
+
}
|
|
5252
|
+
const derivedOutput = deriveOutputsSchema(parseOutputs(workflow.outputs, `${wPath}/outputs`), stepIRs, rawInputSchema);
|
|
5253
|
+
const outputSchema = derivedOutput ? applySchemaPipeline(derivedOutput, options, false) : void 0;
|
|
5254
|
+
const name = normalizeToolName(workflow.workflowId, options.maxToolNameLength ?? 64, workflow.workflowId);
|
|
5255
|
+
const description = workflow.summary && workflow.description ? `${workflow.summary}
|
|
5256
|
+
|
|
5257
|
+
${workflow.description}` : workflow.summary ?? workflow.description ?? `Arazzo workflow: ${workflow.workflowId}`;
|
|
5258
|
+
const operationSteps = stepIRs.filter((s) => s.kind === "operation");
|
|
5259
|
+
const allReadOnly = operationSteps.length === stepIRs.length && operationSteps.every((s) => inferAnnotationsFromMethod(s.method).readOnlyHint === true);
|
|
5260
|
+
const security = [];
|
|
5261
|
+
const seenSecurity = /* @__PURE__ */ new Set();
|
|
5262
|
+
for (const step of operationSteps) {
|
|
5263
|
+
for (const requirement of step.operation.security ?? []) {
|
|
5264
|
+
const key = JSON.stringify(requirement);
|
|
5265
|
+
if (!seenSecurity.has(key)) {
|
|
5266
|
+
seenSecurity.add(key);
|
|
5267
|
+
security.push(requirement);
|
|
5268
|
+
}
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
const ir = {
|
|
5272
|
+
arazzoVersion: ctx.doc.arazzo,
|
|
5273
|
+
workflowId: workflow.workflowId,
|
|
5274
|
+
...workflow.summary !== void 0 && { summary: workflow.summary },
|
|
5275
|
+
...workflow.description !== void 0 && { description: workflow.description },
|
|
5276
|
+
...rawInputSchema !== void 0 && { inputSchema: rawInputSchema },
|
|
5277
|
+
...workflow.dependsOn && { dependsOn: workflow.dependsOn },
|
|
5278
|
+
...workflow.parameters && {
|
|
5279
|
+
parameters: resolveParameters(workflow.parameters, ctx.doc.components, void 0, `${wPath}/parameters`)
|
|
5280
|
+
},
|
|
5281
|
+
steps: stepIRs,
|
|
5282
|
+
...workflow.successActions && {
|
|
5283
|
+
successActions: resolveActions(workflow.successActions, "success", ctx.doc.components, `${wPath}/successActions`)
|
|
5284
|
+
},
|
|
5285
|
+
...workflow.failureActions && {
|
|
5286
|
+
failureActions: resolveActions(workflow.failureActions, "failure", ctx.doc.components, `${wPath}/failureActions`)
|
|
5287
|
+
},
|
|
5288
|
+
...workflow.outputs && { outputs: parseOutputs(workflow.outputs, `${wPath}/outputs`) }
|
|
5289
|
+
};
|
|
5290
|
+
const tool = {
|
|
5291
|
+
name,
|
|
5292
|
+
...workflow.summary !== void 0 && { title: workflow.summary },
|
|
5293
|
+
description,
|
|
5294
|
+
...allReadOnly && {
|
|
5295
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false }
|
|
5296
|
+
},
|
|
5297
|
+
inputSchema,
|
|
5298
|
+
outputSchema,
|
|
5299
|
+
// A workflow tool has no single HTTP shape — each step's mapper lives at
|
|
5300
|
+
// metadata.workflow.steps[*].operation.mapper
|
|
5301
|
+
mapper: [],
|
|
5302
|
+
metadata: {
|
|
5303
|
+
path: `arazzo:${workflow.workflowId}`,
|
|
5304
|
+
method: "post",
|
|
5305
|
+
operationId: workflow.workflowId,
|
|
5306
|
+
...workflow.summary !== void 0 && { operationSummary: workflow.summary },
|
|
5307
|
+
...workflow.description !== void 0 && { operationDescription: workflow.description },
|
|
5308
|
+
...security.length > 0 && { security },
|
|
5309
|
+
workflow: ir
|
|
5310
|
+
}
|
|
5311
|
+
};
|
|
5312
|
+
if (options.emitTypeSignatures) {
|
|
5313
|
+
tool.metadata.typescript = emitToolTypeScript(name, description, inputSchema, outputSchema, {
|
|
5314
|
+
maxDepth: Math.max(1, options.maxSchemaDepth ?? 10)
|
|
5315
|
+
});
|
|
5316
|
+
}
|
|
5317
|
+
return tool;
|
|
5318
|
+
}
|
|
5319
|
+
async function fromArazzo(document, options) {
|
|
5320
|
+
const doc = parseArazzoInput(document);
|
|
5321
|
+
validateDocument(doc);
|
|
5322
|
+
const sources = await prepareSources(doc, options);
|
|
5323
|
+
const workflowIds = new Set(doc.workflows.map((w) => w.workflowId));
|
|
5324
|
+
const dependsEdges = /* @__PURE__ */ new Map();
|
|
5325
|
+
const nestedEdges = /* @__PURE__ */ new Map();
|
|
5326
|
+
const declaredSources = new Set(doc.sourceDescriptions.map((s) => s.name));
|
|
5327
|
+
doc.workflows.forEach((workflow, index) => {
|
|
5328
|
+
if (workflow.dependsOn !== void 0 && !Array.isArray(workflow.dependsOn)) {
|
|
5329
|
+
err(`Workflow "${workflow.workflowId}" dependsOn must be an array of workflowIds`, `/workflows/${index}/dependsOn`);
|
|
5330
|
+
}
|
|
5331
|
+
const localTargets = [];
|
|
5332
|
+
for (const target of workflow.dependsOn ?? []) {
|
|
5333
|
+
if (typeof target !== "string") {
|
|
5334
|
+
err(`Workflow "${workflow.workflowId}" dependsOn entries must be strings`, `/workflows/${index}/dependsOn`);
|
|
5335
|
+
}
|
|
5336
|
+
if (target.startsWith("$")) {
|
|
5337
|
+
const ast = parseRuntimeExpression(target, `/workflows/${index}/dependsOn`);
|
|
5338
|
+
if (ast.type !== "sourceDescriptions" || ast.path.length < 2 || !declaredSources.has(ast.path[0])) {
|
|
5339
|
+
err(
|
|
5340
|
+
`Workflow "${workflow.workflowId}" dependsOn "${target}" must reference a declared source ($sourceDescriptions.<name>.<workflowId>)`,
|
|
5341
|
+
`/workflows/${index}/dependsOn`
|
|
5342
|
+
);
|
|
5343
|
+
}
|
|
5344
|
+
continue;
|
|
5345
|
+
}
|
|
5346
|
+
if (!workflowIds.has(target)) {
|
|
5347
|
+
err(`Workflow "${workflow.workflowId}" dependsOn unknown workflow "${target}"`, `/workflows/${index}/dependsOn`);
|
|
5348
|
+
}
|
|
5349
|
+
localTargets.push(target);
|
|
5350
|
+
}
|
|
5351
|
+
dependsEdges.set(workflow.workflowId, localTargets);
|
|
5352
|
+
nestedEdges.set(
|
|
5353
|
+
workflow.workflowId,
|
|
5354
|
+
workflow.steps.filter((s) => s.workflowId !== void 0 && !s.workflowId.startsWith("$")).map((s) => s.workflowId)
|
|
5355
|
+
);
|
|
5356
|
+
});
|
|
5357
|
+
checkCycles(dependsEdges, "dependsOn chain");
|
|
5358
|
+
checkCycles(nestedEdges, "workflow invocation");
|
|
5359
|
+
const ctx = {
|
|
5360
|
+
doc,
|
|
5361
|
+
sources,
|
|
5362
|
+
generateOptions: options.generateOptions ?? {},
|
|
5363
|
+
workflowIds,
|
|
5364
|
+
operationCache: /* @__PURE__ */ new Map()
|
|
5365
|
+
};
|
|
5366
|
+
const tools = [];
|
|
5367
|
+
const usedNames = /* @__PURE__ */ new Set();
|
|
5368
|
+
for (let wIndex = 0; wIndex < doc.workflows.length; wIndex++) {
|
|
5369
|
+
const workflow = doc.workflows[wIndex];
|
|
5370
|
+
const wPath = `/workflows/${wIndex}`;
|
|
5371
|
+
const stepIRs = [];
|
|
5372
|
+
for (let sIndex = 0; sIndex < workflow.steps.length; sIndex++) {
|
|
5373
|
+
stepIRs.push(await buildStepIR(workflow.steps[sIndex], ctx, `${wPath}/steps/${sIndex}`));
|
|
5374
|
+
}
|
|
5375
|
+
let tool = buildWorkflowTool(workflow, stepIRs, ctx, wPath);
|
|
5376
|
+
if (usedNames.has(tool.name)) {
|
|
5377
|
+
const maxLength = ctx.generateOptions.maxToolNameLength ?? 64;
|
|
5378
|
+
let seed = workflow.workflowId;
|
|
5379
|
+
let deduped = normalizeToolName(`${tool.name}_${fnv1aHex(seed)}`, maxLength, seed);
|
|
5380
|
+
while (usedNames.has(deduped)) {
|
|
5381
|
+
seed += "#";
|
|
5382
|
+
deduped = normalizeToolName(`${tool.name}_${fnv1aHex(seed)}`, maxLength, seed);
|
|
5383
|
+
}
|
|
5384
|
+
tool = { ...tool, name: deduped };
|
|
5385
|
+
}
|
|
5386
|
+
usedNames.add(tool.name);
|
|
5387
|
+
tools.push(tool);
|
|
5388
|
+
}
|
|
5389
|
+
return tools;
|
|
5390
|
+
}
|
|
5391
|
+
|
|
2869
5392
|
// src/request-builder.ts
|
|
2870
5393
|
var RESERVED_DECODE = {
|
|
2871
5394
|
"%3A": ":",
|
|
@@ -3102,7 +5625,7 @@ function buildHttpRequest(tool, input, options = {}) {
|
|
|
3102
5625
|
case "body":
|
|
3103
5626
|
hasBody = true;
|
|
3104
5627
|
contentType = contentType ?? mapper.serialization?.contentType ?? "application/json";
|
|
3105
|
-
if (mapper.serialization?.binary) binaryBody = true;
|
|
5628
|
+
if (mapper.serialization?.binary && mapper.wholeBody) binaryBody = true;
|
|
3106
5629
|
if (mapper.wholeBody) {
|
|
3107
5630
|
rawBody = value;
|
|
3108
5631
|
} else {
|
|
@@ -3199,24 +5722,66 @@ function buildHttpRequest(tool, input, options = {}) {
|
|
|
3199
5722
|
// src/sdk.ts
|
|
3200
5723
|
function toSdkTool(tool, wrapper) {
|
|
3201
5724
|
const wrapSchema = wrapper?.fromJsonSchema ?? ((schema) => schema);
|
|
5725
|
+
const outputSchema = tool.outputSchema !== void 0 && tool.outputSchema["type"] === "object" ? tool.outputSchema : void 0;
|
|
3202
5726
|
return [
|
|
3203
5727
|
tool.name,
|
|
3204
5728
|
{
|
|
3205
5729
|
...tool.title !== void 0 && { title: tool.title },
|
|
3206
5730
|
description: tool.description,
|
|
3207
5731
|
inputSchema: wrapSchema(tool.inputSchema),
|
|
3208
|
-
...
|
|
5732
|
+
...outputSchema !== void 0 && { outputSchema: wrapSchema(outputSchema) },
|
|
3209
5733
|
...tool.annotations !== void 0 && { annotations: tool.annotations }
|
|
3210
5734
|
}
|
|
3211
5735
|
];
|
|
3212
5736
|
}
|
|
5737
|
+
|
|
5738
|
+
// src/token-report.ts
|
|
5739
|
+
function estimateToolTokens(tool) {
|
|
5740
|
+
const advertised = {
|
|
5741
|
+
name: tool.name,
|
|
5742
|
+
...tool.title !== void 0 && { title: tool.title },
|
|
5743
|
+
description: tool.description,
|
|
5744
|
+
...tool.annotations !== void 0 && { annotations: tool.annotations },
|
|
5745
|
+
inputSchema: tool.inputSchema,
|
|
5746
|
+
...tool.outputSchema !== void 0 && { outputSchema: tool.outputSchema }
|
|
5747
|
+
};
|
|
5748
|
+
return Math.ceil(JSON.stringify(advertised).length / 4);
|
|
5749
|
+
}
|
|
5750
|
+
function analyzeToolSet(tools, options = {}) {
|
|
5751
|
+
const tokenBudget = options.tokenBudget ?? 1e4;
|
|
5752
|
+
const maxRecommendedTools = options.maxRecommendedTools ?? 40;
|
|
5753
|
+
const perToolWarning = options.perToolWarning ?? 2e3;
|
|
5754
|
+
const perTool = tools.map((tool) => ({ name: tool.name, tokens: estimateToolTokens(tool) })).sort((a, b) => b.tokens - a.tokens || (a.name < b.name ? -1 : 1));
|
|
5755
|
+
const estimatedTokens = perTool.reduce((sum, entry) => sum + entry.tokens, 0);
|
|
5756
|
+
const warnings = [];
|
|
5757
|
+
if (tools.length > maxRecommendedTools) {
|
|
5758
|
+
warnings.push(
|
|
5759
|
+
`${tools.length} tools exceeds the ~${maxRecommendedTools}-tool range where model selection accuracy degrades \u2014 curate with filters (tags, paths, readOnlyOnly) or split into focused servers.`
|
|
5760
|
+
);
|
|
5761
|
+
}
|
|
5762
|
+
if (estimatedTokens > tokenBudget) {
|
|
5763
|
+
warnings.push(
|
|
5764
|
+
`Estimated ${estimatedTokens} tokens of tool definitions exceeds the ${tokenBudget}-token budget \u2014 trim schemas (maxSchemaDepth, maxProperties) or reduce the tool count.`
|
|
5765
|
+
);
|
|
5766
|
+
}
|
|
5767
|
+
const heavy = perTool.filter((entry) => entry.tokens > perToolWarning);
|
|
5768
|
+
if (heavy.length > 0) {
|
|
5769
|
+
warnings.push(
|
|
5770
|
+
`${heavy.length} tool(s) exceed ${perToolWarning} tokens each (${heavy.slice(0, 3).map((entry) => `${entry.name}: ~${entry.tokens}`).join(", ")}${heavy.length > 3 ? ", \u2026" : ""}) \u2014 consider schema trimming for these.`
|
|
5771
|
+
);
|
|
5772
|
+
}
|
|
5773
|
+
return { toolCount: tools.length, estimatedTokens, perTool, warnings };
|
|
5774
|
+
}
|
|
3213
5775
|
export {
|
|
5776
|
+
ArazzoError,
|
|
3214
5777
|
BLOCKED_HOSTNAMES,
|
|
3215
5778
|
BUILTIN_FORMAT_RESOLVERS,
|
|
5779
|
+
CODECALL_RESERVED_NAMESPACES,
|
|
3216
5780
|
GenerationError,
|
|
3217
5781
|
LoadError,
|
|
3218
5782
|
OpenAPIToolError,
|
|
3219
5783
|
OpenAPIToolGenerator,
|
|
5784
|
+
OverlayError,
|
|
3220
5785
|
ParameterResolver,
|
|
3221
5786
|
ParseError,
|
|
3222
5787
|
RequestBuildError,
|
|
@@ -3227,7 +5792,9 @@ export {
|
|
|
3227
5792
|
SsrfError,
|
|
3228
5793
|
ValidationError,
|
|
3229
5794
|
Validator,
|
|
5795
|
+
analyzeToolSet,
|
|
3230
5796
|
applyClientTarget,
|
|
5797
|
+
applyOverlay,
|
|
3231
5798
|
assertUrlSafe,
|
|
3232
5799
|
buildHttpRequest,
|
|
3233
5800
|
collapseNestedUnions,
|
|
@@ -3236,19 +5803,27 @@ export {
|
|
|
3236
5803
|
decodeIpv4MappedIpv6,
|
|
3237
5804
|
defaultLookup,
|
|
3238
5805
|
demoteFormats,
|
|
5806
|
+
deriveSecurityElicitations,
|
|
5807
|
+
dottedNaming,
|
|
5808
|
+
emitToolTypeScript,
|
|
3239
5809
|
enforceClosedObjects,
|
|
3240
5810
|
ensureArrayItems,
|
|
5811
|
+
estimateToolTokens,
|
|
3241
5812
|
extractExtensionOverrides,
|
|
5813
|
+
fromArazzo,
|
|
3242
5814
|
inferAnnotationsFromMethod,
|
|
3243
5815
|
inlineLocalRefs,
|
|
3244
5816
|
isBlockedAddress,
|
|
3245
5817
|
isBlockedHostname,
|
|
3246
5818
|
isReferenceObject,
|
|
5819
|
+
lintDocument,
|
|
3247
5820
|
normalizeSsrfOptions,
|
|
5821
|
+
parseRuntimeExpression,
|
|
3248
5822
|
requireAllProperties,
|
|
3249
5823
|
resolveExtensionEnabled,
|
|
3250
5824
|
resolveSchemaFormats,
|
|
3251
5825
|
safeFetch,
|
|
3252
5826
|
toJsonSchema,
|
|
5827
|
+
toPascalIdentifier,
|
|
3253
5828
|
toSdkTool
|
|
3254
5829
|
};
|