nexus-ai-pro 2.20.2 → 2.22.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.
Files changed (233) hide show
  1. package/API_STABILITY.md +93 -0
  2. package/CHANGELOG.md +208 -1
  3. package/README.md +11 -6
  4. package/SECURITY.md +16 -2
  5. package/dist/adapter-kit/gather.d.ts +76 -0
  6. package/dist/adapter-kit/gather.js +147 -0
  7. package/dist/adapter-kit/index.d.ts +1 -0
  8. package/dist/adapter-kit/index.js +1 -0
  9. package/dist/adapter-kit/record.d.ts +3 -1
  10. package/dist/adapter-kit/record.js +20 -5
  11. package/dist/agent/openapi.d.ts +94 -8
  12. package/dist/agent/openapi.js +528 -32
  13. package/dist/cli/adapters.d.ts +4 -3
  14. package/dist/cli/adapters.js +71 -2
  15. package/dist/cli/doctor.js +3 -0
  16. package/dist/cli.js +3 -1
  17. package/dist/core/config-builder.d.ts +1 -0
  18. package/dist/core/config-builder.js +1 -0
  19. package/dist/core/defaults.d.ts +34 -0
  20. package/dist/core/defaults.js +39 -0
  21. package/dist/core/secure-stream.js +4 -1
  22. package/dist/decisions/completion.d.ts +10 -7
  23. package/dist/decisions/completion.js +82 -8
  24. package/dist/decisions/core.d.ts +88 -43
  25. package/dist/decisions/core.js +115 -28
  26. package/dist/decisions/errors.d.ts +25 -0
  27. package/dist/decisions/errors.js +12 -0
  28. package/dist/decisions/index.d.ts +6 -5
  29. package/dist/decisions/index.js +2 -1
  30. package/dist/decisions/integrations.d.ts +14 -5
  31. package/dist/decisions/integrations.js +16 -1
  32. package/dist/decisions/mock.d.ts +5 -3
  33. package/dist/decisions/mock.js +4 -1
  34. package/dist/decisions/pictures.d.ts +43 -0
  35. package/dist/decisions/pictures.js +103 -0
  36. package/dist/decisions/typesafe.d.ts +33 -5
  37. package/dist/decisions/typesafe.js +121 -8
  38. package/dist/doctor/index.d.ts +6 -9
  39. package/dist/doctor/index.js +41 -0
  40. package/dist/embeddings/adapters.d.ts +1 -1
  41. package/dist/embeddings/adapters.js +3 -3
  42. package/dist/embeddings/batch-google.d.ts +27 -8
  43. package/dist/embeddings/batch-google.js +112 -36
  44. package/dist/embeddings/batch-mock.js +1 -1
  45. package/dist/embeddings/batch.js +10 -4
  46. package/dist/embeddings/engine.js +14 -13
  47. package/dist/embeddings/index.d.ts +1 -1
  48. package/dist/embeddings/inputs.d.ts +26 -3
  49. package/dist/embeddings/inputs.js +84 -5
  50. package/dist/embeddings/mock.js +1 -1
  51. package/dist/embeddings/models.js +39 -4
  52. package/dist/loaders/csv.d.ts +23 -1
  53. package/dist/loaders/csv.js +125 -72
  54. package/dist/loaders/index.d.ts +7 -2
  55. package/dist/loaders/input.d.ts +6 -0
  56. package/dist/loaders/input.js +42 -1
  57. package/dist/loaders/json.d.ts +14 -3
  58. package/dist/loaders/json.js +171 -20
  59. package/dist/loaders/web.d.ts +55 -0
  60. package/dist/loaders/web.js +20 -1
  61. package/dist/mcp/client.d.ts +54 -5
  62. package/dist/mcp/client.js +171 -20
  63. package/dist/mcp/http.d.ts +45 -0
  64. package/dist/mcp/http.js +233 -0
  65. package/dist/mcp/index.d.ts +4 -2
  66. package/dist/mcp/index.js +3 -1
  67. package/dist/mcp/protocol.d.ts +107 -0
  68. package/dist/mcp/sampling.d.ts +41 -0
  69. package/dist/mcp/sampling.js +52 -0
  70. package/dist/mcp/server.d.ts +96 -14
  71. package/dist/mcp/server.js +274 -59
  72. package/dist/models/data.js +20 -1
  73. package/dist/operations/adapters.d.ts +1 -0
  74. package/dist/operations/adapters.js +3 -1
  75. package/dist/protocols/a2a-grpc.js +10 -3
  76. package/dist/protocols/a2a-signing.d.ts +65 -0
  77. package/dist/protocols/a2a-signing.js +133 -0
  78. package/dist/protocols/a2a.d.ts +14 -0
  79. package/dist/protocols/a2a.js +60 -5
  80. package/dist/providers/cohere.js +16 -2
  81. package/dist/providers/google.js +7 -1
  82. package/dist/providers/ollama.d.ts +1 -1
  83. package/dist/providers/ollama.js +35 -27
  84. package/dist/providers/openai.js +19 -2
  85. package/dist/rag/pinecone.d.ts +63 -2
  86. package/dist/rag/pinecone.js +80 -0
  87. package/dist/realtime/conversation.js +18 -0
  88. package/dist/realtime/gemini-live.transport.d.ts +28 -3
  89. package/dist/realtime/gemini-live.transport.js +170 -32
  90. package/dist/realtime/session.d.ts +2 -0
  91. package/dist/realtime/session.js +19 -4
  92. package/dist/realtime/types.d.ts +35 -3
  93. package/dist/security/output-guard.js +4 -1
  94. package/dist/server/auth.d.ts +263 -2
  95. package/dist/server/auth.js +432 -5
  96. package/dist/server/deployments.d.ts +3 -2
  97. package/dist/server/deployments.js +6 -1
  98. package/dist/telephony/index.d.ts +1 -1
  99. package/dist/telephony/manager.d.ts +8 -1
  100. package/dist/telephony/manager.js +13 -0
  101. package/dist/telephony/providers/telnyx.d.ts +18 -5
  102. package/dist/telephony/providers/telnyx.js +44 -7
  103. package/dist/telephony/providers/twilio.d.ts +3 -1
  104. package/dist/telephony/providers/twilio.js +16 -0
  105. package/dist/tracing/stores.d.ts +2 -0
  106. package/dist/tracing/stores.js +1 -0
  107. package/dist/tracing/tracer.d.ts +5 -2
  108. package/dist/tracing/tracer.js +6 -3
  109. package/dist/types/embeddings.d.ts +47 -5
  110. package/dist/types/messages.d.ts +7 -0
  111. package/dist/types/principal.d.ts +1 -1
  112. package/dist/types/response.d.ts +5 -0
  113. package/dist/types/telephony.d.ts +38 -0
  114. package/dist/types/tracing.d.ts +6 -0
  115. package/dist/version.d.ts +1 -1
  116. package/dist/version.js +1 -1
  117. package/dist/voice/providers/google.d.ts +44 -0
  118. package/dist/voice/providers/google.js +215 -0
  119. package/dist-cjs/adapter-kit/gather.d.ts +76 -0
  120. package/dist-cjs/adapter-kit/gather.js +152 -0
  121. package/dist-cjs/adapter-kit/index.d.ts +1 -0
  122. package/dist-cjs/adapter-kit/index.js +4 -1
  123. package/dist-cjs/adapter-kit/record.d.ts +3 -1
  124. package/dist-cjs/adapter-kit/record.js +20 -5
  125. package/dist-cjs/agent/openapi.d.ts +94 -8
  126. package/dist-cjs/agent/openapi.js +530 -32
  127. package/dist-cjs/cli/adapters.d.ts +4 -3
  128. package/dist-cjs/cli/adapters.js +70 -1
  129. package/dist-cjs/cli/doctor.js +3 -0
  130. package/dist-cjs/core/config-builder.d.ts +1 -0
  131. package/dist-cjs/core/config-builder.js +5 -1
  132. package/dist-cjs/core/defaults.d.ts +34 -0
  133. package/dist-cjs/core/defaults.js +44 -0
  134. package/dist-cjs/core/secure-stream.js +4 -1
  135. package/dist-cjs/decisions/completion.d.ts +10 -7
  136. package/dist-cjs/decisions/completion.js +81 -7
  137. package/dist-cjs/decisions/core.d.ts +88 -43
  138. package/dist-cjs/decisions/core.js +134 -46
  139. package/dist-cjs/decisions/errors.d.ts +25 -0
  140. package/dist-cjs/decisions/errors.js +16 -0
  141. package/dist-cjs/decisions/index.d.ts +6 -5
  142. package/dist-cjs/decisions/index.js +5 -1
  143. package/dist-cjs/decisions/integrations.d.ts +14 -5
  144. package/dist-cjs/decisions/integrations.js +16 -1
  145. package/dist-cjs/decisions/mock.d.ts +5 -3
  146. package/dist-cjs/decisions/mock.js +4 -1
  147. package/dist-cjs/decisions/pictures.d.ts +43 -0
  148. package/dist-cjs/decisions/pictures.js +109 -0
  149. package/dist-cjs/decisions/typesafe.d.ts +33 -5
  150. package/dist-cjs/decisions/typesafe.js +126 -13
  151. package/dist-cjs/doctor/index.d.ts +6 -9
  152. package/dist-cjs/doctor/index.js +41 -0
  153. package/dist-cjs/embeddings/adapters.d.ts +1 -1
  154. package/dist-cjs/embeddings/adapters.js +3 -3
  155. package/dist-cjs/embeddings/batch-google.d.ts +27 -8
  156. package/dist-cjs/embeddings/batch-google.js +112 -36
  157. package/dist-cjs/embeddings/batch-mock.js +1 -1
  158. package/dist-cjs/embeddings/batch.js +9 -3
  159. package/dist-cjs/embeddings/engine.js +13 -12
  160. package/dist-cjs/embeddings/index.d.ts +1 -1
  161. package/dist-cjs/embeddings/inputs.d.ts +26 -3
  162. package/dist-cjs/embeddings/inputs.js +89 -5
  163. package/dist-cjs/embeddings/mock.js +1 -1
  164. package/dist-cjs/embeddings/models.js +39 -4
  165. package/dist-cjs/loaders/csv.d.ts +23 -1
  166. package/dist-cjs/loaders/csv.js +126 -71
  167. package/dist-cjs/loaders/index.d.ts +7 -2
  168. package/dist-cjs/loaders/input.d.ts +6 -0
  169. package/dist-cjs/loaders/input.js +43 -1
  170. package/dist-cjs/loaders/json.d.ts +14 -3
  171. package/dist-cjs/loaders/json.js +170 -19
  172. package/dist-cjs/loaders/web.d.ts +55 -0
  173. package/dist-cjs/loaders/web.js +21 -1
  174. package/dist-cjs/mcp/client.d.ts +54 -5
  175. package/dist-cjs/mcp/client.js +171 -20
  176. package/dist-cjs/mcp/http.d.ts +45 -0
  177. package/dist-cjs/mcp/http.js +236 -0
  178. package/dist-cjs/mcp/index.d.ts +4 -2
  179. package/dist-cjs/mcp/index.js +5 -1
  180. package/dist-cjs/mcp/protocol.d.ts +107 -0
  181. package/dist-cjs/mcp/sampling.d.ts +41 -0
  182. package/dist-cjs/mcp/sampling.js +55 -0
  183. package/dist-cjs/mcp/server.d.ts +96 -14
  184. package/dist-cjs/mcp/server.js +273 -58
  185. package/dist-cjs/models/data.js +20 -1
  186. package/dist-cjs/operations/adapters.d.ts +1 -0
  187. package/dist-cjs/operations/adapters.js +3 -1
  188. package/dist-cjs/protocols/a2a-grpc.js +10 -3
  189. package/dist-cjs/protocols/a2a-signing.d.ts +65 -0
  190. package/dist-cjs/protocols/a2a-signing.js +140 -0
  191. package/dist-cjs/protocols/a2a.d.ts +14 -0
  192. package/dist-cjs/protocols/a2a.js +65 -6
  193. package/dist-cjs/providers/cohere.js +16 -2
  194. package/dist-cjs/providers/google.js +7 -1
  195. package/dist-cjs/providers/ollama.d.ts +1 -1
  196. package/dist-cjs/providers/ollama.js +35 -27
  197. package/dist-cjs/providers/openai.js +19 -2
  198. package/dist-cjs/rag/pinecone.d.ts +63 -2
  199. package/dist-cjs/rag/pinecone.js +81 -0
  200. package/dist-cjs/realtime/conversation.js +18 -0
  201. package/dist-cjs/realtime/gemini-live.transport.d.ts +28 -3
  202. package/dist-cjs/realtime/gemini-live.transport.js +170 -32
  203. package/dist-cjs/realtime/session.d.ts +2 -0
  204. package/dist-cjs/realtime/session.js +19 -4
  205. package/dist-cjs/realtime/types.d.ts +35 -3
  206. package/dist-cjs/security/output-guard.js +4 -1
  207. package/dist-cjs/server/auth.d.ts +263 -2
  208. package/dist-cjs/server/auth.js +437 -5
  209. package/dist-cjs/server/deployments.d.ts +3 -2
  210. package/dist-cjs/server/deployments.js +6 -1
  211. package/dist-cjs/telephony/index.d.ts +1 -1
  212. package/dist-cjs/telephony/manager.d.ts +8 -1
  213. package/dist-cjs/telephony/manager.js +13 -0
  214. package/dist-cjs/telephony/providers/telnyx.d.ts +18 -5
  215. package/dist-cjs/telephony/providers/telnyx.js +44 -7
  216. package/dist-cjs/telephony/providers/twilio.d.ts +3 -1
  217. package/dist-cjs/telephony/providers/twilio.js +16 -0
  218. package/dist-cjs/tracing/stores.d.ts +2 -0
  219. package/dist-cjs/tracing/stores.js +1 -0
  220. package/dist-cjs/tracing/tracer.d.ts +5 -2
  221. package/dist-cjs/tracing/tracer.js +6 -3
  222. package/dist-cjs/types/embeddings.d.ts +47 -5
  223. package/dist-cjs/types/messages.d.ts +7 -0
  224. package/dist-cjs/types/principal.d.ts +1 -1
  225. package/dist-cjs/types/response.d.ts +5 -0
  226. package/dist-cjs/types/telephony.d.ts +38 -0
  227. package/dist-cjs/types/tracing.d.ts +6 -0
  228. package/dist-cjs/version.d.ts +1 -1
  229. package/dist-cjs/version.js +1 -1
  230. package/dist-cjs/voice/providers/google.d.ts +44 -0
  231. package/dist-cjs/voice/providers/google.js +252 -0
  232. package/llms.txt +1 -1
  233. package/package.json +17 -1
@@ -1,3 +1,4 @@
1
+ import { decodeBase64, encodeBase64 } from '../utils/base64.js';
1
2
  import { linkSignals } from '../utils/signals.js';
2
3
  export class OpenApiError extends Error {
3
4
  code;
@@ -12,6 +13,16 @@ export class OpenApiError extends Error {
12
13
  const METHODS = ['get', 'put', 'post', 'delete', 'options', 'head', 'patch', 'trace'];
13
14
  const IGNORED_HEADERS = new Set(['accept', 'content-type', 'authorization']);
14
15
  const OPENAPI_ONLY = new Set(['nullable', 'example', 'xml', 'externalDocs', 'discriminator']);
16
+ const FILE_SCHEMA = {
17
+ type: 'object',
18
+ description: 'A file, its bytes as base64',
19
+ properties: {
20
+ base64: { type: 'string', description: 'The bytes, base64-encoded' },
21
+ filename: { type: 'string' },
22
+ mimeType: { type: 'string' },
23
+ },
24
+ required: ['base64'],
25
+ };
15
26
  export function openApiTools(document, options = {}) {
16
27
  const doc = parseDocument(document);
17
28
  const apiName = options.name ?? (slug(doc.info?.title ?? '') || 'api');
@@ -19,8 +30,11 @@ export function openApiTools(document, options = {}) {
19
30
  const tools = [];
20
31
  const operations = [];
21
32
  const results = {};
22
- const skipped = [];
23
33
  const used = new Set();
34
+ const schemes = isRecord(doc.components?.securitySchemes)
35
+ ? doc.components.securitySchemes
36
+ : {};
37
+ const tokens = new Map();
24
38
  for (const [path, item] of Object.entries(doc.paths ?? {})) {
25
39
  const pathItem = deref(doc, item);
26
40
  for (const method of METHODS) {
@@ -37,17 +51,13 @@ export function openApiTools(document, options = {}) {
37
51
  ...(typeof raw.summary === 'string' ? { summary: raw.summary } : {}),
38
52
  tags: Array.isArray(raw.tags) ? raw.tags.map(String) : [],
39
53
  access: method === 'delete' ? 'destructive' : ['get', 'head', 'options', 'trace'].includes(method) ? 'read' : 'write',
54
+ security: securityOf(raw.security ?? doc.security),
40
55
  };
41
56
  if (options.include && !options.include(operation)) {
42
57
  used.delete(name);
43
58
  continue;
44
59
  }
45
60
  const built = buildOperation(doc, pathItem, raw);
46
- if ('skip' in built) {
47
- used.delete(name);
48
- skipped.push({ method: operation.method, path, reason: built.skip });
49
- continue;
50
- }
51
61
  operations.push(operation);
52
62
  if (built.result)
53
63
  results[name] = built.result;
@@ -59,11 +69,23 @@ export function openApiTools(document, options = {}) {
59
69
  `api.${operation.access}:${apiName}/${name}`,
60
70
  ...(baseUrl ? [`network:${new URL(baseUrl).host}`] : []),
61
71
  ],
62
- ...(baseUrl ? { execute: caller(operation, built.placements, built.bodyType, baseUrl, options) } : {}),
72
+ ...(baseUrl
73
+ ? {
74
+ execute: caller(operation, built.placements, built.body, baseUrl, options, (headers, url, context) => authorize(operation, schemes, options, context, headers, url, baseUrl, tokens)),
75
+ }
76
+ : {}),
63
77
  });
64
78
  }
65
79
  }
66
- return { name: apiName, ...(baseUrl ? { baseUrl } : {}), tools, operations, results, skipped };
80
+ return {
81
+ name: apiName,
82
+ ...(baseUrl ? { baseUrl } : {}),
83
+ tools,
84
+ operations,
85
+ results,
86
+ skipped: [],
87
+ webhooks: webhooksOf(doc),
88
+ };
67
89
  }
68
90
  export async function loadOpenApiTools(url, options = {}) {
69
91
  const response = await (options.fetch ?? globalThis.fetch)(url, {
@@ -72,7 +94,18 @@ export async function loadOpenApiTools(url, options = {}) {
72
94
  });
73
95
  if (!response.ok)
74
96
  throw new OpenApiError(`The OpenAPI document at ${url} answered ${response.status}`, 'OPENAPI_REQUEST', response.status);
75
- return openApiTools(await response.text(), { documentUrl: url, ...options });
97
+ const doc = parseDocument(await readCapped(response, options.maxBytes ?? 5_000_000, `The OpenAPI document at ${url}`));
98
+ const bundled = hasOutsideRef(doc) ? await bundleOpenApiDocument(doc, { ...options, documentUrl: url }) : doc;
99
+ return openApiTools(bundled, { documentUrl: url, ...options });
100
+ }
101
+ function hasOutsideRef(value) {
102
+ if (Array.isArray(value))
103
+ return value.some(hasOutsideRef);
104
+ if (!isRecord(value))
105
+ return false;
106
+ if (typeof value.$ref === 'string' && !value.$ref.startsWith('#'))
107
+ return true;
108
+ return Object.values(value).some(hasOutsideRef);
76
109
  }
77
110
  function parseDocument(document) {
78
111
  let doc = document;
@@ -86,6 +119,8 @@ function parseDocument(document) {
86
119
  }
87
120
  if (!isRecord(doc))
88
121
  throw new OpenApiError('An OpenAPI document is an object', 'OPENAPI_INVALID');
122
+ if (doc.swagger === '2.0')
123
+ return swaggerToOpenApi(doc);
89
124
  if (typeof doc.swagger === 'string') {
90
125
  throw new OpenApiError(`Swagger ${doc.swagger} is not read; convert the document to OpenAPI 3`, 'OPENAPI_UNSUPPORTED');
91
126
  }
@@ -124,7 +159,7 @@ function deref(doc, value, seen = []) {
124
159
  }
125
160
  function resolve(doc, ref) {
126
161
  if (!ref.startsWith('#')) {
127
- throw new OpenApiError(`${ref} is outside the document; only references within it are followed`, 'OPENAPI_UNSUPPORTED');
162
+ throw new OpenApiError(`${ref} is outside the document: read it with loadOpenApiTools(), or bundleOpenApiDocument() first`, 'OPENAPI_UNSUPPORTED');
128
163
  }
129
164
  let node = doc;
130
165
  for (const part of ref.slice(1).split('/').slice(1)) {
@@ -223,39 +258,56 @@ function buildOperation(doc, pathItem, raw) {
223
258
  required.push(arg);
224
259
  placements.push({ arg, name, in: place });
225
260
  }
226
- let bodyType;
261
+ let bodyOf;
227
262
  const body = deref(doc, raw.requestBody);
228
- if (isRecord(body) && isRecord(body.content)) {
263
+ if (isRecord(body) && isRecord(body.content) && Object.keys(body.content).length) {
229
264
  const types = Object.keys(body.content);
230
- const json = types.find((type) => /^application\/(?:[\w.+-]+\+)?json\b/i.test(type));
231
- const form = types.find((type) => /^application\/x-www-form-urlencoded\b/i.test(type));
232
- const chosen = json ?? form;
233
- if (!chosen)
234
- return { skip: `its request body is ${types.join(' or ')}; only JSON and form bodies are sent` };
235
- bodyType = json ? 'json' : 'form';
236
- const media = body.content[chosen];
265
+ const find = (pattern) => types.find((type) => pattern.test(type));
266
+ const chosen = [
267
+ ['json', find(/^application\/(?:[\w.+-]+\+)?json\b/i)],
268
+ ['form', find(/^application\/x-www-form-urlencoded\b/i)],
269
+ ['multipart', find(/^multipart\/form-data\b/i)],
270
+ ['text', find(/^text\/|^application\/(?:[\w.+-]+\+)?xml\b/i)],
271
+ ].find((candidate) => candidate[1] !== undefined) ?? ['binary', types[0]];
272
+ bodyOf = { type: chosen[0], mediaType: chosen[1] };
273
+ const media = body.content[bodyOf.mediaType];
237
274
  const schema = schemaOf(doc, isRecord(media) ? media.schema : undefined, 'readOnly');
238
275
  const fields = isRecord(schema.properties) ? schema.properties : undefined;
239
- const flat = fields &&
276
+ const fielded = bodyOf.type === 'json' || bodyOf.type === 'form' || bodyOf.type === 'multipart';
277
+ const flat = fielded &&
278
+ fields &&
240
279
  (schema.type === 'object' || schema.type === undefined) &&
241
280
  !schema.allOf &&
242
281
  !schema.anyOf &&
243
282
  !schema.oneOf &&
244
283
  Object.keys(fields).every((field) => !(field in properties));
245
- if (flat) {
284
+ if (flat && fields) {
246
285
  const fieldsRequired = new Set(Array.isArray(schema.required) ? schema.required.map(String) : []);
247
286
  for (const [field, fieldSchema] of Object.entries(fields)) {
248
- properties[field] = fieldSchema;
287
+ const file = bodyOf.type === 'multipart' && isFile(fieldSchema);
288
+ properties[field] = file
289
+ ? {
290
+ ...FILE_SCHEMA,
291
+ ...(isRecord(fieldSchema) && typeof fieldSchema.description === 'string'
292
+ ? { description: fieldSchema.description }
293
+ : {}),
294
+ }
295
+ : fieldSchema;
249
296
  if (body.required === true && fieldsRequired.has(field))
250
297
  required.push(field);
251
- placements.push({ arg: field, name: field, in: 'body-field' });
298
+ placements.push({ arg: field, name: field, in: 'body-field', ...(file ? { file: true } : {}) });
252
299
  }
253
300
  }
254
301
  else {
255
302
  const arg = 'body' in properties ? 'requestBody' : 'body';
303
+ const shape = bodyOf.type === 'binary'
304
+ ? FILE_SCHEMA
305
+ : bodyOf.type === 'text'
306
+ ? { type: 'string', ...(typeof schema.description === 'string' ? { description: schema.description } : {}) }
307
+ : schema;
256
308
  properties[arg] = {
257
- ...schema,
258
- ...(typeof body.description === 'string' && !schema.description ? { description: body.description } : {}),
309
+ ...shape,
310
+ ...(typeof body.description === 'string' && !shape.description ? { description: body.description } : {}),
259
311
  };
260
312
  if (body.required === true)
261
313
  required.push(arg);
@@ -285,16 +337,77 @@ function buildOperation(doc, pathItem, raw) {
285
337
  additionalProperties: false,
286
338
  },
287
339
  placements,
288
- ...(bodyType ? { bodyType } : {}),
340
+ ...(bodyOf ? { body: bodyOf } : {}),
289
341
  ...(result ? { result } : {}),
290
342
  };
291
343
  }
344
+ function isFile(schema) {
345
+ if (!isRecord(schema))
346
+ return false;
347
+ return (schema.format === 'binary' || (typeof schema.contentMediaType === 'string' && schema.contentEncoding === undefined));
348
+ }
349
+ function securityOf(value) {
350
+ if (!Array.isArray(value))
351
+ return [];
352
+ return value
353
+ .filter(isRecord)
354
+ .map((alternative) => Object.fromEntries(Object.entries(alternative).map(([name, scopes]) => [name, Array.isArray(scopes) ? scopes.map(String) : []])));
355
+ }
356
+ function webhooksOf(doc) {
357
+ const found = [];
358
+ const bodyOf = (operation) => {
359
+ const body = deref(doc, operation.requestBody);
360
+ const content = isRecord(body) && isRecord(body.content) ? body.content : undefined;
361
+ const json = content && Object.keys(content).find((type) => /json/i.test(type));
362
+ const media = json && content ? content[json] : undefined;
363
+ return isRecord(media) && media.schema !== undefined ? schemaOf(doc, media.schema, 'readOnly') : undefined;
364
+ };
365
+ const add = (name, source, item, expression) => {
366
+ const pathItem = deref(doc, item);
367
+ if (!isRecord(pathItem))
368
+ return;
369
+ for (const method of METHODS) {
370
+ const operation = pathItem[method];
371
+ if (!isRecord(operation))
372
+ continue;
373
+ const body = bodyOf(operation);
374
+ found.push({
375
+ name,
376
+ source,
377
+ method: method.toUpperCase(),
378
+ ...(expression ? { expression } : {}),
379
+ ...(body ? { body } : {}),
380
+ });
381
+ }
382
+ };
383
+ for (const [name, item] of Object.entries(isRecord(doc.webhooks) ? doc.webhooks : {}))
384
+ add(name, 'webhook', item);
385
+ for (const [path, item] of Object.entries(doc.paths ?? {})) {
386
+ const pathItem = deref(doc, item);
387
+ if (!isRecord(pathItem))
388
+ continue;
389
+ for (const method of METHODS) {
390
+ const operation = pathItem[method];
391
+ if (!isRecord(operation) || !isRecord(operation.callbacks))
392
+ continue;
393
+ const owner = typeof operation.operationId === 'string' ? operation.operationId : `${method}_${path}`;
394
+ for (const [callback, raw] of Object.entries(operation.callbacks)) {
395
+ const expressions = deref(doc, raw);
396
+ if (!isRecord(expressions))
397
+ continue;
398
+ for (const [expression, callbackItem] of Object.entries(expressions))
399
+ add(`${owner}.${callback}`, 'callback', callbackItem, expression);
400
+ }
401
+ }
402
+ }
403
+ return found;
404
+ }
292
405
  function describe(raw, operation) {
293
406
  const text = [raw.summary, raw.description].filter((part) => typeof part === 'string' && part.trim() !== '');
294
407
  const said = text.length ? text.join('\n\n') : `${operation.method} ${operation.path}`;
295
408
  return said.length > 1_024 ? `${said.slice(0, 1_021)}...` : said;
296
409
  }
297
- function caller(operation, placements, bodyType, baseUrl, options) {
410
+ function caller(operation, placements, bodyOf, baseUrl, options, authorizing) {
298
411
  const base = new URL(`${baseUrl}/`);
299
412
  return async (args, context) => {
300
413
  context?.signal?.throwIfAborted();
@@ -333,13 +446,41 @@ function caller(operation, placements, bodyType, baseUrl, options) {
333
446
  const own = typeof options.headers === 'function' ? await options.headers({ operation, context }) : options.headers;
334
447
  for (const [name, value] of Object.entries(own ?? {}))
335
448
  headers[name.toLowerCase()] = value;
449
+ await authorizing(headers, url, context);
336
450
  let payload;
337
- if (body !== undefined && bodyType === 'form') {
451
+ const type = bodyOf?.type ?? 'json';
452
+ if (body !== undefined && type === 'form') {
338
453
  headers['content-type'] = 'application/x-www-form-urlencoded';
339
454
  payload = new URLSearchParams(Object.entries(body).map(([key, value]) => [key, text(value)])).toString();
340
455
  }
456
+ else if (body !== undefined && type === 'multipart') {
457
+ const form = new FormData();
458
+ const files = new Set(placements.filter((item) => item.file).map((item) => item.name));
459
+ for (const [key, value] of Object.entries(body)) {
460
+ if (files.has(key)) {
461
+ const file = fileOf(value, `${operation.name}'s "${key}"`);
462
+ form.append(key, new Blob([file.bytes], { type: file.mimeType ?? 'application/octet-stream' }), file.filename ?? key);
463
+ }
464
+ else
465
+ form.append(key, text(value));
466
+ }
467
+ payload = form;
468
+ }
469
+ else if (body !== undefined && type === 'text') {
470
+ headers['content-type'] = bodyOf?.mediaType ?? 'text/plain';
471
+ payload = text(body);
472
+ }
473
+ else if (body !== undefined && type === 'binary') {
474
+ const file = fileOf(body, `${operation.name}'s body`);
475
+ headers['content-type'] =
476
+ file.mimeType ??
477
+ (bodyOf?.mediaType.includes('*')
478
+ ? 'application/octet-stream'
479
+ : (bodyOf?.mediaType ?? 'application/octet-stream'));
480
+ payload = file.bytes;
481
+ }
341
482
  else if (body !== undefined) {
342
- headers['content-type'] = 'application/json';
483
+ headers['content-type'] = bodyOf?.mediaType ?? 'application/json';
343
484
  payload = JSON.stringify(body);
344
485
  }
345
486
  const linked = linkSignals([context?.signal, AbortSignal.timeout(options.timeoutMs ?? 30_000)]);
@@ -351,7 +492,7 @@ function caller(operation, placements, bodyType, baseUrl, options) {
351
492
  redirect: 'manual',
352
493
  signal: linked.signal,
353
494
  });
354
- const answer = await readCapped(response, options.maxResponseBytes ?? 1_000_000, operation);
495
+ const answer = await readCapped(response, options.maxResponseBytes ?? 1_000_000, operation.name);
355
496
  if (response.status >= 300 && response.status < 400) {
356
497
  throw new OpenApiError(`${operation.name} was redirected to ${response.headers.get('location') ?? 'another place'}, which is not followed`, 'OPENAPI_REQUEST', response.status);
357
498
  }
@@ -380,7 +521,105 @@ function caller(operation, placements, bodyType, baseUrl, options) {
380
521
  }
381
522
  };
382
523
  }
383
- async function readCapped(response, limit, operation) {
524
+ function fileOf(value, what) {
525
+ const given = typeof value === 'string' ? { base64: value } : isRecord(value) ? value : {};
526
+ if (typeof given.base64 !== 'string')
527
+ throw new OpenApiError(`${what} is a file: give its base64`, 'OPENAPI_INVALID');
528
+ if (!/^[A-Za-z0-9+/=\s]*$/.test(given.base64))
529
+ throw new OpenApiError(`${what} is not base64`, 'OPENAPI_INVALID');
530
+ return {
531
+ bytes: decodeBase64(given.base64),
532
+ ...(typeof given.filename === 'string' ? { filename: given.filename } : {}),
533
+ ...(typeof given.mimeType === 'string' ? { mimeType: given.mimeType } : {}),
534
+ };
535
+ }
536
+ async function authorize(operation, schemes, options, context, headers, url, baseUrl, tokens) {
537
+ const credentials = options.credentials;
538
+ if (!credentials || operation.security.length === 0)
539
+ return;
540
+ const has = (name) => Object.hasOwn(credentials, name) && isRecord(schemes[name]);
541
+ const chosen = operation.security.find((alternative) => Object.keys(alternative).every(has));
542
+ if (!chosen) {
543
+ const asked = operation.security.map((alternative) => Object.keys(alternative).join(' and ')).join(', or ');
544
+ throw new OpenApiError(`${operation.name} needs credentials for ${asked}`, 'OPENAPI_INVALID');
545
+ }
546
+ for (const [name, scopes] of Object.entries(chosen)) {
547
+ const scheme = schemes[name];
548
+ const given = credentials[name];
549
+ const credential = typeof given === 'function' ? await given({ operation, context }) : given;
550
+ const fields = (isRecord(credential) ? credential : {});
551
+ const token = () => {
552
+ if (typeof credential === 'string')
553
+ return credential;
554
+ if (typeof fields.token === 'string')
555
+ return fields.token;
556
+ throw new OpenApiError(`The credential for ${name} is a token, as text or { token }`, 'OPENAPI_INVALID');
557
+ };
558
+ if (scheme.type === 'apiKey') {
559
+ const key = String(scheme.name ?? '');
560
+ if (scheme.in === 'query')
561
+ url.searchParams.set(key, token());
562
+ else if (scheme.in === 'cookie') {
563
+ const cookie = `${key}=${encodeURIComponent(token())}`;
564
+ headers.cookie = headers.cookie ? `${headers.cookie}; ${cookie}` : cookie;
565
+ }
566
+ else
567
+ headers[key.toLowerCase()] = token();
568
+ }
569
+ else if (scheme.type === 'http') {
570
+ const kind = String(scheme.scheme ?? 'bearer').toLowerCase();
571
+ if (kind === 'basic') {
572
+ if (typeof fields.username !== 'string' || typeof fields.password !== 'string') {
573
+ throw new OpenApiError(`The credential for ${name} is { username, password }`, 'OPENAPI_INVALID');
574
+ }
575
+ headers.authorization = `Basic ${encodeBase64(new TextEncoder().encode(`${fields.username}:${fields.password}`))}`;
576
+ }
577
+ else
578
+ headers.authorization = `${kind === 'bearer' ? 'Bearer' : String(scheme.scheme)} ${token()}`;
579
+ }
580
+ else if (scheme.type === 'oauth2' || scheme.type === 'openIdConnect') {
581
+ const client = typeof fields.clientId === 'string' && typeof fields.clientSecret === 'string'
582
+ ? { clientId: fields.clientId, clientSecret: fields.clientSecret }
583
+ : undefined;
584
+ headers.authorization = `Bearer ${client ? await clientCredentials(name, scheme, client, scopes, baseUrl, options, tokens) : token()}`;
585
+ }
586
+ else {
587
+ throw new OpenApiError(`${name} is ${String(scheme.type)}, which the tools do not send`, 'OPENAPI_UNSUPPORTED');
588
+ }
589
+ }
590
+ }
591
+ async function clientCredentials(name, scheme, client, scopes, baseUrl, options, tokens) {
592
+ const flow = isRecord(scheme.flows) && isRecord(scheme.flows.clientCredentials) ? scheme.flows.clientCredentials : undefined;
593
+ if (!flow || typeof flow.tokenUrl !== 'string') {
594
+ throw new OpenApiError(`${name} has no client-credentials flow to fetch a token from`, 'OPENAPI_UNSUPPORTED');
595
+ }
596
+ const key = `${name}\u0000${client.clientId}\u0000${[...scopes].sort().join(' ')}`;
597
+ const kept = tokens.get(key);
598
+ if (kept && kept.until > Date.now())
599
+ return kept.token;
600
+ const response = await (options.fetch ?? globalThis.fetch)(new URL(flow.tokenUrl, `${baseUrl}/`).href, {
601
+ method: 'POST',
602
+ headers: {
603
+ 'content-type': 'application/x-www-form-urlencoded',
604
+ accept: 'application/json',
605
+ authorization: `Basic ${encodeBase64(new TextEncoder().encode(`${encodeURIComponent(client.clientId)}:${encodeURIComponent(client.clientSecret)}`))}`,
606
+ },
607
+ body: new URLSearchParams({
608
+ grant_type: 'client_credentials',
609
+ ...(scopes.length ? { scope: scopes.join(' ') } : {}),
610
+ }).toString(),
611
+ redirect: 'manual',
612
+ signal: AbortSignal.timeout(options.timeoutMs ?? 30_000),
613
+ });
614
+ const answer = (await response.json().catch(() => ({})));
615
+ if (typeof answer.access_token !== 'string') {
616
+ throw new OpenApiError(`${name}'s token URL answered ${response.status}: ${String(answer.error ?? 'no token')}`, 'OPENAPI_REQUEST', response.status);
617
+ }
618
+ const lifetime = typeof answer.expires_in === 'number' ? answer.expires_in * 1_000 : 3_600_000;
619
+ tokens.set(key, { token: answer.access_token, until: Date.now() + Math.max(0, lifetime - 60_000) });
620
+ return answer.access_token;
621
+ }
622
+ async function readCapped(response, limit, what) {
384
623
  if (!response.body)
385
624
  return '';
386
625
  const reader = response.body.getReader();
@@ -393,7 +632,7 @@ async function readCapped(response, limit, operation) {
393
632
  size += value.byteLength;
394
633
  if (size > limit) {
395
634
  await reader.cancel().catch(() => undefined);
396
- throw new OpenApiError(`${operation.name} answered more than ${limit} bytes`, 'OPENAPI_REQUEST', response.status);
635
+ throw new OpenApiError(`${what} answered more than ${limit} bytes`, 'OPENAPI_REQUEST', response.status);
397
636
  }
398
637
  chunks.push(value);
399
638
  }
@@ -453,3 +692,260 @@ function isRecord(value) {
453
692
  function messageOf(error) {
454
693
  return error instanceof Error ? error.message : String(error);
455
694
  }
695
+ export function swaggerToOpenApi(swagger) {
696
+ if (swagger.swagger !== '2.0') {
697
+ throw new OpenApiError(`Swagger ${String(swagger.swagger)} is not 2.0`, 'OPENAPI_UNSUPPORTED');
698
+ }
699
+ const moved = renameRefs(swagger);
700
+ const consumes = stringList(moved.consumes) ?? ['application/json'];
701
+ const produces = stringList(moved.produces) ?? ['application/json'];
702
+ const schemes = stringList(moved.schemes) ?? ['https'];
703
+ const host = typeof moved.host === 'string' ? moved.host : undefined;
704
+ const basePath = typeof moved.basePath === 'string' ? moved.basePath : '';
705
+ const components = {};
706
+ if (isRecord(moved.definitions))
707
+ components.schemas = moved.definitions;
708
+ if (isRecord(moved.parameters)) {
709
+ components.parameters = Object.fromEntries(Object.entries(moved.parameters).map(([name, parameter]) => [
710
+ name,
711
+ isRecord(parameter) ? parameterOf(parameter) : parameter,
712
+ ]));
713
+ }
714
+ if (isRecord(moved.responses)) {
715
+ components.responses = Object.fromEntries(Object.entries(moved.responses).map(([name, response]) => [name, responseOf(response, produces)]));
716
+ }
717
+ if (isRecord(moved.securityDefinitions)) {
718
+ components.securitySchemes = Object.fromEntries(Object.entries(moved.securityDefinitions).map(([name, scheme]) => [name, securitySchemeOf(scheme)]));
719
+ }
720
+ const paths = {};
721
+ for (const [path, item] of Object.entries(isRecord(moved.paths) ? moved.paths : {})) {
722
+ if (!isRecord(item))
723
+ continue;
724
+ const converted = {};
725
+ const shared = Array.isArray(item.parameters) ? item.parameters : [];
726
+ for (const [key, value] of Object.entries(item)) {
727
+ if (key === 'parameters') {
728
+ converted.parameters = shared.filter((parameter) => !isBodyLike(moved, parameter)).map(parameterOf);
729
+ continue;
730
+ }
731
+ if (!METHODS.includes(key) || !isRecord(value)) {
732
+ converted[key] = value;
733
+ continue;
734
+ }
735
+ converted[key] = operationOf(moved, value, shared, stringList(value.consumes) ?? consumes, stringList(value.produces) ?? produces);
736
+ }
737
+ paths[path] = converted;
738
+ }
739
+ const { swagger: _swagger, host: _host, basePath: _basePath, schemes: _schemes, consumes: _consumes, produces: _produces, definitions: _definitions, parameters: _parameters, responses: _responses, securityDefinitions: _security, paths: _paths, ...rest } = moved;
740
+ return {
741
+ ...rest,
742
+ openapi: '3.0.3',
743
+ ...(host
744
+ ? { servers: [{ url: `${schemes[0]}://${host}${basePath}` }] }
745
+ : basePath
746
+ ? { servers: [{ url: basePath }] }
747
+ : {}),
748
+ paths,
749
+ ...(Object.keys(components).length ? { components } : {}),
750
+ };
751
+ }
752
+ function renameRefs(value) {
753
+ if (Array.isArray(value))
754
+ return value.map(renameRefs);
755
+ if (!isRecord(value))
756
+ return value;
757
+ const out = {};
758
+ for (const [key, item] of Object.entries(value)) {
759
+ out[key] =
760
+ key === '$ref' && typeof item === 'string'
761
+ ? item
762
+ .replace(/^#\/definitions\//, '#/components/schemas/')
763
+ .replace(/^#\/parameters\//, '#/components/parameters/')
764
+ .replace(/^#\/responses\//, '#/components/responses/')
765
+ : renameRefs(item);
766
+ }
767
+ return out;
768
+ }
769
+ function isBodyLike(doc, parameter) {
770
+ const found = swaggerParameter(doc, parameter);
771
+ return found !== undefined && (found.in === 'body' || found.in === 'formData');
772
+ }
773
+ function swaggerParameter(doc, parameter) {
774
+ if (!isRecord(parameter))
775
+ return undefined;
776
+ if (typeof parameter.$ref !== 'string')
777
+ return parameter;
778
+ const name = parameter.$ref.replace(/^#\/components\/parameters\//, '');
779
+ const shared = isRecord(doc.parameters) ? doc.parameters[name] : undefined;
780
+ return isRecord(shared) ? shared : undefined;
781
+ }
782
+ function parameterOf(parameter) {
783
+ if (!isRecord(parameter) ||
784
+ typeof parameter.$ref === 'string' ||
785
+ parameter.in === 'body' ||
786
+ parameter.in === 'formData') {
787
+ return parameter;
788
+ }
789
+ const { name, in: place, description, required, ...schema } = parameter;
790
+ const { collectionFormat: _format, allowEmptyValue: _empty, ...kept } = schema;
791
+ return {
792
+ name,
793
+ in: place,
794
+ ...(description !== undefined ? { description } : {}),
795
+ ...(required !== undefined ? { required } : {}),
796
+ schema: kept,
797
+ };
798
+ }
799
+ function responseOf(response, produces) {
800
+ if (!isRecord(response) || typeof response.$ref === 'string')
801
+ return response;
802
+ const { schema, examples: _examples, ...rest } = response;
803
+ return {
804
+ ...rest,
805
+ description: typeof rest.description === 'string' ? rest.description : '',
806
+ ...(schema !== undefined ? { content: { [produces[0] ?? 'application/json']: { schema } } } : {}),
807
+ };
808
+ }
809
+ function securitySchemeOf(scheme) {
810
+ if (!isRecord(scheme))
811
+ return scheme;
812
+ if (scheme.type === 'basic')
813
+ return { type: 'http', scheme: 'basic', ...(scheme.description ? { description: scheme.description } : {}) };
814
+ if (scheme.type !== 'oauth2')
815
+ return scheme;
816
+ const flowName = { implicit: 'implicit', password: 'password', application: 'clientCredentials', accessCode: 'authorizationCode' }[String(scheme.flow)] ?? 'clientCredentials';
817
+ return {
818
+ type: 'oauth2',
819
+ ...(scheme.description ? { description: scheme.description } : {}),
820
+ flows: {
821
+ [flowName]: {
822
+ ...(scheme.authorizationUrl ? { authorizationUrl: scheme.authorizationUrl } : {}),
823
+ ...(scheme.tokenUrl ? { tokenUrl: scheme.tokenUrl } : {}),
824
+ scopes: isRecord(scheme.scopes) ? scheme.scopes : {},
825
+ },
826
+ },
827
+ };
828
+ }
829
+ function operationOf(doc, operation, shared, consumes, produces) {
830
+ const own = Array.isArray(operation.parameters) ? operation.parameters : [];
831
+ const all = [...shared, ...own].map((parameter) => swaggerParameter(doc, parameter) ?? parameter);
832
+ const body = all.find((parameter) => isRecord(parameter) && parameter.in === 'body');
833
+ const form = all.filter((parameter) => isRecord(parameter) && parameter.in === 'formData');
834
+ const { parameters: _parameters, consumes: _consumes, produces: _produces, responses, ...rest } = operation;
835
+ const out = {
836
+ ...rest,
837
+ ...(own.length ? { parameters: own.filter((parameter) => !isBodyLike(doc, parameter)).map(parameterOf) } : {}),
838
+ responses: Object.fromEntries(Object.entries(isRecord(responses) ? responses : {}).map(([code, response]) => [
839
+ code,
840
+ responseOf(response, produces),
841
+ ])),
842
+ };
843
+ if (body) {
844
+ out.requestBody = {
845
+ ...(body.description ? { description: body.description } : {}),
846
+ ...(body.required !== undefined ? { required: body.required } : {}),
847
+ content: {
848
+ [consumes.find((type) => /json/i.test(type)) ?? consumes[0] ?? 'application/json']: {
849
+ schema: body.schema ?? {},
850
+ },
851
+ },
852
+ };
853
+ }
854
+ else if (form.length) {
855
+ const files = form.some((field) => field.type === 'file');
856
+ const type = files || consumes.some((item) => /multipart\/form-data/i.test(item))
857
+ ? 'multipart/form-data'
858
+ : 'application/x-www-form-urlencoded';
859
+ const properties = Object.fromEntries(form.map((field) => {
860
+ const { name: _name, in: _in, required: _required, collectionFormat: _format, ...schema } = field;
861
+ return [String(field.name), field.type === 'file' ? { ...schema, type: 'string', format: 'binary' } : schema];
862
+ }));
863
+ const required = form.filter((field) => field.required === true).map((field) => String(field.name));
864
+ out.requestBody = {
865
+ ...(required.length ? { required: true } : {}),
866
+ content: { [type]: { schema: { type: 'object', properties, ...(required.length ? { required } : {}) } } },
867
+ };
868
+ }
869
+ return out;
870
+ }
871
+ function stringList(value) {
872
+ return Array.isArray(value) && value.length ? value.map(String) : undefined;
873
+ }
874
+ export async function bundleOpenApiDocument(document, options) {
875
+ const root = new URL(options.documentUrl);
876
+ const allowed = (url) => options.references === 'any'
877
+ ? url.protocol === 'https:' || url.protocol === 'http:'
878
+ : typeof options.references === 'function'
879
+ ? options.references(url.href)
880
+ : url.origin === root.origin;
881
+ const loaded = new Map([[stripHash(root), Promise.resolve(document)]]);
882
+ const load = (url) => {
883
+ const key = stripHash(url);
884
+ let found = loaded.get(key);
885
+ if (!found) {
886
+ if (!allowed(url)) {
887
+ return Promise.reject(new OpenApiError(`${key} is not a document references may name here`, 'OPENAPI_UNSUPPORTED'));
888
+ }
889
+ if (loaded.size >= (options.maxDocuments ?? 50)) {
890
+ return Promise.reject(new OpenApiError(`The document refers to more than ${options.maxDocuments ?? 50} documents`, 'OPENAPI_INVALID'));
891
+ }
892
+ found = fetchDocument(key, options);
893
+ loaded.set(key, found);
894
+ }
895
+ return found;
896
+ };
897
+ const inline = async (value, from, trail) => {
898
+ if (Array.isArray(value))
899
+ return Promise.all(value.map((item) => inline(item, from, trail)));
900
+ if (!isRecord(value))
901
+ return value;
902
+ if (typeof value.$ref === 'string') {
903
+ const target = new URL(value.$ref, from);
904
+ if (stripHash(target) === stripHash(root)) {
905
+ const { $ref: _ref, ...siblings } = value;
906
+ return { $ref: target.hash || '#', ...(await inlineFields(siblings, from, trail)) };
907
+ }
908
+ if (trail.includes(target.href))
909
+ return { description: `The same shape as ${target.hash.split('/').at(-1) || target.pathname}, nested` };
910
+ const named = pointer(await load(target), target.hash, target.href);
911
+ const { $ref: _ref, ...siblings } = value;
912
+ return {
913
+ ...(await inline(named, target, [...trail, target.href])),
914
+ ...(await inlineFields(siblings, from, trail)),
915
+ };
916
+ }
917
+ return inlineFields(value, from, trail);
918
+ };
919
+ const inlineFields = async (value, from, trail) => Object.fromEntries(await Promise.all(Object.entries(value).map(async ([key, item]) => [key, await inline(item, from, trail)])));
920
+ return (await inline(document, root, []));
921
+ }
922
+ async function fetchDocument(url, options) {
923
+ const response = await (options.fetch ?? globalThis.fetch)(url, {
924
+ headers: { accept: 'application/json' },
925
+ signal: AbortSignal.timeout(30_000),
926
+ });
927
+ if (!response.ok)
928
+ throw new OpenApiError(`${url} answered ${response.status}`, 'OPENAPI_REQUEST', response.status);
929
+ const text = await readCapped(response, options.maxBytes ?? 5_000_000, url);
930
+ try {
931
+ return JSON.parse(text);
932
+ }
933
+ catch {
934
+ throw new OpenApiError(`${url} is not JSON; parse a YAML document first`, 'OPENAPI_INVALID');
935
+ }
936
+ }
937
+ function pointer(document, hash, where) {
938
+ let node = document;
939
+ for (const part of hash.replace(/^#/, '').split('/').slice(1)) {
940
+ const key = decodeURIComponent(part).replace(/~1/g, '/').replace(/~0/g, '~');
941
+ node = isRecord(node) || Array.isArray(node) ? node[key] : undefined;
942
+ if (node === undefined)
943
+ throw new OpenApiError(`${where} names nothing`, 'OPENAPI_INVALID');
944
+ }
945
+ return node;
946
+ }
947
+ function stripHash(url) {
948
+ const copy = new URL(url.href);
949
+ copy.hash = '';
950
+ return copy.href;
951
+ }