@devlab.group/agent-commerce 1.1.0 → 1.2.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 +58 -40
- package/dist/{chunk-M4I7I2UY.js → chunk-5TKNTWMC.js} +3 -3
- package/dist/{chunk-M4I7I2UY.js.map → chunk-5TKNTWMC.js.map} +1 -1
- package/dist/{chunk-64UZVJEO.js → chunk-EIACECVM.js} +7 -4
- package/dist/chunk-EIACECVM.js.map +1 -0
- package/dist/cli/index.js +1307 -78
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +5 -5
- package/dist/index.js +161 -91
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +1 -1
- package/dist/{protocol-adapter-DZ8qkkAa.d.ts → protocol-adapter-BAKGnuZ4.d.ts} +1 -1
- package/dist/{runtime-CC6EFmLP.d.ts → runtime-Bqg9qW3t.d.ts} +33 -2
- package/dist/x402.d.ts +1 -1
- package/dist/x402.js +3 -3
- package/package.json +12 -3
- package/dist/chunk-64UZVJEO.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -2,14 +2,15 @@
|
|
|
2
2
|
import { ZodError, z } from 'zod';
|
|
3
3
|
import { existsSync, mkdirSync, accessSync, constants, closeSync, openSync, readFileSync, chmodSync, promises } from 'fs';
|
|
4
4
|
import * as path from 'path';
|
|
5
|
-
import { resolve, dirname, join } from 'path';
|
|
6
|
-
import { stringify, parse, YAMLError } from 'yaml';
|
|
5
|
+
import { resolve, dirname, basename as basename$1, extname, join } from 'path';
|
|
6
|
+
import { stringify, parse, Document, YAMLError } from 'yaml';
|
|
7
7
|
import { CommanderError, Command } from 'commander';
|
|
8
8
|
import picocolors from 'picocolors';
|
|
9
9
|
import { randomUUID } from 'crypto';
|
|
10
10
|
import Database from 'better-sqlite3';
|
|
11
11
|
import { createRequire } from 'module';
|
|
12
|
-
import { writeFile } from 'fs/promises';
|
|
12
|
+
import { writeFile, stat, readFile, rename, rm } from 'fs/promises';
|
|
13
|
+
import { validate } from '@scalar/openapi-parser';
|
|
13
14
|
import * as clack from '@clack/prompts';
|
|
14
15
|
|
|
15
16
|
var __defProp = Object.defineProperty;
|
|
@@ -30,12 +31,34 @@ var init_filename = __esm({
|
|
|
30
31
|
}
|
|
31
32
|
});
|
|
32
33
|
|
|
34
|
+
// src/core/domain/common.ts
|
|
35
|
+
var PROTOCOL_NAMES;
|
|
36
|
+
var init_common = __esm({
|
|
37
|
+
"src/core/domain/common.ts"() {
|
|
38
|
+
PROTOCOL_NAMES = ["http", "mcp", "a2a"];
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// src/core/domain/event.ts
|
|
43
|
+
var init_event = __esm({
|
|
44
|
+
"src/core/domain/event.ts"() {
|
|
45
|
+
}
|
|
46
|
+
});
|
|
47
|
+
|
|
33
48
|
// src/core/domain/resource.ts
|
|
34
49
|
var init_resource = __esm({
|
|
35
50
|
"src/core/domain/resource.ts"() {
|
|
36
51
|
}
|
|
37
52
|
});
|
|
38
53
|
|
|
54
|
+
// src/core/domain/wire.ts
|
|
55
|
+
var PAYMENT_INPUT_FIELD;
|
|
56
|
+
var init_wire = __esm({
|
|
57
|
+
"src/core/domain/wire.ts"() {
|
|
58
|
+
PAYMENT_INPUT_FIELD = "_payment";
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
39
62
|
// src/core/errors/codes.ts
|
|
40
63
|
var COMMERCE_ERROR_HTTP_STATUS, RETRYABLE_ERROR_CODES;
|
|
41
64
|
var init_codes = __esm({
|
|
@@ -142,6 +165,45 @@ var init_logger = __esm({
|
|
|
142
165
|
}
|
|
143
166
|
});
|
|
144
167
|
|
|
168
|
+
// src/core/interfaces/protocol-adapter.ts
|
|
169
|
+
var init_protocol_adapter = __esm({
|
|
170
|
+
"src/core/interfaces/protocol-adapter.ts"() {
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// src/core/interfaces/runtime.ts
|
|
175
|
+
var systemClock;
|
|
176
|
+
var init_runtime = __esm({
|
|
177
|
+
"src/core/interfaces/runtime.ts"() {
|
|
178
|
+
systemClock = {
|
|
179
|
+
now: () => /* @__PURE__ */ new Date(),
|
|
180
|
+
nowIso: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
181
|
+
monotonicMs: () => performance.now()
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
// src/core/public-types.ts
|
|
187
|
+
var init_public_types = __esm({
|
|
188
|
+
"src/core/public-types.ts"() {
|
|
189
|
+
init_common();
|
|
190
|
+
init_event();
|
|
191
|
+
init_resource();
|
|
192
|
+
init_wire();
|
|
193
|
+
init_errors();
|
|
194
|
+
init_logger();
|
|
195
|
+
init_protocol_adapter();
|
|
196
|
+
init_runtime();
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// src/core/index.ts
|
|
201
|
+
var init_core = __esm({
|
|
202
|
+
"src/core/index.ts"() {
|
|
203
|
+
init_public_types();
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
145
207
|
// src/core/execution/backend-http.ts
|
|
146
208
|
function extractPathParameterNames(url) {
|
|
147
209
|
return [...url.matchAll(PATH_PARAM_PATTERN)].map((match) => match[1]);
|
|
@@ -169,14 +231,6 @@ var init_event_bus = __esm({
|
|
|
169
231
|
}
|
|
170
232
|
});
|
|
171
233
|
|
|
172
|
-
// src/core/domain/wire.ts
|
|
173
|
-
var PAYMENT_INPUT_FIELD;
|
|
174
|
-
var init_wire = __esm({
|
|
175
|
-
"src/core/domain/wire.ts"() {
|
|
176
|
-
PAYMENT_INPUT_FIELD = "_payment";
|
|
177
|
-
}
|
|
178
|
-
});
|
|
179
|
-
|
|
180
234
|
// src/core/execution/validation.ts
|
|
181
235
|
function typeList(raw) {
|
|
182
236
|
if (typeof raw === "string") return [raw];
|
|
@@ -223,50 +277,6 @@ var init_execution = __esm({
|
|
|
223
277
|
}
|
|
224
278
|
});
|
|
225
279
|
|
|
226
|
-
// src/core/domain/event.ts
|
|
227
|
-
var init_event = __esm({
|
|
228
|
-
"src/core/domain/event.ts"() {
|
|
229
|
-
}
|
|
230
|
-
});
|
|
231
|
-
|
|
232
|
-
// src/core/interfaces/protocol-adapter.ts
|
|
233
|
-
var init_protocol_adapter = __esm({
|
|
234
|
-
"src/core/interfaces/protocol-adapter.ts"() {
|
|
235
|
-
}
|
|
236
|
-
});
|
|
237
|
-
|
|
238
|
-
// src/core/interfaces/runtime.ts
|
|
239
|
-
var systemClock;
|
|
240
|
-
var init_runtime = __esm({
|
|
241
|
-
"src/core/interfaces/runtime.ts"() {
|
|
242
|
-
systemClock = {
|
|
243
|
-
now: () => /* @__PURE__ */ new Date(),
|
|
244
|
-
nowIso: () => (/* @__PURE__ */ new Date()).toISOString(),
|
|
245
|
-
monotonicMs: () => performance.now()
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
// src/core/public-types.ts
|
|
251
|
-
var init_public_types = __esm({
|
|
252
|
-
"src/core/public-types.ts"() {
|
|
253
|
-
init_event();
|
|
254
|
-
init_resource();
|
|
255
|
-
init_wire();
|
|
256
|
-
init_errors();
|
|
257
|
-
init_logger();
|
|
258
|
-
init_protocol_adapter();
|
|
259
|
-
init_runtime();
|
|
260
|
-
}
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// src/core/index.ts
|
|
264
|
-
var init_core = __esm({
|
|
265
|
-
"src/core/index.ts"() {
|
|
266
|
-
init_public_types();
|
|
267
|
-
}
|
|
268
|
-
});
|
|
269
|
-
|
|
270
280
|
// src/payments/x402/networks.ts
|
|
271
281
|
function findNetworkProfile(id) {
|
|
272
282
|
return BY_ID.get(id);
|
|
@@ -770,7 +780,7 @@ function validateMountPaths(protocols) {
|
|
|
770
780
|
if (baseA === baseB || baseA.startsWith(`${baseB}/`) || baseB.startsWith(`${baseA}/`)) {
|
|
771
781
|
throw new CommerceError(
|
|
772
782
|
"CONFIG_INVALID",
|
|
773
|
-
`protocols.${nameA}.mountPath ("${a.mountPath}") collides with protocols.${nameB}.mountPath ("${b.mountPath}")
|
|
783
|
+
`protocols.${nameA}.mountPath ("${a.mountPath}") collides with protocols.${nameB}.mountPath ("${b.mountPath}") - each mount registers a wildcard, so overlapping prefixes cannot both be served`,
|
|
774
784
|
{ details: { path: `protocols.${nameA}.mountPath` } }
|
|
775
785
|
);
|
|
776
786
|
}
|
|
@@ -833,7 +843,8 @@ function normaliseResource(id, entry, protocols, x402) {
|
|
|
833
843
|
);
|
|
834
844
|
}
|
|
835
845
|
validateBackendUrl(id, entry.backend.url);
|
|
836
|
-
|
|
846
|
+
const pathScope = validateInputBindings(id, entry.backend, entry.input);
|
|
847
|
+
validatePathParametersDeclared(id, entry.backend.url, pathScope.schema, pathScope.where);
|
|
837
848
|
if (entry.pricing.type === "dynamic") {
|
|
838
849
|
throw new CommerceError(
|
|
839
850
|
"CONFIG_INVALID",
|
|
@@ -886,7 +897,8 @@ function normaliseResource(id, entry, protocols, x402) {
|
|
|
886
897
|
timeoutMs: toNumber(entry.backend.timeoutMs, `resources.${id}.backend.timeoutMs`, {
|
|
887
898
|
min: 1
|
|
888
899
|
})
|
|
889
|
-
} : {}
|
|
900
|
+
} : {},
|
|
901
|
+
...entry.backend.inputBindings !== void 0 ? { inputBindings: pickDefined(entry.backend.inputBindings) } : {}
|
|
890
902
|
},
|
|
891
903
|
pricing,
|
|
892
904
|
exposedVia: entry.expose,
|
|
@@ -940,7 +952,7 @@ function validateResourceSchemaKeywords(id, path2, schema) {
|
|
|
940
952
|
if ((Object.hasOwn(schema, "properties") || Object.hasOwn(schema, "required")) && excludesObjectType(schema)) {
|
|
941
953
|
throw new CommerceError(
|
|
942
954
|
"CONFIG_INVALID",
|
|
943
|
-
`Resource "${id}" ${path2} declares "properties" and/or "required" but its "type" (${JSON.stringify(schema["type"])}) does not include "object"
|
|
955
|
+
`Resource "${id}" ${path2} declares "properties" and/or "required" but its "type" (${JSON.stringify(schema["type"])}) does not include "object" - the validator will never route a value through either check under that type, so this schema claims to constrain input it does not actually enforce. Remove "properties"/"required", or include "object" in "type".`,
|
|
944
956
|
{ details: { path: `resources.${id}.${path2}`, resourceId: id } }
|
|
945
957
|
);
|
|
946
958
|
}
|
|
@@ -974,7 +986,7 @@ function validateResourceSchemaKeywords(id, path2, schema) {
|
|
|
974
986
|
validateResourceSchemaKeywords(id, `${path2}.items`, items);
|
|
975
987
|
} else if (Array.isArray(items)) {
|
|
976
988
|
console.warn(
|
|
977
|
-
`[agent-commerce] resource "${id}" ${path2}.items is a tuple (an array of schemas), which this gateway does not enforce
|
|
989
|
+
`[agent-commerce] resource "${id}" ${path2}.items is a tuple (an array of schemas), which this gateway does not enforce - only a single schema applied to every array element is supported (see src/core/execution/validation.ts). Each position's schema is unenforced; treat it as documentation only.`
|
|
978
990
|
);
|
|
979
991
|
}
|
|
980
992
|
}
|
|
@@ -983,14 +995,14 @@ function validatePricingAmount(id, amount, x402) {
|
|
|
983
995
|
if (!PRICING_AMOUNT_PATTERN.test(amount)) {
|
|
984
996
|
throw new CommerceError(
|
|
985
997
|
"CONFIG_INVALID",
|
|
986
|
-
`Resource "${id}" has pricing.amount "${amount}", which is not a plain positive decimal (no currency symbol, no thousands separator, no exponent
|
|
998
|
+
`Resource "${id}" has pricing.amount "${amount}", which is not a plain positive decimal (no currency symbol, no thousands separator, no exponent - e.g. "0.01")`,
|
|
987
999
|
{ details: { path: path2, resourceId: id } }
|
|
988
1000
|
);
|
|
989
1001
|
}
|
|
990
1002
|
if (ZERO_AMOUNT_PATTERN.test(amount)) {
|
|
991
1003
|
throw new CommerceError(
|
|
992
1004
|
"CONFIG_INVALID",
|
|
993
|
-
`Resource "${id}" has pricing.amount "0"
|
|
1005
|
+
`Resource "${id}" has pricing.amount "0" - a paid resource cannot cost zero; use "pricing: { type: free }" instead`,
|
|
994
1006
|
{ details: { path: path2, resourceId: id } }
|
|
995
1007
|
);
|
|
996
1008
|
}
|
|
@@ -1005,7 +1017,86 @@ function validatePricingAmount(id, amount, x402) {
|
|
|
1005
1017
|
}
|
|
1006
1018
|
}
|
|
1007
1019
|
}
|
|
1008
|
-
function
|
|
1020
|
+
function validateInputBindings(id, backend, input) {
|
|
1021
|
+
const legacy = { schema: input, where: "its input schema" };
|
|
1022
|
+
const bindings = backend.inputBindings;
|
|
1023
|
+
const templated = extractPathParameterNames(backend.url).length > 0;
|
|
1024
|
+
if (bindings === void 0) return legacy;
|
|
1025
|
+
const path2 = `resources.${id}.backend.inputBindings`;
|
|
1026
|
+
const fail = (message, details = {}) => {
|
|
1027
|
+
throw new CommerceError("CONFIG_INVALID", `Resource "${id}" ${message}`, {
|
|
1028
|
+
details: { path: path2, resourceId: id, ...details }
|
|
1029
|
+
});
|
|
1030
|
+
};
|
|
1031
|
+
const entries = Object.entries(bindings).filter(
|
|
1032
|
+
(entry) => entry[1] !== void 0
|
|
1033
|
+
);
|
|
1034
|
+
if (entries.length === 0) {
|
|
1035
|
+
fail("has an empty backend.inputBindings - remove the block to use the default mapping");
|
|
1036
|
+
}
|
|
1037
|
+
if (backend.method === "GET" || backend.method === "DELETE") {
|
|
1038
|
+
if (bindings.body !== void 0) {
|
|
1039
|
+
fail(
|
|
1040
|
+
`binds a request body on a ${backend.method}, which sends none - the value would be silently dropped`
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
if (templated && bindings.path === void 0) {
|
|
1045
|
+
fail(
|
|
1046
|
+
'has backend.url path parameters but no "path" binding - in explicit binding mode nothing else supplies them, so every call would fail to reach the backend'
|
|
1047
|
+
);
|
|
1048
|
+
}
|
|
1049
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1050
|
+
const properties = isPlainObject2(input?.["properties"]) ? input["properties"] : {};
|
|
1051
|
+
const required = new Set(
|
|
1052
|
+
Array.isArray(input?.["required"]) ? input["required"].filter((value) => typeof value === "string") : []
|
|
1053
|
+
);
|
|
1054
|
+
for (const [location, property] of entries) {
|
|
1055
|
+
if (property === PAYMENT_INPUT_FIELD) {
|
|
1056
|
+
fail(
|
|
1057
|
+
`binds "${location}" to "${PAYMENT_INPUT_FIELD}", which is reserved for payment proofs`,
|
|
1058
|
+
{ location }
|
|
1059
|
+
);
|
|
1060
|
+
}
|
|
1061
|
+
const other = seen.get(property);
|
|
1062
|
+
if (other !== void 0) {
|
|
1063
|
+
fail(`binds both "${other}" and "${location}" to the input property "${property}"`, {
|
|
1064
|
+
location,
|
|
1065
|
+
property
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1068
|
+
seen.set(property, location);
|
|
1069
|
+
if (!Object.hasOwn(properties, property)) {
|
|
1070
|
+
fail(
|
|
1071
|
+
`binds "${location}" to input property "${property}", which the input schema does not declare - the schema is closed, so a caller could never supply it`,
|
|
1072
|
+
{ location, property }
|
|
1073
|
+
);
|
|
1074
|
+
}
|
|
1075
|
+
const declared = properties[property];
|
|
1076
|
+
if (location !== "body" && isPlainObject2(declared) && !isObjectSchemaNode(declared)) {
|
|
1077
|
+
fail(
|
|
1078
|
+
`binds "${location}" to input property "${property}", which is not an object schema - ${location} parameters are read as an object of name/value pairs`,
|
|
1079
|
+
{ location, property }
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
if (templated && bindings.path !== void 0 && !required.has(bindings.path)) {
|
|
1084
|
+
fail(
|
|
1085
|
+
`binds path parameters to input property "${bindings.path}" without listing it in the input schema's "required" - a caller that omits it cannot supply any path parameter, so the request could never be built`,
|
|
1086
|
+
{ property: bindings.path }
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
if (bindings.path === void 0) return { schema: void 0, where: "its input schema" };
|
|
1090
|
+
const group = properties[bindings.path];
|
|
1091
|
+
return {
|
|
1092
|
+
schema: isPlainObject2(group) ? group : void 0,
|
|
1093
|
+
where: `input.properties.${bindings.path}`
|
|
1094
|
+
};
|
|
1095
|
+
}
|
|
1096
|
+
function pickDefined(value) {
|
|
1097
|
+
return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0));
|
|
1098
|
+
}
|
|
1099
|
+
function validatePathParametersDeclared(id, url, input, where) {
|
|
1009
1100
|
const stray = findUnparsedBraceToken(url);
|
|
1010
1101
|
if (stray !== void 0) {
|
|
1011
1102
|
throw new CommerceError(
|
|
@@ -1044,14 +1135,14 @@ function validatePathParametersDeclared(id, url, input) {
|
|
|
1044
1135
|
if (!Object.hasOwn(properties, param)) {
|
|
1045
1136
|
throw new CommerceError(
|
|
1046
1137
|
"CONFIG_INVALID",
|
|
1047
|
-
`Resource "${id}" has backend.url path parameter "{${param}}" which is not declared in
|
|
1138
|
+
`Resource "${id}" has backend.url path parameter "{${param}}" which is not declared in ${where} - the caller has no way to supply it, so every call would settle payment (if priced) and then fail to reach the backend`,
|
|
1048
1139
|
{ details: { path: path2, resourceId: id, param } }
|
|
1049
1140
|
);
|
|
1050
1141
|
}
|
|
1051
1142
|
if (!required.has(param)) {
|
|
1052
1143
|
throw new CommerceError(
|
|
1053
1144
|
"CONFIG_INVALID",
|
|
1054
|
-
`Resource "${id}" has backend.url path parameter "{${param}}" declared in
|
|
1145
|
+
`Resource "${id}" has backend.url path parameter "{${param}}" declared in ${where} but not listed in its "required" - a caller that omits it hits the same unservable-request problem as an undeclared parameter`,
|
|
1055
1146
|
{ details: { path: path2, resourceId: id, param } }
|
|
1056
1147
|
);
|
|
1057
1148
|
}
|
|
@@ -1094,7 +1185,7 @@ function validateAddress(path2, value) {
|
|
|
1094
1185
|
);
|
|
1095
1186
|
}
|
|
1096
1187
|
}
|
|
1097
|
-
var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, MountPathSchema, ProtocolsSchema, DEFAULT_A2A_MOUNT_PATH, BackendMethodSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, PaymentsSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
1188
|
+
var SUPPORTED_CONFIG_VERSION, ADDRESS_PATTERN, ZERO_ADDRESS_PATTERN, MCP_TOOL_NAME_PATTERN, NumberOrString, BooleanOrString, MerchantSchema, ServerSchema, StorageSchema, RESERVED_GATEWAY_PATHS, MountPathSchema, ProtocolsSchema, DEFAULT_A2A_MOUNT_PATH, BackendMethodSchema, BackendInputBindingsSchema, BackendHandlerSchema, PricingSchema, JsonSchemaValueSchema, ResourceEntrySchema, ResourcesMapSchema, FacilitatorAuthSchema, FacilitatorSchema, X402Schema, PaymentsSchema, RawConfigSchema, SUPPORTED_PROTOCOLS, SUPPORTED_PAYMENT_METHODS, EMPTY_CLOSED_OBJECT_SCHEMA, UNSUPPORTED_SCHEMA_KEYWORDS, PRICING_AMOUNT_PATTERN, ZERO_AMOUNT_PATTERN;
|
|
1098
1189
|
var init_schema = __esm({
|
|
1099
1190
|
"src/config/schema.ts"() {
|
|
1100
1191
|
init_execution();
|
|
@@ -1119,15 +1210,15 @@ var init_schema = __esm({
|
|
|
1119
1210
|
adminToken: z.string().min(1).optional(),
|
|
1120
1211
|
/** Browser origins allowed to read the dashboard-facing routes. Empty by default: closed. */
|
|
1121
1212
|
// Entries are matched literally against the browser's
|
|
1122
|
-
// `Origin` header, so `"*"` and a trailing slash match nothing at all
|
|
1213
|
+
// `Origin` header, so `"*"` and a trailing slash match nothing at all -
|
|
1123
1214
|
// fail-closed (a lockout, not a bypass), but silently, and a lockout with
|
|
1124
1215
|
// no explanation is the kind of thing an operator "fixes" by disabling the
|
|
1125
1216
|
// check. Reject those two shapes with a pointer instead.
|
|
1126
1217
|
allowedOrigins: z.array(
|
|
1127
1218
|
z.string().min(1).refine((origin) => origin !== "*", {
|
|
1128
|
-
message: `wildcard "*" is not supported
|
|
1219
|
+
message: `wildcard "*" is not supported - allowedOrigins entries are matched literally against the browser's Origin header, so "*" would match nothing. List each scheme://host[:port] explicitly.`
|
|
1129
1220
|
}).refine((origin) => !origin.endsWith("/"), {
|
|
1130
|
-
message: 'must not end with "/"
|
|
1221
|
+
message: 'must not end with "/" - a browser Origin header never has a trailing slash, so this entry would match nothing. Use e.g. "http://localhost:5173".'
|
|
1131
1222
|
})
|
|
1132
1223
|
).optional()
|
|
1133
1224
|
}).strict();
|
|
@@ -1151,9 +1242,9 @@ var init_schema = __esm({
|
|
|
1151
1242
|
"/api/events/stream"
|
|
1152
1243
|
];
|
|
1153
1244
|
MountPathSchema = z.string().min(1).refine((value) => value.startsWith("/"), {
|
|
1154
|
-
message: 'must start with "/"
|
|
1245
|
+
message: 'must start with "/" - it is an absolute gateway path, e.g. "/mcp".'
|
|
1155
1246
|
}).refine((value) => !/[:*?\s]/.test(value), {
|
|
1156
|
-
message: 'must not contain ":", "*", "?" or whitespace
|
|
1247
|
+
message: 'must not contain ":", "*", "?" or whitespace - the mount is a literal path prefix, not a Fastify route pattern, and registers its own wildcard.'
|
|
1157
1248
|
}).refine(
|
|
1158
1249
|
(value) => {
|
|
1159
1250
|
const base = value.replace(/\/+$/, "");
|
|
@@ -1180,12 +1271,18 @@ var init_schema = __esm({
|
|
|
1180
1271
|
}).strict();
|
|
1181
1272
|
DEFAULT_A2A_MOUNT_PATH = "/a2a";
|
|
1182
1273
|
BackendMethodSchema = z.enum(["GET", "POST", "PUT", "PATCH", "DELETE"]);
|
|
1274
|
+
BackendInputBindingsSchema = z.object({
|
|
1275
|
+
path: z.string().min(1).optional(),
|
|
1276
|
+
query: z.string().min(1).optional(),
|
|
1277
|
+
body: z.string().min(1).optional()
|
|
1278
|
+
}).strict();
|
|
1183
1279
|
BackendHandlerSchema = z.object({
|
|
1184
1280
|
type: z.literal("http"),
|
|
1185
1281
|
method: BackendMethodSchema,
|
|
1186
1282
|
url: z.string().min(1),
|
|
1187
1283
|
headers: z.record(z.string(), z.string()).optional(),
|
|
1188
|
-
timeoutMs: NumberOrString.optional()
|
|
1284
|
+
timeoutMs: NumberOrString.optional(),
|
|
1285
|
+
inputBindings: BackendInputBindingsSchema.optional()
|
|
1189
1286
|
}).strict();
|
|
1190
1287
|
PricingSchema = z.discriminatedUnion("type", [
|
|
1191
1288
|
z.object({ type: z.literal("free") }).strict(),
|
|
@@ -1230,7 +1327,7 @@ var init_schema = __esm({
|
|
|
1230
1327
|
z.object({
|
|
1231
1328
|
mode: z.literal("remote"),
|
|
1232
1329
|
url: z.string().min(1),
|
|
1233
|
-
// Absent means "this facilitator takes no credential"
|
|
1330
|
+
// Absent means "this facilitator takes no credential" - an explicit
|
|
1234
1331
|
// statement, normalised to `{ type: 'none' }` below. On a mainnet that
|
|
1235
1332
|
// combination is refused outright, so the default can never quietly
|
|
1236
1333
|
// become an unauthenticated production facilitator.
|
|
@@ -1248,7 +1345,7 @@ var init_schema = __esm({
|
|
|
1248
1345
|
payTo: z.string().min(1),
|
|
1249
1346
|
maxTimeoutSeconds: NumberOrString,
|
|
1250
1347
|
facilitator: FacilitatorSchema,
|
|
1251
|
-
/** Real funds. Never defaulted
|
|
1348
|
+
/** Real funds. Never defaulted - see src/payments/x402/guardrails.ts. */
|
|
1252
1349
|
allowMainnet: BooleanOrString.optional(),
|
|
1253
1350
|
/** Accepts a mainnet facilitator that takes no credential. Never defaulted. */
|
|
1254
1351
|
allowUnauthenticatedFacilitator: BooleanOrString.optional()
|
|
@@ -1265,7 +1362,7 @@ var init_schema = __esm({
|
|
|
1265
1362
|
resources: ResourcesMapSchema,
|
|
1266
1363
|
payments: PaymentsSchema
|
|
1267
1364
|
}).strict();
|
|
1268
|
-
SUPPORTED_PROTOCOLS =
|
|
1365
|
+
SUPPORTED_PROTOCOLS = new Set(PROTOCOL_NAMES);
|
|
1269
1366
|
SUPPORTED_PAYMENT_METHODS = /* @__PURE__ */ new Set(["x402"]);
|
|
1270
1367
|
EMPTY_CLOSED_OBJECT_SCHEMA = {
|
|
1271
1368
|
type: "object",
|
|
@@ -1387,6 +1484,7 @@ var init_config = __esm({
|
|
|
1387
1484
|
|
|
1388
1485
|
// src/cli/program.ts
|
|
1389
1486
|
init_filename();
|
|
1487
|
+
init_core();
|
|
1390
1488
|
|
|
1391
1489
|
// src/cli/commands/demo.ts
|
|
1392
1490
|
function defaultDemoSteps() {
|
|
@@ -1493,7 +1591,7 @@ var A2A_UNSUPPORTED = [
|
|
|
1493
1591
|
|
|
1494
1592
|
// src/storage/receipts/store.ts
|
|
1495
1593
|
init_core();
|
|
1496
|
-
var PACKAGE_VERSION = "1.
|
|
1594
|
+
var PACKAGE_VERSION = "1.2.0" ;
|
|
1497
1595
|
|
|
1498
1596
|
// src/storage/receipts/redact.ts
|
|
1499
1597
|
var SECRET_KEY_PATTERN = /private[-_ ]?key|secret|password|mnemonic|seed[-_ ]?phrase|api[-_ ]?key|authoriz(e|ation)|^auth$|auth[-_]?header|payment[-_ ]?proof|^proof$|x-payment|signature|signed[-_ ]?message|token|bearer|credential|cookie|session|jwt/i;
|
|
@@ -2246,7 +2344,7 @@ function maskMiddle(value, keep = 6) {
|
|
|
2246
2344
|
}
|
|
2247
2345
|
createRequire(import.meta.url);
|
|
2248
2346
|
function readVersionReport() {
|
|
2249
|
-
const injected = '{"cliVersion":"1.
|
|
2347
|
+
const injected = '{"cliVersion":"1.2.0","pinned":[{"name":"@coinbase/x402","version":"2.1.0","via":"@devlab.group/agent-commerce"},{"name":"@modelcontextprotocol/sdk","version":"1.30.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/core","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"@x402/evm","version":"2.23.0","via":"@devlab.group/agent-commerce"},{"name":"viem","version":"2.55.18","via":"@devlab.group/agent-commerce"},{"name":"better-sqlite3","version":"13.0.3","via":"@devlab.group/agent-commerce"},{"name":"fastify","version":"5.12.0","via":"@devlab.group/agent-commerce"},{"name":"pino","version":"10.3.1","via":"@devlab.group/agent-commerce"},{"name":"zod","version":"3.25.76","via":"@devlab.group/agent-commerce"}]}' ;
|
|
2250
2348
|
{
|
|
2251
2349
|
return JSON.parse(injected);
|
|
2252
2350
|
}
|
|
@@ -2552,6 +2650,1109 @@ function printDoctorReport(report, io, json) {
|
|
|
2552
2650
|
io.stdout(`Score: ${report.score.passed}/${report.score.total} checks passed`);
|
|
2553
2651
|
}
|
|
2554
2652
|
|
|
2653
|
+
// src/cli/commands/import-openapi.ts
|
|
2654
|
+
init_core();
|
|
2655
|
+
|
|
2656
|
+
// src/openapi/discover.ts
|
|
2657
|
+
init_errors();
|
|
2658
|
+
init_execution();
|
|
2659
|
+
|
|
2660
|
+
// src/openapi/refs.ts
|
|
2661
|
+
init_errors();
|
|
2662
|
+
var MAX_REF_DEPTH = 100;
|
|
2663
|
+
function isRefNode(value) {
|
|
2664
|
+
return typeof value === "object" && value !== null && typeof value.$ref === "string";
|
|
2665
|
+
}
|
|
2666
|
+
function dereference(document, node, stack = []) {
|
|
2667
|
+
let current = node;
|
|
2668
|
+
let chain = stack;
|
|
2669
|
+
while (isRefNode(current)) {
|
|
2670
|
+
const pointer = current.$ref;
|
|
2671
|
+
if (!pointer.startsWith("#")) {
|
|
2672
|
+
throw new CommerceError(
|
|
2673
|
+
"CONFIG_INVALID",
|
|
2674
|
+
`OpenAPI document contains an external reference "${pointer}". Only internal references (#/...) are supported`,
|
|
2675
|
+
{ details: { ref: pointer } }
|
|
2676
|
+
);
|
|
2677
|
+
}
|
|
2678
|
+
if (chain.includes(pointer)) {
|
|
2679
|
+
throw new CommerceError(
|
|
2680
|
+
"CONFIG_INVALID",
|
|
2681
|
+
`OpenAPI reference "${pointer}" is circular (${[...chain, pointer].join(" -> ")})`,
|
|
2682
|
+
{ details: { ref: pointer, chain: [...chain, pointer] } }
|
|
2683
|
+
);
|
|
2684
|
+
}
|
|
2685
|
+
if (chain.length >= MAX_REF_DEPTH) {
|
|
2686
|
+
throw new CommerceError(
|
|
2687
|
+
"CONFIG_INVALID",
|
|
2688
|
+
`OpenAPI reference chain exceeds ${MAX_REF_DEPTH} hops at "${pointer}"`,
|
|
2689
|
+
{ details: { ref: pointer } }
|
|
2690
|
+
);
|
|
2691
|
+
}
|
|
2692
|
+
current = resolvePointer(document, pointer);
|
|
2693
|
+
chain = [...chain, pointer];
|
|
2694
|
+
}
|
|
2695
|
+
return { value: current, stack: chain };
|
|
2696
|
+
}
|
|
2697
|
+
function resolvePointer(document, ref) {
|
|
2698
|
+
const pointer = ref.slice(1);
|
|
2699
|
+
if (pointer === "" || pointer === "/") return document;
|
|
2700
|
+
if (!pointer.startsWith("/")) {
|
|
2701
|
+
throw new CommerceError(
|
|
2702
|
+
"CONFIG_INVALID",
|
|
2703
|
+
`OpenAPI reference "${ref}" is not a JSON Pointer. Plain names and anchors are not supported`,
|
|
2704
|
+
{ details: { ref } }
|
|
2705
|
+
);
|
|
2706
|
+
}
|
|
2707
|
+
let current = document;
|
|
2708
|
+
for (const rawSegment of pointer.slice(1).split("/")) {
|
|
2709
|
+
const segment = decodeURIComponent(rawSegment).replaceAll("~1", "/").replaceAll("~0", "~");
|
|
2710
|
+
if (Array.isArray(current)) {
|
|
2711
|
+
const index = Number(segment);
|
|
2712
|
+
current = Number.isInteger(index) ? current[index] : void 0;
|
|
2713
|
+
} else if (typeof current === "object" && current !== null) {
|
|
2714
|
+
current = current[segment];
|
|
2715
|
+
} else {
|
|
2716
|
+
current = void 0;
|
|
2717
|
+
}
|
|
2718
|
+
if (current === void 0) {
|
|
2719
|
+
throw new CommerceError(
|
|
2720
|
+
"CONFIG_INVALID",
|
|
2721
|
+
`OpenAPI reference "${ref}" does not resolve to anything in the document`,
|
|
2722
|
+
{ details: { ref } }
|
|
2723
|
+
);
|
|
2724
|
+
}
|
|
2725
|
+
}
|
|
2726
|
+
return current;
|
|
2727
|
+
}
|
|
2728
|
+
|
|
2729
|
+
// src/openapi/discover.ts
|
|
2730
|
+
var SUPPORTED_METHODS = {
|
|
2731
|
+
get: "GET",
|
|
2732
|
+
post: "POST",
|
|
2733
|
+
put: "PUT",
|
|
2734
|
+
patch: "PATCH",
|
|
2735
|
+
delete: "DELETE"
|
|
2736
|
+
};
|
|
2737
|
+
var NON_OPERATION_KEYS = /* @__PURE__ */ new Set(["summary", "description", "servers", "parameters", "$ref"]);
|
|
2738
|
+
var ID_ALLOWED = /[^A-Za-z0-9_.-]+/g;
|
|
2739
|
+
var MAX_ID_LENGTH = 128;
|
|
2740
|
+
function discoverOperations(loaded, options = {}) {
|
|
2741
|
+
const { document } = loaded;
|
|
2742
|
+
const diagnostics = [];
|
|
2743
|
+
const operations = [];
|
|
2744
|
+
const claimed = /* @__PURE__ */ new Map();
|
|
2745
|
+
if (options.baseUrl !== void 0) assertAbsoluteHttpUrl(options.baseUrl, "--base-url");
|
|
2746
|
+
const paths = document["paths"];
|
|
2747
|
+
if (!isRecord(paths)) {
|
|
2748
|
+
return { operations, diagnostics };
|
|
2749
|
+
}
|
|
2750
|
+
const rootServers = document["servers"];
|
|
2751
|
+
const rootSecurity = document["security"];
|
|
2752
|
+
for (const [path2, rawPathItem] of Object.entries(paths)) {
|
|
2753
|
+
if (path2.startsWith("x-")) continue;
|
|
2754
|
+
if (!path2.startsWith("/")) {
|
|
2755
|
+
diagnostics.push({
|
|
2756
|
+
severity: "warning",
|
|
2757
|
+
code: "invalid-path-key",
|
|
2758
|
+
operation: path2,
|
|
2759
|
+
message: `Skipped "${path2}": a Paths key must start with "/"`
|
|
2760
|
+
});
|
|
2761
|
+
continue;
|
|
2762
|
+
}
|
|
2763
|
+
const pathItem = dereference(document, rawPathItem).value;
|
|
2764
|
+
if (!isRecord(pathItem)) continue;
|
|
2765
|
+
for (const [key, rawOperation] of Object.entries(pathItem)) {
|
|
2766
|
+
if (NON_OPERATION_KEYS.has(key) || key.startsWith("x-")) continue;
|
|
2767
|
+
const method = SUPPORTED_METHODS[key.toLowerCase()];
|
|
2768
|
+
if (method === void 0) {
|
|
2769
|
+
diagnostics.push({
|
|
2770
|
+
severity: "warning",
|
|
2771
|
+
code: "unsupported-method",
|
|
2772
|
+
operation: `${key.toUpperCase()} ${path2}`,
|
|
2773
|
+
message: `Skipped ${key.toUpperCase()} ${path2}: only GET, POST, PUT, PATCH and DELETE are supported`
|
|
2774
|
+
});
|
|
2775
|
+
continue;
|
|
2776
|
+
}
|
|
2777
|
+
const operation = dereference(document, rawOperation).value;
|
|
2778
|
+
if (!isRecord(operation)) continue;
|
|
2779
|
+
const where = `${method} ${path2}`;
|
|
2780
|
+
const operationId = typeof operation["operationId"] === "string" ? operation["operationId"] : void 0;
|
|
2781
|
+
const resourceId = toResourceId(operationId, method, path2);
|
|
2782
|
+
const previous = claimed.get(resourceId);
|
|
2783
|
+
if (previous !== void 0) {
|
|
2784
|
+
throw new CommerceError(
|
|
2785
|
+
"CONFIG_INVALID",
|
|
2786
|
+
`Operations "${previous}" and "${where}" both produce the resource id "${resourceId}". Give one of them a distinct operationId - ids are what agents discover, so the importer will not rename either`,
|
|
2787
|
+
{ details: { resourceId, operations: [previous, where] } }
|
|
2788
|
+
);
|
|
2789
|
+
}
|
|
2790
|
+
claimed.set(resourceId, where);
|
|
2791
|
+
const server = selectServer(
|
|
2792
|
+
document,
|
|
2793
|
+
[operation["servers"], pathItem["servers"], rootServers],
|
|
2794
|
+
{ where, resourceId },
|
|
2795
|
+
options,
|
|
2796
|
+
diagnostics
|
|
2797
|
+
);
|
|
2798
|
+
if (server === void 0) continue;
|
|
2799
|
+
const backendUrl = `${server.replace(/\/+$/, "")}${path2}`;
|
|
2800
|
+
const stray = findUnparsedBraceToken(backendUrl);
|
|
2801
|
+
if (stray !== void 0) {
|
|
2802
|
+
diagnostics.push({
|
|
2803
|
+
severity: "error",
|
|
2804
|
+
code: "unsupported-path-template",
|
|
2805
|
+
operation: resourceId,
|
|
2806
|
+
message: `Skipped ${where}: path template "${stray}" uses characters the gateway cannot substitute (allowed: A-Z a-z 0-9 _ . -)`
|
|
2807
|
+
});
|
|
2808
|
+
continue;
|
|
2809
|
+
}
|
|
2810
|
+
const summary = typeof operation["summary"] === "string" ? operation["summary"] : void 0;
|
|
2811
|
+
const description = typeof operation["description"] === "string" ? operation["description"] : summary;
|
|
2812
|
+
const security = operation["security"] ?? rootSecurity;
|
|
2813
|
+
operations.push({
|
|
2814
|
+
resourceId,
|
|
2815
|
+
method,
|
|
2816
|
+
path: path2,
|
|
2817
|
+
backendUrl,
|
|
2818
|
+
...operationId !== void 0 ? { operationId } : {},
|
|
2819
|
+
tags: toArray(operation["tags"]).filter((tag) => typeof tag === "string"),
|
|
2820
|
+
name: summary ?? operationId ?? resourceId,
|
|
2821
|
+
...description !== void 0 ? { description } : {},
|
|
2822
|
+
parameters: [
|
|
2823
|
+
...toArray(pathItem["parameters"]),
|
|
2824
|
+
// Operation parameters last: Phase 4 lets the later one win, which is
|
|
2825
|
+
// the OpenAPI override rule (same name + in).
|
|
2826
|
+
...toArray(operation["parameters"])
|
|
2827
|
+
],
|
|
2828
|
+
...operation["requestBody"] !== void 0 ? { requestBody: operation["requestBody"] } : {},
|
|
2829
|
+
...operation["responses"] !== void 0 ? { responses: operation["responses"] } : {},
|
|
2830
|
+
security: toArray(security)
|
|
2831
|
+
});
|
|
2832
|
+
}
|
|
2833
|
+
}
|
|
2834
|
+
return { operations, diagnostics };
|
|
2835
|
+
}
|
|
2836
|
+
function toResourceId(operationId, method, path2) {
|
|
2837
|
+
const fromOperationId = operationId === void 0 ? "" : normaliseId(operationId);
|
|
2838
|
+
if (fromOperationId !== "") return fromOperationId;
|
|
2839
|
+
return normaliseId(`${method.toLowerCase()}_${path2}`);
|
|
2840
|
+
}
|
|
2841
|
+
function normaliseId(value) {
|
|
2842
|
+
return value.replace(ID_ALLOWED, "_").replace(/_+/g, "_").replace(/^[_.-]+|[_.-]+$/g, "").slice(0, MAX_ID_LENGTH);
|
|
2843
|
+
}
|
|
2844
|
+
function selectServer(document, candidates, context, options, diagnostics) {
|
|
2845
|
+
if (options.baseUrl !== void 0) return options.baseUrl;
|
|
2846
|
+
for (const candidate of candidates) {
|
|
2847
|
+
const servers = toArray(candidate);
|
|
2848
|
+
const first = servers.length > 0 ? dereference(document, servers[0]).value : void 0;
|
|
2849
|
+
if (!isRecord(first)) continue;
|
|
2850
|
+
const url = first["url"];
|
|
2851
|
+
if (typeof url !== "string" || url === "") continue;
|
|
2852
|
+
const substituted = substituteServerVariables(url, first["variables"], context, diagnostics);
|
|
2853
|
+
if (substituted === void 0) return void 0;
|
|
2854
|
+
if (!isAbsoluteHttpUrl(substituted)) {
|
|
2855
|
+
diagnostics.push({
|
|
2856
|
+
severity: "error",
|
|
2857
|
+
code: "relative-server-url",
|
|
2858
|
+
operation: context.resourceId,
|
|
2859
|
+
message: `Skipped ${context.where}: server URL "${substituted}" is relative, so no backend host is known. Pass --base-url`
|
|
2860
|
+
});
|
|
2861
|
+
return void 0;
|
|
2862
|
+
}
|
|
2863
|
+
return substituted;
|
|
2864
|
+
}
|
|
2865
|
+
diagnostics.push({
|
|
2866
|
+
severity: "error",
|
|
2867
|
+
code: "no-server-url",
|
|
2868
|
+
operation: context.resourceId,
|
|
2869
|
+
message: `Skipped ${context.where}: the document declares no server URL. Pass --base-url`
|
|
2870
|
+
});
|
|
2871
|
+
return void 0;
|
|
2872
|
+
}
|
|
2873
|
+
function substituteServerVariables(url, variables, context, diagnostics) {
|
|
2874
|
+
if (!url.includes("{")) return url;
|
|
2875
|
+
const declared = isRecord(variables) ? variables : {};
|
|
2876
|
+
const used = [];
|
|
2877
|
+
let missing;
|
|
2878
|
+
const substituted = url.replace(/\{([^{}]*)\}/g, (match, name) => {
|
|
2879
|
+
const variable = declared[name];
|
|
2880
|
+
const fallback = isRecord(variable) ? variable["default"] : void 0;
|
|
2881
|
+
if (typeof fallback !== "string") {
|
|
2882
|
+
missing = name;
|
|
2883
|
+
return match;
|
|
2884
|
+
}
|
|
2885
|
+
used.push(`${name}=${fallback}`);
|
|
2886
|
+
return fallback;
|
|
2887
|
+
});
|
|
2888
|
+
if (missing !== void 0) {
|
|
2889
|
+
diagnostics.push({
|
|
2890
|
+
severity: "error",
|
|
2891
|
+
code: "server-variable-without-default",
|
|
2892
|
+
operation: context.resourceId,
|
|
2893
|
+
message: `Skipped ${context.where}: server variable "{${missing}}" has no default value. Pass --base-url`
|
|
2894
|
+
});
|
|
2895
|
+
return void 0;
|
|
2896
|
+
}
|
|
2897
|
+
if (used.length > 0) {
|
|
2898
|
+
diagnostics.push({
|
|
2899
|
+
severity: "warning",
|
|
2900
|
+
code: "server-variable-default",
|
|
2901
|
+
operation: context.resourceId,
|
|
2902
|
+
message: `Server URL for ${context.where} uses declared defaults (${used.join(", ")}); override with --base-url if that is not the deployment you mean`
|
|
2903
|
+
});
|
|
2904
|
+
}
|
|
2905
|
+
return substituted;
|
|
2906
|
+
}
|
|
2907
|
+
function isAbsoluteHttpUrl(value) {
|
|
2908
|
+
try {
|
|
2909
|
+
const parsed = new URL(value);
|
|
2910
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return false;
|
|
2911
|
+
return parsed.search === "" && parsed.hash === "";
|
|
2912
|
+
} catch {
|
|
2913
|
+
return false;
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
function assertAbsoluteHttpUrl(value, label) {
|
|
2917
|
+
if (!isAbsoluteHttpUrl(value)) {
|
|
2918
|
+
throw new CommerceError(
|
|
2919
|
+
"CONFIG_INVALID",
|
|
2920
|
+
`${label} "${value}" must be an absolute http:// or https:// URL with no query string or fragment`,
|
|
2921
|
+
{ details: { value } }
|
|
2922
|
+
);
|
|
2923
|
+
}
|
|
2924
|
+
}
|
|
2925
|
+
function isRecord(value) {
|
|
2926
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2927
|
+
}
|
|
2928
|
+
function toArray(value) {
|
|
2929
|
+
return Array.isArray(value) ? value : [];
|
|
2930
|
+
}
|
|
2931
|
+
|
|
2932
|
+
// src/openapi/request.ts
|
|
2933
|
+
init_execution();
|
|
2934
|
+
|
|
2935
|
+
// src/openapi/schema.ts
|
|
2936
|
+
init_errors();
|
|
2937
|
+
var SUPPORTED_TYPES = /* @__PURE__ */ new Set([
|
|
2938
|
+
"object",
|
|
2939
|
+
"string",
|
|
2940
|
+
"number",
|
|
2941
|
+
"integer",
|
|
2942
|
+
"boolean",
|
|
2943
|
+
"array",
|
|
2944
|
+
"null"
|
|
2945
|
+
]);
|
|
2946
|
+
var METADATA_KEYWORDS = ["title", "description", "default", "example", "examples", "deprecated"];
|
|
2947
|
+
var ANNOTATION_KEYWORDS = /* @__PURE__ */ new Set([
|
|
2948
|
+
"xml",
|
|
2949
|
+
"externalDocs",
|
|
2950
|
+
"readOnly",
|
|
2951
|
+
"writeOnly",
|
|
2952
|
+
"$schema",
|
|
2953
|
+
"$id",
|
|
2954
|
+
"$comment",
|
|
2955
|
+
"$defs",
|
|
2956
|
+
"definitions"
|
|
2957
|
+
]);
|
|
2958
|
+
function isPrimitiveSchema(schema) {
|
|
2959
|
+
const types = typeList2(schema["type"]);
|
|
2960
|
+
if (types === void 0) return false;
|
|
2961
|
+
return types.every((type) => type !== "object" && type !== "array" && SUPPORTED_TYPES.has(type));
|
|
2962
|
+
}
|
|
2963
|
+
function convertSchema(document, node, stack = []) {
|
|
2964
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
2965
|
+
try {
|
|
2966
|
+
const schema = convertNode(document, node, stack, dropped);
|
|
2967
|
+
return schema === void 0 ? { supported: false, reason: "schema could not be represented" } : { supported: true, schema, dropped: [...dropped] };
|
|
2968
|
+
} catch (error) {
|
|
2969
|
+
if (error instanceof UnsupportedSchema) return { supported: false, reason: error.message };
|
|
2970
|
+
if (isCommerceError(error)) return { supported: false, reason: error.message };
|
|
2971
|
+
throw error;
|
|
2972
|
+
}
|
|
2973
|
+
}
|
|
2974
|
+
var UnsupportedSchema = class extends Error {
|
|
2975
|
+
};
|
|
2976
|
+
function convertNode(document, node, stack, dropped) {
|
|
2977
|
+
if (node === true) return {};
|
|
2978
|
+
if (node === false) throw new UnsupportedSchema("schema is `false`, which accepts no value");
|
|
2979
|
+
const resolved = dereference(document, node, stack);
|
|
2980
|
+
const source = resolved.value;
|
|
2981
|
+
if (typeof source !== "object" || source === null || Array.isArray(source)) {
|
|
2982
|
+
throw new UnsupportedSchema("schema node is not an object");
|
|
2983
|
+
}
|
|
2984
|
+
const schemaNode = source;
|
|
2985
|
+
if (Array.isArray(schemaNode["allOf"])) {
|
|
2986
|
+
return mergeAllOf(document, schemaNode, resolved.stack, dropped);
|
|
2987
|
+
}
|
|
2988
|
+
for (const keyword of ["oneOf", "anyOf", "not", "discriminator"]) {
|
|
2989
|
+
if (Object.hasOwn(schemaNode, keyword)) {
|
|
2990
|
+
throw new UnsupportedSchema(
|
|
2991
|
+
`schema uses "${keyword}", which this gateway cannot enforce - accepting it would advertise validation that never runs`
|
|
2992
|
+
);
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
const result = {};
|
|
2996
|
+
const types = resolveTypes(schemaNode);
|
|
2997
|
+
if (types !== void 0) result["type"] = types.length === 1 ? types[0] : types;
|
|
2998
|
+
for (const keyword of METADATA_KEYWORDS) {
|
|
2999
|
+
if (Object.hasOwn(schemaNode, keyword)) result[keyword] = schemaNode[keyword];
|
|
3000
|
+
}
|
|
3001
|
+
if (Array.isArray(schemaNode["enum"])) result["enum"] = [...schemaNode["enum"]];
|
|
3002
|
+
const properties = schemaNode["properties"];
|
|
3003
|
+
if (isRecord2(properties)) {
|
|
3004
|
+
const converted = {};
|
|
3005
|
+
for (const [name, sub] of Object.entries(properties)) {
|
|
3006
|
+
const child = convertNode(document, sub, resolved.stack, dropped);
|
|
3007
|
+
if (child !== void 0) converted[name] = child;
|
|
3008
|
+
}
|
|
3009
|
+
result["properties"] = converted;
|
|
3010
|
+
}
|
|
3011
|
+
const required = schemaNode["required"];
|
|
3012
|
+
if (Array.isArray(required)) {
|
|
3013
|
+
const names = required.filter((name) => typeof name === "string");
|
|
3014
|
+
if (names.length > 0) result["required"] = names;
|
|
3015
|
+
}
|
|
3016
|
+
const additional = schemaNode["additionalProperties"];
|
|
3017
|
+
if (typeof additional === "boolean") {
|
|
3018
|
+
result["additionalProperties"] = additional;
|
|
3019
|
+
} else if (additional !== void 0) {
|
|
3020
|
+
const child = convertNode(document, additional, resolved.stack, dropped);
|
|
3021
|
+
if (child !== void 0) result["additionalProperties"] = child;
|
|
3022
|
+
} else if (isRecord2(properties)) {
|
|
3023
|
+
result["additionalProperties"] = false;
|
|
3024
|
+
}
|
|
3025
|
+
const items = schemaNode["items"];
|
|
3026
|
+
if (Array.isArray(items)) {
|
|
3027
|
+
dropped.add("items (tuple form)");
|
|
3028
|
+
} else if (items !== void 0) {
|
|
3029
|
+
const child = convertNode(document, items, resolved.stack, dropped);
|
|
3030
|
+
if (child !== void 0) result["items"] = child;
|
|
3031
|
+
}
|
|
3032
|
+
for (const keyword of Object.keys(schemaNode)) {
|
|
3033
|
+
if (isCarriedKeyword(keyword) || ANNOTATION_KEYWORDS.has(keyword) || keyword.startsWith("x-")) {
|
|
3034
|
+
continue;
|
|
3035
|
+
}
|
|
3036
|
+
dropped.add(keyword);
|
|
3037
|
+
}
|
|
3038
|
+
return result;
|
|
3039
|
+
}
|
|
3040
|
+
function isCarriedKeyword(keyword) {
|
|
3041
|
+
return keyword === "type" || keyword === "properties" || keyword === "required" || keyword === "additionalProperties" || keyword === "enum" || keyword === "items" || keyword === "nullable" || keyword === "$ref" || METADATA_KEYWORDS.includes(keyword);
|
|
3042
|
+
}
|
|
3043
|
+
function resolveTypes(schema) {
|
|
3044
|
+
const declared = typeList2(schema["type"]);
|
|
3045
|
+
if (declared === void 0) return void 0;
|
|
3046
|
+
const known = declared.filter((type) => SUPPORTED_TYPES.has(type));
|
|
3047
|
+
if (known.length === 0) {
|
|
3048
|
+
throw new UnsupportedSchema(`schema type "${String(schema["type"])}" is not a JSON type`);
|
|
3049
|
+
}
|
|
3050
|
+
if (schema["nullable"] === true && !known.includes("null")) known.push("null");
|
|
3051
|
+
return known;
|
|
3052
|
+
}
|
|
3053
|
+
function typeList2(raw) {
|
|
3054
|
+
if (typeof raw === "string") return [raw];
|
|
3055
|
+
if (Array.isArray(raw) && raw.every((entry) => typeof entry === "string")) {
|
|
3056
|
+
return raw;
|
|
3057
|
+
}
|
|
3058
|
+
return void 0;
|
|
3059
|
+
}
|
|
3060
|
+
function mergeAllOf(document, schema, stack, dropped) {
|
|
3061
|
+
const branches = schema["allOf"];
|
|
3062
|
+
const siblings = { ...schema };
|
|
3063
|
+
delete siblings["allOf"];
|
|
3064
|
+
const merged = { type: "object", properties: {}, required: [] };
|
|
3065
|
+
const properties = merged["properties"];
|
|
3066
|
+
const required = /* @__PURE__ */ new Set();
|
|
3067
|
+
const parts = [...branches, ...Object.keys(siblings).length > 0 ? [siblings] : []];
|
|
3068
|
+
for (const branch of parts) {
|
|
3069
|
+
const converted = convertNode(document, branch, stack, dropped);
|
|
3070
|
+
if (converted === void 0) throw new UnsupportedSchema("allOf branch is empty");
|
|
3071
|
+
const types = typeList2(converted["type"]);
|
|
3072
|
+
if (types !== void 0 && !types.includes("object")) {
|
|
3073
|
+
throw new UnsupportedSchema("allOf mixes object and non-object schemas");
|
|
3074
|
+
}
|
|
3075
|
+
const branchProperties = converted["properties"];
|
|
3076
|
+
if (isRecord2(branchProperties)) {
|
|
3077
|
+
for (const [name, sub] of Object.entries(branchProperties)) {
|
|
3078
|
+
const existing = properties[name];
|
|
3079
|
+
if (existing !== void 0 && JSON.stringify(existing) !== JSON.stringify(sub)) {
|
|
3080
|
+
throw new UnsupportedSchema(
|
|
3081
|
+
`allOf branches declare property "${name}" differently, which cannot be merged`
|
|
3082
|
+
);
|
|
3083
|
+
}
|
|
3084
|
+
properties[name] = sub;
|
|
3085
|
+
}
|
|
3086
|
+
}
|
|
3087
|
+
for (const name of Array.isArray(converted["required"]) ? converted["required"] : []) {
|
|
3088
|
+
if (typeof name === "string") required.add(name);
|
|
3089
|
+
}
|
|
3090
|
+
for (const keyword of ["enum", "items"]) {
|
|
3091
|
+
if (Object.hasOwn(converted, keyword)) {
|
|
3092
|
+
throw new UnsupportedSchema(`allOf branch uses "${keyword}", which cannot be merged`);
|
|
3093
|
+
}
|
|
3094
|
+
}
|
|
3095
|
+
if (typeof converted["description"] === "string" && merged["description"] === void 0) {
|
|
3096
|
+
merged["description"] = converted["description"];
|
|
3097
|
+
}
|
|
3098
|
+
}
|
|
3099
|
+
if (required.size > 0) merged["required"] = [...required];
|
|
3100
|
+
else delete merged["required"];
|
|
3101
|
+
merged["additionalProperties"] = false;
|
|
3102
|
+
return merged;
|
|
3103
|
+
}
|
|
3104
|
+
function isRecord2(value) {
|
|
3105
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
// src/openapi/request.ts
|
|
3109
|
+
var PATH_GROUP = "path";
|
|
3110
|
+
var QUERY_GROUP = "query";
|
|
3111
|
+
var BODY_GROUP = "body";
|
|
3112
|
+
var IGNORED_HEADER_NAMES = /* @__PURE__ */ new Set(["accept", "content-type", "authorization"]);
|
|
3113
|
+
var UNSUPPORTED_QUERY_STYLES = /* @__PURE__ */ new Set(["deepObject", "spaceDelimited", "pipeDelimited"]);
|
|
3114
|
+
function mapRequest(loaded, candidate) {
|
|
3115
|
+
const { document } = loaded;
|
|
3116
|
+
const diagnostics = [];
|
|
3117
|
+
const dropped = /* @__PURE__ */ new Set();
|
|
3118
|
+
const operation = candidate.resourceId;
|
|
3119
|
+
const warn = (code, message) => {
|
|
3120
|
+
diagnostics.push({ severity: "warning", code, operation, message });
|
|
3121
|
+
};
|
|
3122
|
+
const skip = (code, message) => {
|
|
3123
|
+
diagnostics.push({
|
|
3124
|
+
severity: "error",
|
|
3125
|
+
code,
|
|
3126
|
+
operation,
|
|
3127
|
+
message: `Skipped ${candidate.method} ${candidate.path}: ${message}`
|
|
3128
|
+
});
|
|
3129
|
+
return { supported: false, diagnostics };
|
|
3130
|
+
};
|
|
3131
|
+
const pathProperties = {};
|
|
3132
|
+
const queryProperties = {};
|
|
3133
|
+
const requiredQuery = [];
|
|
3134
|
+
const templateParams = new Set(extractPathParameterNames(candidate.path));
|
|
3135
|
+
for (const parameter of mergeParameters(document, candidate.parameters)) {
|
|
3136
|
+
const name = typeof parameter["name"] === "string" ? parameter["name"] : void 0;
|
|
3137
|
+
const location = typeof parameter["in"] === "string" ? parameter["in"] : void 0;
|
|
3138
|
+
if (name === void 0 || location === void 0) continue;
|
|
3139
|
+
const required2 = parameter["required"] === true || location === "path";
|
|
3140
|
+
const label = `${location} parameter "${name}"`;
|
|
3141
|
+
if (location === "header" || location === "cookie") {
|
|
3142
|
+
if (location === "header" && IGNORED_HEADER_NAMES.has(name.toLowerCase())) continue;
|
|
3143
|
+
if (required2) {
|
|
3144
|
+
return skip(
|
|
3145
|
+
"unsupported-required-parameter",
|
|
3146
|
+
`${label} is required, and ${location} parameters are operator configuration in this release, not agent input`
|
|
3147
|
+
);
|
|
3148
|
+
}
|
|
3149
|
+
warn(
|
|
3150
|
+
"unsupported-optional-parameter",
|
|
3151
|
+
`Omitted optional ${label}: ${location} parameters are not imported. Configure it under backend.headers if the API needs it`
|
|
3152
|
+
);
|
|
3153
|
+
continue;
|
|
3154
|
+
}
|
|
3155
|
+
if (location !== "path" && location !== "query") {
|
|
3156
|
+
if (required2) return skip("unsupported-required-parameter", `${label} has unknown location`);
|
|
3157
|
+
warn("unsupported-optional-parameter", `Omitted optional ${label}: unknown location`);
|
|
3158
|
+
continue;
|
|
3159
|
+
}
|
|
3160
|
+
const unsupported = describeUnsupportedParameter(document, parameter, location);
|
|
3161
|
+
if (unsupported !== void 0) {
|
|
3162
|
+
if (required2) return skip("unsupported-required-parameter", `${label} ${unsupported}`);
|
|
3163
|
+
warn("unsupported-optional-parameter", `Omitted optional ${label}: it ${unsupported}`);
|
|
3164
|
+
continue;
|
|
3165
|
+
}
|
|
3166
|
+
const converted = convertSchema(document, parameter["schema"]);
|
|
3167
|
+
if (!converted.supported) {
|
|
3168
|
+
if (required2) return skip("unsupported-required-parameter", `${label} ${converted.reason}`);
|
|
3169
|
+
warn("unsupported-optional-parameter", `Omitted optional ${label}: ${converted.reason}`);
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
for (const keyword of converted.dropped) dropped.add(keyword);
|
|
3173
|
+
if (location === "path") {
|
|
3174
|
+
if (!templateParams.has(name)) {
|
|
3175
|
+
warn(
|
|
3176
|
+
"path-parameter-not-in-template",
|
|
3177
|
+
`Ignored path parameter "${name}": it does not appear in "${candidate.path}"`
|
|
3178
|
+
);
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
pathProperties[name] = converted.schema;
|
|
3182
|
+
} else {
|
|
3183
|
+
queryProperties[name] = converted.schema;
|
|
3184
|
+
if (required2) requiredQuery.push(name);
|
|
3185
|
+
}
|
|
3186
|
+
}
|
|
3187
|
+
for (const param of templateParams) {
|
|
3188
|
+
if (!Object.hasOwn(pathProperties, param)) {
|
|
3189
|
+
return skip(
|
|
3190
|
+
"undeclared-path-parameter",
|
|
3191
|
+
`"{${param}}" appears in the path but is not declared as a path parameter, so a caller could never supply it`
|
|
3192
|
+
);
|
|
3193
|
+
}
|
|
3194
|
+
}
|
|
3195
|
+
const body = resolveBody(document, candidate);
|
|
3196
|
+
if (body.kind === "unsupported") {
|
|
3197
|
+
if (body.required) return skip("unsupported-request-body", body.reason);
|
|
3198
|
+
warn("unsupported-request-body", `Omitted the request body: ${body.reason}`);
|
|
3199
|
+
}
|
|
3200
|
+
if (body.kind === "schema") {
|
|
3201
|
+
for (const keyword of body.dropped) dropped.add(keyword);
|
|
3202
|
+
}
|
|
3203
|
+
const properties = {};
|
|
3204
|
+
const required = [];
|
|
3205
|
+
const bindings = {};
|
|
3206
|
+
if (Object.keys(pathProperties).length > 0) {
|
|
3207
|
+
properties[PATH_GROUP] = closedObject(pathProperties, Object.keys(pathProperties));
|
|
3208
|
+
required.push(PATH_GROUP);
|
|
3209
|
+
bindings.path = PATH_GROUP;
|
|
3210
|
+
}
|
|
3211
|
+
if (Object.keys(queryProperties).length > 0) {
|
|
3212
|
+
properties[QUERY_GROUP] = closedObject(queryProperties, requiredQuery);
|
|
3213
|
+
if (requiredQuery.length > 0) required.push(QUERY_GROUP);
|
|
3214
|
+
bindings.query = QUERY_GROUP;
|
|
3215
|
+
}
|
|
3216
|
+
if (body.kind === "schema") {
|
|
3217
|
+
properties[BODY_GROUP] = body.schema;
|
|
3218
|
+
if (body.required) required.push(BODY_GROUP);
|
|
3219
|
+
bindings.body = BODY_GROUP;
|
|
3220
|
+
}
|
|
3221
|
+
return {
|
|
3222
|
+
supported: true,
|
|
3223
|
+
inputSchema: {
|
|
3224
|
+
type: "object",
|
|
3225
|
+
properties,
|
|
3226
|
+
...required.length > 0 ? { required } : {},
|
|
3227
|
+
additionalProperties: false
|
|
3228
|
+
},
|
|
3229
|
+
inputBindings: bindings,
|
|
3230
|
+
...body.kind === "schema" && body.contentType !== void 0 ? { contentType: body.contentType } : {},
|
|
3231
|
+
droppedKeywords: [...dropped],
|
|
3232
|
+
diagnostics
|
|
3233
|
+
};
|
|
3234
|
+
}
|
|
3235
|
+
function mergeParameters(document, parameters) {
|
|
3236
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
3237
|
+
for (const raw of parameters) {
|
|
3238
|
+
const resolved = dereference(document, raw).value;
|
|
3239
|
+
if (typeof resolved !== "object" || resolved === null || Array.isArray(resolved)) continue;
|
|
3240
|
+
const parameter = resolved;
|
|
3241
|
+
byIdentity.set(`${String(parameter["in"])}:${String(parameter["name"])}`, parameter);
|
|
3242
|
+
}
|
|
3243
|
+
return [...byIdentity.values()];
|
|
3244
|
+
}
|
|
3245
|
+
function describeUnsupportedParameter(document, parameter, location) {
|
|
3246
|
+
if (parameter["content"] !== void 0) {
|
|
3247
|
+
return "uses the `content` form, whose media-type serialization the gateway does not perform";
|
|
3248
|
+
}
|
|
3249
|
+
const style = parameter["style"];
|
|
3250
|
+
if (location === "query" && typeof style === "string" && UNSUPPORTED_QUERY_STYLES.has(style)) {
|
|
3251
|
+
return `uses style "${style}", which the gateway does not serialize`;
|
|
3252
|
+
}
|
|
3253
|
+
if (location === "path" && typeof style === "string" && style !== "simple") {
|
|
3254
|
+
return `uses style "${style}"; the gateway substitutes plain values only`;
|
|
3255
|
+
}
|
|
3256
|
+
const converted = convertSchema(document, parameter["schema"]);
|
|
3257
|
+
if (!converted.supported) return converted.reason;
|
|
3258
|
+
if (!isPrimitiveSchema(converted.schema)) {
|
|
3259
|
+
return "is not a primitive; object and array parameters need serialization the gateway does not perform";
|
|
3260
|
+
}
|
|
3261
|
+
return void 0;
|
|
3262
|
+
}
|
|
3263
|
+
function resolveBody(document, candidate) {
|
|
3264
|
+
if (candidate.requestBody === void 0) return { kind: "none" };
|
|
3265
|
+
const resolved = dereference(document, candidate.requestBody).value;
|
|
3266
|
+
if (typeof resolved !== "object" || resolved === null || Array.isArray(resolved)) {
|
|
3267
|
+
return { kind: "none" };
|
|
3268
|
+
}
|
|
3269
|
+
const requestBody = resolved;
|
|
3270
|
+
const required = requestBody["required"] === true;
|
|
3271
|
+
if (candidate.method === "GET" || candidate.method === "DELETE") {
|
|
3272
|
+
return {
|
|
3273
|
+
kind: "unsupported",
|
|
3274
|
+
required: false,
|
|
3275
|
+
reason: `a ${candidate.method} request body is not sent by the gateway`
|
|
3276
|
+
};
|
|
3277
|
+
}
|
|
3278
|
+
const content = requestBody["content"];
|
|
3279
|
+
if (!isRecord3(content)) return { kind: "none" };
|
|
3280
|
+
const mediaType = pickJsonMediaType(Object.keys(content));
|
|
3281
|
+
if (mediaType === void 0) {
|
|
3282
|
+
return {
|
|
3283
|
+
kind: "unsupported",
|
|
3284
|
+
required,
|
|
3285
|
+
reason: `no JSON request body content type (found: ${Object.keys(content).join(", ") || "none"}). Only application/json and application/*+json are supported - multipart and form data are never serialized as JSON`
|
|
3286
|
+
};
|
|
3287
|
+
}
|
|
3288
|
+
const media = content[mediaType];
|
|
3289
|
+
const schemaNode = isRecord3(media) ? media["schema"] : void 0;
|
|
3290
|
+
if (schemaNode === void 0) {
|
|
3291
|
+
return {
|
|
3292
|
+
kind: "schema",
|
|
3293
|
+
schema: { type: "object", additionalProperties: true },
|
|
3294
|
+
required,
|
|
3295
|
+
...mediaType === "application/json" ? {} : { contentType: mediaType },
|
|
3296
|
+
dropped: []
|
|
3297
|
+
};
|
|
3298
|
+
}
|
|
3299
|
+
const converted = convertSchema(document, schemaNode);
|
|
3300
|
+
if (!converted.supported) return { kind: "unsupported", required, reason: converted.reason };
|
|
3301
|
+
return {
|
|
3302
|
+
kind: "schema",
|
|
3303
|
+
schema: converted.schema,
|
|
3304
|
+
required,
|
|
3305
|
+
...mediaType === "application/json" ? {} : { contentType: mediaType },
|
|
3306
|
+
dropped: converted.dropped
|
|
3307
|
+
};
|
|
3308
|
+
}
|
|
3309
|
+
function pickJsonMediaType(keys) {
|
|
3310
|
+
const normalised = keys.map((key) => ({ key, type: key.split(";")[0]?.trim().toLowerCase() }));
|
|
3311
|
+
const exact = normalised.find((entry) => entry.type === "application/json");
|
|
3312
|
+
if (exact !== void 0) return exact.key;
|
|
3313
|
+
return [...normalised].sort((a, b) => (a.type ?? "").localeCompare(b.type ?? "")).find((entry) => entry.type?.startsWith("application/") && entry.type.endsWith("+json"))?.key;
|
|
3314
|
+
}
|
|
3315
|
+
function closedObject(properties, required) {
|
|
3316
|
+
return {
|
|
3317
|
+
type: "object",
|
|
3318
|
+
properties,
|
|
3319
|
+
...required.length > 0 ? { required: [...required] } : {},
|
|
3320
|
+
additionalProperties: false
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3323
|
+
function isRecord3(value) {
|
|
3324
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3325
|
+
}
|
|
3326
|
+
|
|
3327
|
+
// src/openapi/draft.ts
|
|
3328
|
+
function buildResourceDrafts(loaded, options = {}) {
|
|
3329
|
+
const discovery = discoverOperations(
|
|
3330
|
+
loaded,
|
|
3331
|
+
options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {}
|
|
3332
|
+
);
|
|
3333
|
+
const diagnostics = [...discovery.diagnostics];
|
|
3334
|
+
const drafts = [];
|
|
3335
|
+
const wanted = options.include?.operationIds;
|
|
3336
|
+
const wantedTags = options.include?.tags;
|
|
3337
|
+
const matched = /* @__PURE__ */ new Set();
|
|
3338
|
+
for (const candidate of discovery.operations) {
|
|
3339
|
+
if (wanted !== void 0 && !selects(wanted, candidate)) continue;
|
|
3340
|
+
if (wantedTags !== void 0 && !candidate.tags.some((tag) => wantedTags.includes(tag))) {
|
|
3341
|
+
continue;
|
|
3342
|
+
}
|
|
3343
|
+
if (wanted !== void 0) {
|
|
3344
|
+
for (const id of wanted) if (selects([id], candidate)) matched.add(id);
|
|
3345
|
+
}
|
|
3346
|
+
const mapping = mapRequest(loaded, candidate);
|
|
3347
|
+
diagnostics.push(...mapping.diagnostics);
|
|
3348
|
+
if (!mapping.supported) continue;
|
|
3349
|
+
const dropped = new Set(mapping.droppedKeywords);
|
|
3350
|
+
const output = selectOutputSchema(loaded.document, candidate, diagnostics, dropped);
|
|
3351
|
+
if (dropped.size > 0) {
|
|
3352
|
+
diagnostics.push({
|
|
3353
|
+
severity: "warning",
|
|
3354
|
+
code: "unenforced-schema-constraints",
|
|
3355
|
+
operation: candidate.resourceId,
|
|
3356
|
+
message: `${candidate.resourceId}: dropped ${[...dropped].join(", ")} - this gateway validates structure only, and keeping them would advertise checks it never performs`
|
|
3357
|
+
});
|
|
3358
|
+
}
|
|
3359
|
+
const review = [];
|
|
3360
|
+
if (declaresSecurity(candidate)) {
|
|
3361
|
+
diagnostics.push({
|
|
3362
|
+
severity: "warning",
|
|
3363
|
+
code: "backend-authentication-required",
|
|
3364
|
+
operation: candidate.resourceId,
|
|
3365
|
+
message: `${candidate.resourceId} declares backend authentication. OpenAPI credentials were not imported. Configure backend.headers with environment placeholders before enabling the resource`
|
|
3366
|
+
});
|
|
3367
|
+
review.push(
|
|
3368
|
+
"This operation declares backend authentication. No credential was imported.",
|
|
3369
|
+
"Add it under backend.headers with an ${ENV_VAR} placeholder before enabling."
|
|
3370
|
+
);
|
|
3371
|
+
}
|
|
3372
|
+
if (options.policy?.pricing === void 0 || options.policy.expose === void 0) {
|
|
3373
|
+
review.push(
|
|
3374
|
+
"REVIEW: pricing and exposure are not inferred from OpenAPI. Add e.g.",
|
|
3375
|
+
' pricing: { type: free } # or { type: fixed, amount: "0.01", currency: USDC }',
|
|
3376
|
+
" expose: [http] # http | mcp | a2a"
|
|
3377
|
+
);
|
|
3378
|
+
}
|
|
3379
|
+
drafts.push({
|
|
3380
|
+
id: candidate.resourceId,
|
|
3381
|
+
...candidate.operationId !== void 0 ? { operationId: candidate.operationId } : {},
|
|
3382
|
+
tags: candidate.tags,
|
|
3383
|
+
source: `${candidate.method} ${candidate.path}`,
|
|
3384
|
+
resource: {
|
|
3385
|
+
name: candidate.name,
|
|
3386
|
+
...candidate.description !== void 0 ? { description: candidate.description } : {},
|
|
3387
|
+
input: mapping.inputSchema,
|
|
3388
|
+
...output !== void 0 ? { output } : {},
|
|
3389
|
+
backend: {
|
|
3390
|
+
type: "http",
|
|
3391
|
+
method: candidate.method,
|
|
3392
|
+
url: candidate.backendUrl,
|
|
3393
|
+
...mapping.contentType !== void 0 ? { headers: { "Content-Type": mapping.contentType } } : {},
|
|
3394
|
+
...Object.keys(mapping.inputBindings).length > 0 ? { inputBindings: mapping.inputBindings } : {}
|
|
3395
|
+
},
|
|
3396
|
+
// Policy is written only when the operator asked for it. An absent
|
|
3397
|
+
// `pricing`/`expose` is what makes the draft fail config validation
|
|
3398
|
+
// until a human has decided.
|
|
3399
|
+
...options.policy?.pricing !== void 0 ? { pricing: options.policy.pricing } : {},
|
|
3400
|
+
...options.policy?.expose !== void 0 ? { expose: [...options.policy.expose] } : {},
|
|
3401
|
+
...options.policy?.payments !== void 0 ? { payments: [...options.policy.payments] } : {}
|
|
3402
|
+
},
|
|
3403
|
+
review
|
|
3404
|
+
});
|
|
3405
|
+
}
|
|
3406
|
+
return {
|
|
3407
|
+
version: loaded.version,
|
|
3408
|
+
sourcePath: loaded.sourcePath,
|
|
3409
|
+
drafts,
|
|
3410
|
+
diagnostics,
|
|
3411
|
+
unmatchedOperationIds: (wanted ?? []).filter((id) => !matched.has(id))
|
|
3412
|
+
};
|
|
3413
|
+
}
|
|
3414
|
+
function selects(ids, candidate) {
|
|
3415
|
+
return ids.includes(candidate.resourceId) || candidate.operationId !== void 0 && ids.includes(candidate.operationId);
|
|
3416
|
+
}
|
|
3417
|
+
function declaresSecurity(candidate) {
|
|
3418
|
+
return candidate.security.some(
|
|
3419
|
+
(requirement) => typeof requirement === "object" && requirement !== null && Object.keys(requirement).length > 0
|
|
3420
|
+
);
|
|
3421
|
+
}
|
|
3422
|
+
function selectOutputSchema(document, candidate, diagnostics, dropped) {
|
|
3423
|
+
const responses = candidate.responses;
|
|
3424
|
+
if (!isRecord4(responses)) return void 0;
|
|
3425
|
+
const successes = Object.keys(responses).filter((status) => /^2\d\d$/.test(status)).sort((a, b) => rank(a) - rank(b));
|
|
3426
|
+
if (successes.length === 0) return void 0;
|
|
3427
|
+
const withBody = successes.filter((status) => {
|
|
3428
|
+
const response2 = responses[status];
|
|
3429
|
+
const content2 = isRecord4(response2) ? response2["content"] : void 0;
|
|
3430
|
+
return isRecord4(content2) && pickJsonMediaType(Object.keys(content2)) !== void 0;
|
|
3431
|
+
});
|
|
3432
|
+
const chosen = withBody[0];
|
|
3433
|
+
if (chosen === void 0) return void 0;
|
|
3434
|
+
const response = responses[chosen];
|
|
3435
|
+
const content = response["content"];
|
|
3436
|
+
const mediaType = pickJsonMediaType(Object.keys(content));
|
|
3437
|
+
const media = content[mediaType];
|
|
3438
|
+
const schemaNode = isRecord4(media) ? media["schema"] : void 0;
|
|
3439
|
+
if (schemaNode === void 0) return void 0;
|
|
3440
|
+
const converted = convertSchema(document, schemaNode);
|
|
3441
|
+
if (!converted.supported) {
|
|
3442
|
+
diagnostics.push({
|
|
3443
|
+
severity: "warning",
|
|
3444
|
+
code: "unsupported-output-schema",
|
|
3445
|
+
operation: candidate.resourceId,
|
|
3446
|
+
message: `${candidate.resourceId}: omitted the output schema (${converted.reason})`
|
|
3447
|
+
});
|
|
3448
|
+
return void 0;
|
|
3449
|
+
}
|
|
3450
|
+
for (const keyword of converted.dropped) dropped.add(keyword);
|
|
3451
|
+
if (withBody.length > 1) {
|
|
3452
|
+
diagnostics.push({
|
|
3453
|
+
severity: "warning",
|
|
3454
|
+
code: "multiple-success-responses",
|
|
3455
|
+
operation: candidate.resourceId,
|
|
3456
|
+
message: `${candidate.resourceId}: responses ${withBody.join(", ")} all carry a JSON body; used ${chosen}`
|
|
3457
|
+
});
|
|
3458
|
+
}
|
|
3459
|
+
return converted.schema;
|
|
3460
|
+
}
|
|
3461
|
+
function rank(status) {
|
|
3462
|
+
const preferred = ["200", "201", "202"].indexOf(status);
|
|
3463
|
+
return preferred === -1 ? 100 + Number(status) : preferred;
|
|
3464
|
+
}
|
|
3465
|
+
function renderResourcesYaml(result) {
|
|
3466
|
+
const resources = {};
|
|
3467
|
+
for (const draft of result.drafts) resources[draft.id] = draft.resource;
|
|
3468
|
+
const doc = new Document({ resources });
|
|
3469
|
+
doc.commentBefore = [
|
|
3470
|
+
" Generated by agent-commerce import openapi. Review before use.",
|
|
3471
|
+
` Source: ${basename(result.sourcePath)} (OpenAPI ${result.version})`,
|
|
3472
|
+
" Merge the resources below into config.yaml once pricing, exposure and",
|
|
3473
|
+
" any backend authentication have been decided."
|
|
3474
|
+
].join("\n");
|
|
3475
|
+
const map = doc.getIn(["resources"], true);
|
|
3476
|
+
map?.items.forEach((item, index) => {
|
|
3477
|
+
const draft = result.drafts[index];
|
|
3478
|
+
const key = item.key;
|
|
3479
|
+
if (draft === void 0 || draft.review.length === 0 || key === null) return;
|
|
3480
|
+
key.commentBefore = draft.review.map((line) => ` ${line}`).join("\n");
|
|
3481
|
+
});
|
|
3482
|
+
return doc.toString({ lineWidth: 0 });
|
|
3483
|
+
}
|
|
3484
|
+
function basename(path2) {
|
|
3485
|
+
return path2.split(/[\\/]/).pop() ?? path2;
|
|
3486
|
+
}
|
|
3487
|
+
function isRecord4(value) {
|
|
3488
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3489
|
+
}
|
|
3490
|
+
|
|
3491
|
+
// src/openapi/load.ts
|
|
3492
|
+
init_errors();
|
|
3493
|
+
var MAX_SOURCE_BYTES = 10 * 1024 * 1024;
|
|
3494
|
+
var SUPPORTED_EXTENSIONS = /* @__PURE__ */ new Set([".yaml", ".yml", ".json"]);
|
|
3495
|
+
var SUPPORTED_VERSIONS = /* @__PURE__ */ new Set(["3.0", "3.1", "3.2"]);
|
|
3496
|
+
function invalid2(message, details, cause) {
|
|
3497
|
+
return new CommerceError("CONFIG_INVALID", message, {
|
|
3498
|
+
details,
|
|
3499
|
+
...cause !== void 0 ? { cause } : {}
|
|
3500
|
+
});
|
|
3501
|
+
}
|
|
3502
|
+
async function loadOpenApiDocument(sourcePath) {
|
|
3503
|
+
const details = { sourcePath };
|
|
3504
|
+
let stats;
|
|
3505
|
+
try {
|
|
3506
|
+
stats = await stat(sourcePath);
|
|
3507
|
+
} catch (error) {
|
|
3508
|
+
throw invalid2(`OpenAPI document "${sourcePath}" could not be read`, details, error);
|
|
3509
|
+
}
|
|
3510
|
+
if (stats.isDirectory()) {
|
|
3511
|
+
throw invalid2(`"${sourcePath}" is a directory, not an OpenAPI document`, details);
|
|
3512
|
+
}
|
|
3513
|
+
if (stats.size > MAX_SOURCE_BYTES) {
|
|
3514
|
+
throw invalid2(
|
|
3515
|
+
`OpenAPI document "${sourcePath}" is ${stats.size} bytes, over the ${MAX_SOURCE_BYTES}-byte limit`,
|
|
3516
|
+
{ ...details, size: stats.size, maxBytes: MAX_SOURCE_BYTES }
|
|
3517
|
+
);
|
|
3518
|
+
}
|
|
3519
|
+
const extension = extname(sourcePath).toLowerCase();
|
|
3520
|
+
if (!SUPPORTED_EXTENSIONS.has(extension)) {
|
|
3521
|
+
throw invalid2(
|
|
3522
|
+
`OpenAPI document "${sourcePath}" has an unsupported extension "${extension || "(none)"}". Supported: .yaml, .yml, .json`,
|
|
3523
|
+
details
|
|
3524
|
+
);
|
|
3525
|
+
}
|
|
3526
|
+
const source = await readFile(sourcePath, "utf8");
|
|
3527
|
+
if (source.trim() === "") {
|
|
3528
|
+
throw invalid2(`OpenAPI document "${sourcePath}" is empty`, details);
|
|
3529
|
+
}
|
|
3530
|
+
let parsed;
|
|
3531
|
+
try {
|
|
3532
|
+
parsed = extension === ".json" ? JSON.parse(source) : parse(source);
|
|
3533
|
+
} catch (error) {
|
|
3534
|
+
throw invalid2(
|
|
3535
|
+
`OpenAPI document "${sourcePath}" is not valid ${extension === ".json" ? "JSON" : "YAML"}: ${error instanceof Error ? error.message : String(error)}`,
|
|
3536
|
+
details,
|
|
3537
|
+
error
|
|
3538
|
+
);
|
|
3539
|
+
}
|
|
3540
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
3541
|
+
throw invalid2(`OpenAPI document "${sourcePath}" is not an object`, details);
|
|
3542
|
+
}
|
|
3543
|
+
const document = parsed;
|
|
3544
|
+
const version = readVersion(document, sourcePath);
|
|
3545
|
+
rejectExternalReferences(document, sourcePath);
|
|
3546
|
+
const result = await validate(document);
|
|
3547
|
+
if (!result.valid) {
|
|
3548
|
+
const first = result.errors?.[0];
|
|
3549
|
+
throw invalid2(
|
|
3550
|
+
`OpenAPI document "${sourcePath}" is not a valid OpenAPI ${version} description: ${first ? `${first.message}${first.path ? ` (at ${first.path})` : ""}` : "unknown validation error"}`,
|
|
3551
|
+
{ ...details, errors: result.errors?.slice(0, 10) ?? [] }
|
|
3552
|
+
);
|
|
3553
|
+
}
|
|
3554
|
+
return { version, document, sourcePath };
|
|
3555
|
+
}
|
|
3556
|
+
function readVersion(document, sourcePath) {
|
|
3557
|
+
const details = { sourcePath };
|
|
3558
|
+
if (typeof document["swagger"] === "string") {
|
|
3559
|
+
throw invalid2(
|
|
3560
|
+
`"${sourcePath}" is a Swagger ${document["swagger"]} document. Only OpenAPI 3.0, 3.1 and 3.2 are supported - convert it first`,
|
|
3561
|
+
details
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
const declared = document["openapi"];
|
|
3565
|
+
if (typeof declared !== "string") {
|
|
3566
|
+
throw invalid2(`"${sourcePath}" has no "openapi" version field`, details);
|
|
3567
|
+
}
|
|
3568
|
+
const featureVersion = /^(\d+\.\d+)(?:\.|$)/.exec(declared)?.[1];
|
|
3569
|
+
if (featureVersion === void 0 || !SUPPORTED_VERSIONS.has(featureVersion)) {
|
|
3570
|
+
throw invalid2(
|
|
3571
|
+
`OpenAPI version "${declared}" is not supported. Supported: 3.0.x, 3.1.x, 3.2.x`,
|
|
3572
|
+
{ ...details, version: declared }
|
|
3573
|
+
);
|
|
3574
|
+
}
|
|
3575
|
+
return featureVersion;
|
|
3576
|
+
}
|
|
3577
|
+
function rejectExternalReferences(document, sourcePath) {
|
|
3578
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
3579
|
+
const queue = [document];
|
|
3580
|
+
while (queue.length > 0) {
|
|
3581
|
+
const node = queue.pop();
|
|
3582
|
+
if (typeof node !== "object" || node === null) continue;
|
|
3583
|
+
if (seen.has(node)) continue;
|
|
3584
|
+
seen.add(node);
|
|
3585
|
+
if (Array.isArray(node)) {
|
|
3586
|
+
queue.push(...node);
|
|
3587
|
+
continue;
|
|
3588
|
+
}
|
|
3589
|
+
for (const [key, value] of Object.entries(node)) {
|
|
3590
|
+
if (key === "$ref" && typeof value === "string" && !value.startsWith("#")) {
|
|
3591
|
+
throw invalid2(
|
|
3592
|
+
`OpenAPI document "${sourcePath}" contains an external reference "${value}". Only internal references (#/...) are supported; the importer performs no network or filesystem lookups`,
|
|
3593
|
+
{ sourcePath, ref: value }
|
|
3594
|
+
);
|
|
3595
|
+
}
|
|
3596
|
+
queue.push(value);
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3599
|
+
}
|
|
3600
|
+
|
|
3601
|
+
// src/cli/commands/import-openapi.ts
|
|
3602
|
+
function defaultOutputPath(source) {
|
|
3603
|
+
const name = basename$1(source);
|
|
3604
|
+
const stem = name.slice(0, name.length - extname(name).length) || name;
|
|
3605
|
+
return `${stem}.agent-commerce.yaml`;
|
|
3606
|
+
}
|
|
3607
|
+
async function runImportOpenApi(options, io, deps = {}) {
|
|
3608
|
+
const fileExists = deps.fileExists ?? existsSync;
|
|
3609
|
+
const outputPath = options.output ?? defaultOutputPath(options.source);
|
|
3610
|
+
let policy;
|
|
3611
|
+
try {
|
|
3612
|
+
policy = buildPolicy(options);
|
|
3613
|
+
} catch (error) {
|
|
3614
|
+
io.stderr(`FAIL ${error instanceof Error ? error.message : String(error)}`);
|
|
3615
|
+
return 1;
|
|
3616
|
+
}
|
|
3617
|
+
if (fileExists(outputPath) && options.force !== true) {
|
|
3618
|
+
io.stderr(`FAIL ${outputPath} already exists. Re-run with --force to overwrite.`);
|
|
3619
|
+
return 1;
|
|
3620
|
+
}
|
|
3621
|
+
let result;
|
|
3622
|
+
try {
|
|
3623
|
+
const loaded = await loadOpenApiDocument(options.source);
|
|
3624
|
+
result = buildResourceDrafts(loaded, {
|
|
3625
|
+
...options.baseUrl !== void 0 ? { baseUrl: options.baseUrl } : {},
|
|
3626
|
+
...Object.keys(policy).length > 0 ? { policy } : {},
|
|
3627
|
+
...options.operations !== void 0 || options.tags !== void 0 ? {
|
|
3628
|
+
include: {
|
|
3629
|
+
...options.operations !== void 0 ? { operationIds: options.operations } : {},
|
|
3630
|
+
...options.tags !== void 0 ? { tags: options.tags } : {}
|
|
3631
|
+
}
|
|
3632
|
+
} : {}
|
|
3633
|
+
});
|
|
3634
|
+
} catch (error) {
|
|
3635
|
+
io.stderr(`FAIL ${error instanceof Error ? error.message : String(error)}`);
|
|
3636
|
+
return 1;
|
|
3637
|
+
}
|
|
3638
|
+
const skipped = result.diagnostics.filter((entry) => entry.severity === "error");
|
|
3639
|
+
const warnings = result.diagnostics.filter((entry) => entry.severity === "warning");
|
|
3640
|
+
const failures = [];
|
|
3641
|
+
if (result.unmatchedOperationIds.length > 0) {
|
|
3642
|
+
failures.push(`no operation matched ${result.unmatchedOperationIds.join(", ")}`);
|
|
3643
|
+
}
|
|
3644
|
+
if (result.drafts.length === 0) {
|
|
3645
|
+
failures.push("no supported operations were imported");
|
|
3646
|
+
}
|
|
3647
|
+
if (options.strict === true && warnings.length > 0) {
|
|
3648
|
+
failures.push(`${warnings.length} warning(s) with --strict`);
|
|
3649
|
+
}
|
|
3650
|
+
const wrote = failures.length === 0;
|
|
3651
|
+
if (wrote) {
|
|
3652
|
+
try {
|
|
3653
|
+
await writeAtomically(outputPath, renderResourcesYaml(result), deps);
|
|
3654
|
+
} catch (error) {
|
|
3655
|
+
io.stderr(`FAIL ${outputPath} could not be written: ${describe(error)}`);
|
|
3656
|
+
return 1;
|
|
3657
|
+
}
|
|
3658
|
+
}
|
|
3659
|
+
if (options.json === true) {
|
|
3660
|
+
io.stdout(
|
|
3661
|
+
JSON.stringify(
|
|
3662
|
+
{
|
|
3663
|
+
source: options.source,
|
|
3664
|
+
openapi: result.version,
|
|
3665
|
+
output: wrote ? outputPath : null,
|
|
3666
|
+
imported: result.drafts.length,
|
|
3667
|
+
skipped: skipped.length,
|
|
3668
|
+
warnings: warnings.length,
|
|
3669
|
+
resources: result.drafts.map((draft) => ({
|
|
3670
|
+
id: draft.id,
|
|
3671
|
+
source: draft.source,
|
|
3672
|
+
...draft.operationId !== void 0 ? { operationId: draft.operationId } : {},
|
|
3673
|
+
tags: draft.tags
|
|
3674
|
+
})),
|
|
3675
|
+
diagnostics: result.diagnostics,
|
|
3676
|
+
unmatchedOperationIds: result.unmatchedOperationIds,
|
|
3677
|
+
exitCode: failures.length === 0 ? 0 : 1
|
|
3678
|
+
},
|
|
3679
|
+
null,
|
|
3680
|
+
2
|
|
3681
|
+
)
|
|
3682
|
+
);
|
|
3683
|
+
} else {
|
|
3684
|
+
printSummary(result, { outputPath, wrote, policy }, io);
|
|
3685
|
+
}
|
|
3686
|
+
for (const failure of failures) io.stderr(`FAIL ${failure}`);
|
|
3687
|
+
return failures.length === 0 ? 0 : 1;
|
|
3688
|
+
}
|
|
3689
|
+
function buildPolicy(options) {
|
|
3690
|
+
const policy = {};
|
|
3691
|
+
if (options.free === true) policy.pricing = { type: "free" };
|
|
3692
|
+
if (options.expose !== void 0) {
|
|
3693
|
+
const requested = options.expose.split(",").map((name) => name.trim()).filter((name) => name !== "");
|
|
3694
|
+
if (requested.length === 0) {
|
|
3695
|
+
throw new Error(`--expose needs at least one protocol (${PROTOCOL_NAMES.join(", ")})`);
|
|
3696
|
+
}
|
|
3697
|
+
for (const name of requested) {
|
|
3698
|
+
if (!PROTOCOL_NAMES.includes(name)) {
|
|
3699
|
+
throw new Error(
|
|
3700
|
+
`--expose "${name}" is not a supported protocol. Supported: ${PROTOCOL_NAMES.join(", ")}`
|
|
3701
|
+
);
|
|
3702
|
+
}
|
|
3703
|
+
}
|
|
3704
|
+
policy.expose = requested;
|
|
3705
|
+
}
|
|
3706
|
+
return policy;
|
|
3707
|
+
}
|
|
3708
|
+
async function writeAtomically(outputPath, content, deps) {
|
|
3709
|
+
if (deps.writeFile !== void 0) {
|
|
3710
|
+
await deps.writeFile(outputPath, content);
|
|
3711
|
+
return;
|
|
3712
|
+
}
|
|
3713
|
+
const target = resolve(outputPath);
|
|
3714
|
+
const temporary = `${target}.${process.pid}.tmp`;
|
|
3715
|
+
try {
|
|
3716
|
+
await writeFile(temporary, content, "utf8");
|
|
3717
|
+
await rename(temporary, target);
|
|
3718
|
+
} catch (error) {
|
|
3719
|
+
await rm(temporary, { force: true });
|
|
3720
|
+
throw error;
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
function printSummary(result, context, io) {
|
|
3724
|
+
const skipped = result.diagnostics.filter((entry) => entry.severity === "error");
|
|
3725
|
+
const warnings = result.diagnostics.filter((entry) => entry.severity === "warning");
|
|
3726
|
+
io.stdout(`OpenAPI ${result.version}: ${result.sourcePath}`);
|
|
3727
|
+
io.stdout("");
|
|
3728
|
+
io.stdout(`Imported: ${result.drafts.length}`);
|
|
3729
|
+
io.stdout(`Skipped: ${skipped.length}`);
|
|
3730
|
+
io.stdout(`Warnings: ${warnings.length}`);
|
|
3731
|
+
if (skipped.length > 0) {
|
|
3732
|
+
io.stdout("");
|
|
3733
|
+
io.stdout("Skipped operations:");
|
|
3734
|
+
for (const entry of skipped) io.stdout(` ${entry.message}`);
|
|
3735
|
+
}
|
|
3736
|
+
if (warnings.length > 0) {
|
|
3737
|
+
io.stdout("");
|
|
3738
|
+
io.stdout("Warnings:");
|
|
3739
|
+
for (const entry of warnings) io.stdout(` ${entry.message}`);
|
|
3740
|
+
}
|
|
3741
|
+
if (context.wrote) {
|
|
3742
|
+
io.stdout("");
|
|
3743
|
+
io.stdout("Generated:");
|
|
3744
|
+
io.stdout(` ${context.outputPath}`);
|
|
3745
|
+
}
|
|
3746
|
+
io.stdout("");
|
|
3747
|
+
if (context.policy.pricing === void 0 || context.policy.expose === void 0) {
|
|
3748
|
+
io.stdout("Pricing/exposure were not inferred.");
|
|
3749
|
+
}
|
|
3750
|
+
io.stdout("Review the generated resources before merging them into config.yaml.");
|
|
3751
|
+
}
|
|
3752
|
+
function describe(error) {
|
|
3753
|
+
return error instanceof Error ? error.message : String(error);
|
|
3754
|
+
}
|
|
3755
|
+
|
|
2555
3756
|
// src/cli/commands/init.ts
|
|
2556
3757
|
init_filename();
|
|
2557
3758
|
|
|
@@ -2770,7 +3971,7 @@ var processIo = {
|
|
|
2770
3971
|
// src/cli/program.ts
|
|
2771
3972
|
function buildProgram(io = processIo) {
|
|
2772
3973
|
const program = new Command();
|
|
2773
|
-
program.name("agent-commerce").description("CLI for the Agent Commerce Gateway: init, validate, doctor, demo.").version(readVersionReport().cliVersion, "-v, --version", "Print the CLI version.").exitOverride().configureOutput({
|
|
3974
|
+
program.name("agent-commerce").description("CLI for the Agent Commerce Gateway: init, import, validate, doctor, demo.").version(readVersionReport().cliVersion, "-v, --version", "Print the CLI version.").exitOverride().configureOutput({
|
|
2774
3975
|
writeOut: (str) => io.stdout(str.replace(/\n+$/, "")),
|
|
2775
3976
|
writeErr: (str) => io.stderr(str.replace(/\n+$/, ""))
|
|
2776
3977
|
});
|
|
@@ -2799,6 +4000,31 @@ function buildProgram(io = processIo) {
|
|
|
2799
4000
|
const code = await runInit({ outputPath: opts.output, force: opts.force, yes: opts.yes }, io);
|
|
2800
4001
|
process.exitCode = code;
|
|
2801
4002
|
});
|
|
4003
|
+
const importCommand = program.command("import").description("Generate Agent Commerce resource drafts from an API description.");
|
|
4004
|
+
importCommand.command("openapi").description("Convert a local OpenAPI 3.0/3.1/3.2 document into resource drafts.").argument("<source>", "path to a local .yaml, .yml or .json OpenAPI document").option("--output <path>", "output path (default: <source>.agent-commerce.yaml)").option("--force", "overwrite an existing output file", false).option("--base-url <url>", "backend base URL, overriding the document servers").option(
|
|
4005
|
+
"--operation <operationId>",
|
|
4006
|
+
"import only this operation (repeatable)",
|
|
4007
|
+
collect,
|
|
4008
|
+
void 0
|
|
4009
|
+
).option("--tag <tag>", "import only operations with this tag (repeatable, OR-ed)", collect).option("--free", "mark generated resources as pricing.type free", false).option("--expose <protocols>", `comma-separated: ${PROTOCOL_NAMES.join(",")}`).option("--strict", "exit non-zero when the import produced warnings", false).option("--json", "emit a machine-readable summary instead of a report", false).action(
|
|
4010
|
+
async (source, opts) => {
|
|
4011
|
+
process.exitCode = await runImportOpenApi(
|
|
4012
|
+
{
|
|
4013
|
+
source,
|
|
4014
|
+
...opts.output !== void 0 ? { output: opts.output } : {},
|
|
4015
|
+
force: opts.force,
|
|
4016
|
+
...opts.baseUrl !== void 0 ? { baseUrl: opts.baseUrl } : {},
|
|
4017
|
+
...opts.operation !== void 0 ? { operations: opts.operation } : {},
|
|
4018
|
+
...opts.tag !== void 0 ? { tags: opts.tag } : {},
|
|
4019
|
+
free: opts.free,
|
|
4020
|
+
...opts.expose !== void 0 ? { expose: opts.expose } : {},
|
|
4021
|
+
strict: opts.strict,
|
|
4022
|
+
json: opts.json
|
|
4023
|
+
},
|
|
4024
|
+
io
|
|
4025
|
+
);
|
|
4026
|
+
}
|
|
4027
|
+
);
|
|
2802
4028
|
program.command("demo").description(
|
|
2803
4029
|
"Run the documented docker compose + demo agent quickstart, verifying health between steps."
|
|
2804
4030
|
).action(async () => {
|
|
@@ -2806,6 +4032,9 @@ function buildProgram(io = processIo) {
|
|
|
2806
4032
|
});
|
|
2807
4033
|
return program;
|
|
2808
4034
|
}
|
|
4035
|
+
function collect(value, previous) {
|
|
4036
|
+
return [...previous ?? [], value];
|
|
4037
|
+
}
|
|
2809
4038
|
|
|
2810
4039
|
// src/cli/index.ts
|
|
2811
4040
|
for (const stream of [process.stdout, process.stderr]) {
|