swagger-digest 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,504 @@
1
+ // src/version.ts
2
+ import { createRequire } from "module";
3
+ function readPackageVersion(moduleUrl) {
4
+ try {
5
+ const require2 = createRequire(moduleUrl);
6
+ const pkg = require2("../package.json");
7
+ if (typeof pkg?.version === "string" && pkg.version) return pkg.version;
8
+ return "unknown";
9
+ } catch {
10
+ return "unknown";
11
+ }
12
+ }
13
+ var VERSION = readPackageVersion(import.meta.url);
14
+ var USER_AGENT = `swagger-digest/${VERSION}`;
15
+
16
+ // src/transformer/utils.ts
17
+ function isObject(value) {
18
+ return value !== null && typeof value === "object" && !Array.isArray(value);
19
+ }
20
+ function entries(value) {
21
+ return isObject(value) ? Object.entries(value) : [];
22
+ }
23
+ function cleanText(value) {
24
+ return typeof value === "string" ? value.trim().replace(/\s+/g, " ") : "";
25
+ }
26
+ function markdownCell(value) {
27
+ return cleanText(value).replace(/\|/g, "\\|").replace(/`/g, "\\`") || "\u2014";
28
+ }
29
+ function safeSourceUrl(value) {
30
+ try {
31
+ const url = new URL(value);
32
+ url.username = "";
33
+ url.password = "";
34
+ for (const key of [...url.searchParams.keys()]) {
35
+ if (/(token|secret|password|signature|credential|api[-_]?key|authorization|auth)/i.test(key)) {
36
+ url.searchParams.set(key, "[redacted]");
37
+ }
38
+ }
39
+ return url.toString();
40
+ } catch {
41
+ return value;
42
+ }
43
+ }
44
+ function safeComment(value) {
45
+ return cleanText(value).replace(/\*\//g, "* /").replace(/[\r\n]+/g, " ");
46
+ }
47
+ function propertyKey(value) {
48
+ return /^[$A-Z_a-z][$\w]*$/.test(value) ? value : JSON.stringify(value);
49
+ }
50
+ function toTypeScriptIdentifier(value) {
51
+ let identifier = value.replace(/[^\p{L}\p{N}_$]+/gu, "_").replace(/^([^\p{L}_$])/u, "_$1");
52
+ if (!identifier) identifier = "Unnamed";
53
+ const reserved = /* @__PURE__ */ new Set([
54
+ "abstract",
55
+ "any",
56
+ "as",
57
+ "asserts",
58
+ "async",
59
+ "await",
60
+ "boolean",
61
+ "break",
62
+ "case",
63
+ "catch",
64
+ "class",
65
+ "const",
66
+ "constructor",
67
+ "continue",
68
+ "debugger",
69
+ "declare",
70
+ "default",
71
+ "delete",
72
+ "do",
73
+ "else",
74
+ "enum",
75
+ "export",
76
+ "extends",
77
+ "false",
78
+ "finally",
79
+ "for",
80
+ "from",
81
+ "function",
82
+ "get",
83
+ "if",
84
+ "implements",
85
+ "import",
86
+ "in",
87
+ "infer",
88
+ "instanceof",
89
+ "interface",
90
+ "is",
91
+ "keyof",
92
+ "let",
93
+ "module",
94
+ "namespace",
95
+ "never",
96
+ "new",
97
+ "null",
98
+ "number",
99
+ "object",
100
+ "of",
101
+ "package",
102
+ "private",
103
+ "protected",
104
+ "public",
105
+ "readonly",
106
+ "require",
107
+ "return",
108
+ "set",
109
+ "static",
110
+ "string",
111
+ "super",
112
+ "switch",
113
+ "symbol",
114
+ "this",
115
+ "throw",
116
+ "true",
117
+ "try",
118
+ "type",
119
+ "typeof",
120
+ "undefined",
121
+ "unique",
122
+ "unknown",
123
+ "var",
124
+ "void",
125
+ "while",
126
+ "with",
127
+ "yield"
128
+ ]);
129
+ if (reserved.has(identifier)) identifier = `${identifier}Model`;
130
+ return identifier;
131
+ }
132
+
1
133
  // src/fetcher.ts
2
134
  import { load } from "cheerio";
135
+ import { parse as parseYaml2 } from "yaml";
136
+
137
+ // src/errors.ts
138
+ var FetchError = class extends Error {
139
+ kind;
140
+ constructor(kind, message) {
141
+ super(message);
142
+ this.name = "FetchError";
143
+ this.kind = kind;
144
+ }
145
+ };
146
+ var FetchSizeError = class extends FetchError {
147
+ constructor(message) {
148
+ super("size", message);
149
+ this.name = "FetchSizeError";
150
+ }
151
+ };
152
+ var BlockedNetworkError = class extends FetchError {
153
+ constructor(message) {
154
+ super("blocked", message);
155
+ this.name = "BlockedNetworkError";
156
+ }
157
+ };
158
+ var FetchNetworkError = class extends FetchError {
159
+ constructor(message) {
160
+ super("network", message);
161
+ this.name = "FetchNetworkError";
162
+ }
163
+ };
164
+ var FetchHttpError = class extends FetchError {
165
+ status;
166
+ constructor(message, status) {
167
+ super("http", message);
168
+ this.name = "FetchHttpError";
169
+ this.status = status;
170
+ }
171
+ };
172
+ function isFailFastFetchError(error) {
173
+ return error instanceof FetchError && (error.kind === "size" || error.kind === "blocked" || error.kind === "network");
174
+ }
175
+
176
+ // src/hardened-fetch.ts
177
+ import { lookup } from "dns/promises";
178
+ import { isIP } from "net";
179
+ var DEFAULT_MAX_BYTES = 10 * 1024 * 1024;
180
+ var DEFAULT_MAX_REDIRECTS = 5;
181
+ var REDIRECT_STATUSES = /* @__PURE__ */ new Set([301, 302, 303, 307, 308]);
182
+ function isPrivateAddress(address) {
183
+ const family = isIP(address);
184
+ if (family === 4) return isPrivateIPv4(address);
185
+ if (family === 6) {
186
+ const normalized = address.toLowerCase();
187
+ const mapped = normalized.match(/^::ffff:(\d+\.\d+\.\d+\.\d+)$/);
188
+ if (mapped?.[1]) return isPrivateIPv4(mapped[1]);
189
+ if (normalized === "::1" || normalized === "::") return true;
190
+ if (/^f[cd][0-9a-f]{2}(?::|$)/.test(normalized)) return true;
191
+ if (/^fe[89ab][0-9a-f](?::|$)/.test(normalized)) return true;
192
+ return false;
193
+ }
194
+ return true;
195
+ }
196
+ function isPrivateIPv4(address) {
197
+ const parts = address.split(".").map(Number);
198
+ if (parts.length !== 4 || parts.some((part) => Number.isNaN(part))) return true;
199
+ const [a, b] = parts;
200
+ if (a === 0) return true;
201
+ if (a === 10) return true;
202
+ if (a === 127) return true;
203
+ if (a === 169 && b === 254) return true;
204
+ if (a === 172 && b >= 16 && b <= 31) return true;
205
+ if (a === 192 && b === 168) return true;
206
+ return false;
207
+ }
208
+ async function assertPublicHost(hostname) {
209
+ if (isIP(hostname) !== 0) {
210
+ if (isPrivateAddress(hostname)) {
211
+ throw new BlockedNetworkError(
212
+ `Blocked request to private network address (host resolved to a loopback/private/metadata address). Enable this only for hosts you trust; use it against internal corporate APIs without this option.`
213
+ );
214
+ }
215
+ return;
216
+ }
217
+ let addresses;
218
+ try {
219
+ addresses = await lookup(hostname, { all: true, verbatim: true });
220
+ } catch (error) {
221
+ throw new FetchNetworkError(
222
+ `DNS resolution failed for ${hostname}: ${error instanceof Error ? error.message : String(error)}`
223
+ );
224
+ }
225
+ if (!addresses.length) {
226
+ throw new FetchNetworkError(`DNS resolution returned no addresses for ${hostname}`);
227
+ }
228
+ for (const { address } of addresses) {
229
+ if (isPrivateAddress(address)) {
230
+ throw new BlockedNetworkError(
231
+ `Blocked request to ${hostname}: it resolves to a private/loopback/metadata address while blockPrivateNetworks is enabled.`
232
+ );
233
+ }
234
+ }
235
+ }
236
+ function normalizeHeaders(headers) {
237
+ return Object.fromEntries(Object.entries(headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
238
+ }
239
+ async function fetchWithLimits(url, options) {
240
+ const maxBytes = options.maxBytes ?? DEFAULT_MAX_BYTES;
241
+ const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;
242
+ const defaultHeaders = normalizeHeaders({
243
+ accept: "application/json, application/yaml, text/yaml, text/plain, text/html;q=0.8, */*;q=0.5",
244
+ "user-agent": USER_AGENT
245
+ });
246
+ const userHeaders = normalizeHeaders(options.headers ?? {});
247
+ return await follow(url, defaultHeaders, userHeaders, options, maxBytes, maxRedirects);
248
+ }
249
+ async function follow(url, defaultHeaders, userHeaders, options, maxBytes, redirectsLeft) {
250
+ let target;
251
+ try {
252
+ target = new URL(url);
253
+ } catch {
254
+ throw new FetchNetworkError(`Invalid URL: ${url}`);
255
+ }
256
+ if (target.protocol !== "http:" && target.protocol !== "https:") {
257
+ throw new FetchNetworkError(`Only HTTP and HTTPS URLs are supported: ${url}`);
258
+ }
259
+ if (options.blockPrivateNetworks) {
260
+ await assertPublicHost(target.hostname);
261
+ }
262
+ const sameOrigin = !options.allowedHeaderOrigin || !options.headers || target.origin === options.allowedHeaderOrigin;
263
+ if (!sameOrigin && options.allowedHeaderOrigin && options.headers && Object.keys(options.headers).length > 0) {
264
+ options.onCrossOriginHeadersDropped?.(target.origin);
265
+ }
266
+ const requestHeaders = sameOrigin ? { ...defaultHeaders, ...userHeaders } : defaultHeaders;
267
+ const controller = new AbortController();
268
+ const timeout = AbortSignal.timeout(options.timeoutMs);
269
+ timeout.addEventListener("abort", () => controller.abort(), { once: true });
270
+ let response;
271
+ try {
272
+ response = await fetch(target, {
273
+ headers: requestHeaders,
274
+ redirect: "manual",
275
+ signal: controller.signal
276
+ });
277
+ } catch (error) {
278
+ if (error instanceof FetchError) throw error;
279
+ const reason = error instanceof Error ? error.message : String(error);
280
+ throw new FetchNetworkError(`Request to ${target} failed: ${describeNetworkError(reason)}`);
281
+ }
282
+ if (REDIRECT_STATUSES.has(response.status)) {
283
+ const location = response.headers.get("location");
284
+ if (!location) {
285
+ throw new FetchNetworkError(`Redirect response without a Location header from ${target}`);
286
+ }
287
+ if (redirectsLeft <= 0) {
288
+ throw new FetchNetworkError(`Too many redirects (limit ${options.maxRedirects ?? 5}) while fetching ${url}`);
289
+ }
290
+ try {
291
+ await response.arrayBuffer();
292
+ } catch {
293
+ }
294
+ const next = new URL(response.headers.get("location") ?? "", target);
295
+ return follow(next.toString(), defaultHeaders, userHeaders, options, maxBytes, redirectsLeft - 1);
296
+ }
297
+ if (!response.ok) {
298
+ await discardBodySafely(response);
299
+ throw new FetchHttpError(`HTTP ${response.status} ${response.statusText} for ${url}`, response.status);
300
+ }
301
+ const contentLength = response.headers.get("content-length");
302
+ if (contentLength !== null) {
303
+ const declared = Number(contentLength);
304
+ if (Number.isFinite(declared) && declared > maxBytes) {
305
+ await discardBodySafely(response);
306
+ throw new FetchSizeError(
307
+ `Response from ${target} is ${declared} bytes, exceeding the ${maxBytes} byte limit (content-length). Increase --max-bytes if the spec is genuinely large.`
308
+ );
309
+ }
310
+ }
311
+ const text = await readBodyWithCap(response, target.toString(), maxBytes);
312
+ return {
313
+ text,
314
+ contentType: response.headers.get("content-type") ?? "",
315
+ status: response.status,
316
+ url: target.toString()
317
+ };
318
+ }
319
+ function describeNetworkError(reason) {
320
+ if (/timeouterror|timed out|aborted/i.test(reason)) return "request timed out";
321
+ if (/enotfound|getaddrinfo/i.test(reason)) return "host not found (DNS)";
322
+ if (/econnrefused/i.test(reason)) return "connection refused";
323
+ return reason;
324
+ }
325
+ async function readBodyWithCap(response, url, maxBytes) {
326
+ const body = response.body;
327
+ if (!body) return "";
328
+ const chunks = [];
329
+ let total = 0;
330
+ try {
331
+ for await (const chunk of body) {
332
+ chunks.push(chunk);
333
+ total += chunk.byteLength;
334
+ if (total > maxBytes) {
335
+ try {
336
+ await body.cancel();
337
+ } catch {
338
+ }
339
+ throw new FetchSizeError(
340
+ `Response body from ${url} exceeded the ${maxBytes} byte limit while streaming (content-length was ${response.headers.get("content-length") ?? "not declared"}). Use --max-bytes to raise the cap if the spec is genuinely large.`
341
+ );
342
+ }
343
+ }
344
+ } catch (error) {
345
+ if (error instanceof FetchSizeError) throw error;
346
+ throw new FetchNetworkError(`Failed to read response body from ${url}: ${describeNetworkError(String(error))}`);
347
+ }
348
+ const decoder = new TextDecoder("utf-8");
349
+ const parts = [];
350
+ for (const chunk of chunks) parts.push(decoder.decode(chunk, { stream: true }));
351
+ parts.push(decoder.decode());
352
+ return parts.join("");
353
+ }
354
+ async function discardBodySafely(response) {
355
+ try {
356
+ await response.arrayBuffer();
357
+ } catch {
358
+ }
359
+ }
360
+
361
+ // src/bundler.ts
3
362
  import { parse as parseYaml } from "yaml";
363
+ var MAX_EXTERNAL_DOCUMENTS = 5;
364
+ async function bundleExternalRefs(spec, sourceUrl, fetchDocument) {
365
+ if (!sourceUrl) return spec;
366
+ const externalRefs = collectExternalRefs(spec);
367
+ if (externalRefs.size === 0) return spec;
368
+ const schemaBucket = openSchemaBucket(spec);
369
+ const usedNames = new Set(Object.keys(schemaBucket));
370
+ const mapping = /* @__PURE__ */ new Map();
371
+ let fetched = 0;
372
+ for (const ref of externalRefs) {
373
+ if (fetched >= MAX_EXTERNAL_DOCUMENTS) break;
374
+ const { docUrl, pointer } = splitExternalRef(ref, sourceUrl);
375
+ if (!docUrl) continue;
376
+ try {
377
+ const text = await fetchDocument(docUrl);
378
+ const parsed = parseDocumentText(text);
379
+ if (parsed === void 0) continue;
380
+ fetched += 1;
381
+ const target = resolvePointer(parsed, pointer);
382
+ if (target === void 0) continue;
383
+ const localName = uniqueNameFor(localNameFor(docUrl, pointer), usedNames);
384
+ schemaBucket[localName] = target;
385
+ mapping.set(ref, localName);
386
+ } catch {
387
+ }
388
+ }
389
+ if (mapping.size === 0) return spec;
390
+ const rewritten = rewriteRefs(spec, mapping);
391
+ return isObject(rewritten) ? rewritten : spec;
392
+ }
393
+ function collectExternalRefs(node) {
394
+ const found = /* @__PURE__ */ new Set();
395
+ const walk = (value) => {
396
+ if (Array.isArray(value)) {
397
+ for (const item of value) walk(item);
398
+ return;
399
+ }
400
+ if (!isObject(value)) return;
401
+ for (const [key, refValue] of Object.entries(value)) {
402
+ if (key === "$ref" && typeof refValue === "string" && !refValue.startsWith("#")) {
403
+ found.add(refValue);
404
+ continue;
405
+ }
406
+ if (key === "example" || key === "examples" || key === "externalDocs") continue;
407
+ walk(refValue);
408
+ }
409
+ };
410
+ walk(node);
411
+ return found;
412
+ }
413
+ function rewriteRefs(node, mapping) {
414
+ if (Array.isArray(node)) return node.map((item) => rewriteRefs(item, mapping));
415
+ if (!isObject(node)) return node;
416
+ const result = {};
417
+ for (const [key, value] of Object.entries(node)) {
418
+ if (key === "$ref" && typeof value === "string") {
419
+ const localName = mapping.get(value);
420
+ if (localName === void 0) {
421
+ result[key] = value;
422
+ continue;
423
+ }
424
+ result[key] = `#/${bucketPath(bucketStyle(value))}/${localName}`;
425
+ continue;
426
+ }
427
+ result[key] = rewriteRefs(value, mapping);
428
+ }
429
+ return result;
430
+ }
431
+ function bucketStyle(ref) {
432
+ return ref.includes("/components/") ? "openapi" : "swagger";
433
+ }
434
+ function bucketPath(style) {
435
+ return style === "openapi" ? "components/schemas" : "definitions";
436
+ }
437
+ function openSchemaBucket(spec) {
438
+ if (isObject(spec.components)) {
439
+ if (!isObject(spec.components.schemas)) spec.components.schemas = {};
440
+ return spec.components.schemas;
441
+ }
442
+ if (!isObject(spec.definitions)) spec.definitions = {};
443
+ return spec.definitions;
444
+ }
445
+ function splitExternalRef(ref, baseUrl) {
446
+ const hashIndex = ref.indexOf("#");
447
+ if (hashIndex < 0) return { docUrl: void 0, pointer: "" };
448
+ const docPart = ref.slice(0, hashIndex);
449
+ const pointer = ref.slice(hashIndex + 1);
450
+ try {
451
+ return { docUrl: new URL(docPart, baseUrl).toString(), pointer };
452
+ } catch {
453
+ return { docUrl: void 0, pointer };
454
+ }
455
+ }
456
+ function parseDocumentText(text) {
457
+ try {
458
+ return JSON.parse(text);
459
+ } catch {
460
+ try {
461
+ return parseYaml(text);
462
+ } catch {
463
+ return void 0;
464
+ }
465
+ }
466
+ }
467
+ function resolvePointer(root, pointer) {
468
+ if (!pointer.startsWith("/")) return void 0;
469
+ let current = root;
470
+ for (const rawPart of pointer.slice(1).split("/")) {
471
+ const part = rawPart.replace(/~1/g, "/").replace(/~0/g, "~");
472
+ if (!isObject(current)) return void 0;
473
+ current = current[part];
474
+ }
475
+ return current;
476
+ }
477
+ function localNameFor(docUrl, pointer) {
478
+ try {
479
+ const url = new URL(docUrl);
480
+ const file = url.pathname.split("/").pop() ?? "external";
481
+ const stem = file.replace(/\.(json|ya?ml)$/i, "").replace(/[^A-Za-z0-9_$]+/g, "_") || "external";
482
+ const segments = pointer.split("/").filter(Boolean);
483
+ const pointerName = segments.length > 0 ? segments[segments.length - 1] : void 0;
484
+ const cleanedPointer = pointerName?.replace(/[^A-Za-z0-9_$]+/g, "_");
485
+ return [stem, cleanedPointer].filter(Boolean).join("_") || "external";
486
+ } catch {
487
+ return "external";
488
+ }
489
+ }
490
+ function uniqueNameFor(base, used) {
491
+ let candidate = base;
492
+ let suffix = 2;
493
+ while (used.has(candidate)) {
494
+ candidate = `${candidate}${suffix}`;
495
+ suffix += 1;
496
+ }
497
+ used.add(candidate);
498
+ return candidate;
499
+ }
500
+
501
+ // src/fetcher.ts
4
502
  var SPEC_FILE_EXTENSION = /\.(?:json|ya?ml)$/i;
5
503
  var FALLBACK_PATHS = ["/v3/api-docs", "/swagger/v1/swagger.json", "/swagger.json", "/openapi.json", "/api-docs"];
6
504
  function isRecord(value) {
@@ -14,7 +512,7 @@ function isOpenApiSpec(value) {
14
512
  return hasVersion && hasInfo && hasPaths;
15
513
  }
16
514
  function parseSpecText(text, source = "response") {
17
- const parsers = [() => JSON.parse(text), () => parseYaml(text)];
515
+ const parsers = [() => JSON.parse(text), () => parseYaml2(text)];
18
516
  const errors = [];
19
517
  for (const parser of parsers) {
20
518
  try {
@@ -30,62 +528,164 @@ function parseSpecText(text, source = "response") {
30
528
  function isDirectSpecUrl(url) {
31
529
  return SPEC_FILE_EXTENSION.test(url.pathname);
32
530
  }
33
- async function fetchText(url, timeoutMs) {
34
- const response = await fetch(url, {
35
- headers: {
36
- accept: "application/json, application/yaml, text/yaml, text/plain, text/html;q=0.8, */*;q=0.5",
37
- "user-agent": "swagger-digest/0.1.0"
38
- },
39
- signal: AbortSignal.timeout(timeoutMs),
40
- redirect: "follow"
531
+ async function fetchText(url, ctx) {
532
+ const response = await fetchWithLimits(url, {
533
+ timeoutMs: ctx.timeoutMs,
534
+ maxBytes: ctx.maxBytes,
535
+ maxRedirects: ctx.maxRedirects,
536
+ blockPrivateNetworks: ctx.blockPrivateNetworks,
537
+ headers: ctx.headers,
538
+ allowedHeaderOrigin: ctx.allowedHeaderOrigin,
539
+ onCrossOriginHeadersDropped: ctx.onCrossOriginHeadersDropped
41
540
  });
42
- if (!response.ok) {
43
- throw new Error(`HTTP ${response.status} ${response.statusText} for ${url}`);
44
- }
45
541
  return {
46
- text: await response.text(),
47
- contentType: response.headers.get("content-type") ?? ""
542
+ text: response.text,
543
+ contentType: response.contentType
48
544
  };
49
545
  }
50
546
  function extractScripts(html) {
51
547
  const $ = load(html);
52
548
  const inline = [];
53
549
  const external = [];
54
- $("script").each((_index, element) => {
550
+ for (const element of $("script").toArray()) {
55
551
  const body = $(element).html();
56
552
  if (body?.trim()) inline.push(body);
57
553
  const src = $(element).attr("src");
58
554
  if (src?.trim()) external.push(src.trim());
59
- });
555
+ }
60
556
  return { inline, external };
61
557
  }
62
558
  function extractSpecUrlFromScripts(scripts, pageUrl) {
63
- const urlPattern = /\burl\s*:\s*(["'])(.*?)\1/s;
64
- for (const script of scripts) {
65
- const match = urlPattern.exec(script);
66
- if (!match?.[2]) continue;
67
- const candidate = match[2].trim();
68
- if (!candidate || candidate.startsWith("data:")) continue;
559
+ return extractSpecUrlsFromScripts(scripts, pageUrl)[0];
560
+ }
561
+ function extractSpecUrlsFromScripts(scripts, pageUrl) {
562
+ const found = [];
563
+ const pushCandidate = (candidate) => {
564
+ const trimmed = candidate.trim();
565
+ if (!trimmed || trimmed.startsWith("data:")) return;
69
566
  try {
70
- return new URL(candidate, pageUrl).toString();
567
+ const resolved = new URL(trimmed, pageUrl).toString();
568
+ if (!found.includes(resolved)) found.push(resolved);
71
569
  } catch {
72
570
  }
571
+ };
572
+ for (const script of scripts) {
573
+ const urlsArray = findObjectLiteralArrayAfterKey(script, "urls");
574
+ if (urlsArray) {
575
+ const entryPattern = /\burl\s*:\s*(["'])(.*?)\1/gs;
576
+ for (const match of urlsArray.matchAll(entryPattern)) {
577
+ pushCandidate(match[2] ?? "");
578
+ }
579
+ }
580
+ if (!urlsArray) {
581
+ const urlPattern = /\burl\s*:\s*(["'])(.*?)\1/s;
582
+ const match = urlPattern.exec(script);
583
+ if (match?.[2]) pushCandidate(match[2]);
584
+ }
585
+ }
586
+ return found;
587
+ }
588
+ function findObjectLiteralArrayAfterKey(source, key) {
589
+ const keyPattern = new RegExp(`\\b${key}\\s*:`);
590
+ const match = keyPattern.exec(source);
591
+ if (!match) return void 0;
592
+ let index = match.index + match[0].length;
593
+ while (/\s/.test(source[index] ?? "")) index += 1;
594
+ if (source[index] !== "[") return void 0;
595
+ const start = index;
596
+ let depth = 0;
597
+ let quote;
598
+ let escaped = false;
599
+ let lineComment = false;
600
+ let blockComment = false;
601
+ for (; index < source.length; index += 1) {
602
+ const char = source[index] ?? "";
603
+ const next = source[index + 1] ?? "";
604
+ if (lineComment) {
605
+ if (char === "\n") lineComment = false;
606
+ continue;
607
+ }
608
+ if (blockComment) {
609
+ if (char === "*" && next === "/") {
610
+ blockComment = false;
611
+ index += 1;
612
+ }
613
+ continue;
614
+ }
615
+ if (quote) {
616
+ if (escaped) {
617
+ escaped = false;
618
+ } else if (char === "\\") {
619
+ escaped = true;
620
+ } else if (char === quote) {
621
+ quote = void 0;
622
+ }
623
+ continue;
624
+ }
625
+ if (char === "/" && next === "/") {
626
+ lineComment = true;
627
+ index += 1;
628
+ continue;
629
+ }
630
+ if (char === "/" && next === "*") {
631
+ blockComment = true;
632
+ index += 1;
633
+ continue;
634
+ }
635
+ if (char === '"' || char === "'" || char === "`") {
636
+ quote = char;
637
+ continue;
638
+ }
639
+ if (char === "[") depth += 1;
640
+ if (char === "]") {
641
+ depth -= 1;
642
+ if (depth === 0) return source.slice(start, index + 1);
643
+ }
73
644
  }
74
645
  return void 0;
75
646
  }
76
- function extractSpecUrl(scripts, pageUrl) {
77
- const fromScript = extractSpecUrlFromScripts(scripts, pageUrl);
78
- if (fromScript) return fromScript;
647
+ function extractSpecUrls(scripts, pageUrl) {
648
+ const fromScripts = extractSpecUrlsFromScripts(scripts, pageUrl);
79
649
  const queryUrl = pageUrl.searchParams.get("url");
80
650
  if (queryUrl) {
81
651
  try {
82
- return new URL(queryUrl, pageUrl).toString();
652
+ const resolved = new URL(queryUrl, pageUrl).toString();
653
+ if (!fromScripts.includes(resolved)) fromScripts.push(resolved);
83
654
  } catch {
84
655
  }
85
656
  }
657
+ return fromScripts;
658
+ }
659
+ async function probeSpecUrls(candidates, ctx, recordFailure, onMultiple) {
660
+ if (candidates.length === 0) return void 0;
661
+ if (candidates.length > 1) {
662
+ onMultiple?.(candidates[0] ?? "", candidates);
663
+ }
664
+ const batchSize = 4;
665
+ for (let start = 0; start < candidates.length; start += batchSize) {
666
+ const batch = candidates.slice(start, start + batchSize);
667
+ const attempts = await Promise.all(
668
+ batch.map(async (candidate) => {
669
+ try {
670
+ return { candidate, spec: await tryFetchSpec(candidate, ctx) };
671
+ } catch (error) {
672
+ return { candidate, error };
673
+ }
674
+ })
675
+ );
676
+ const winner = attempts.find(
677
+ (attempt) => attempt.spec !== void 0
678
+ );
679
+ if (winner) {
680
+ return { spec: winner.spec, sourceUrl: winner.candidate, retrievalMethod: "html-config" };
681
+ }
682
+ for (const attempt of attempts) {
683
+ recordFailure("Configured spec", attempt.error, attempt.candidate);
684
+ }
685
+ }
86
686
  return void 0;
87
687
  }
88
- async function fetchExternalConfigScripts(srcs, pageUrl, timeoutMs, errors) {
688
+ async function fetchExternalConfigScripts(srcs, pageUrl, ctx, errors) {
89
689
  const candidates = srcs.map((src) => {
90
690
  try {
91
691
  return new URL(src, pageUrl);
@@ -100,7 +700,7 @@ async function fetchExternalConfigScripts(srcs, pageUrl, timeoutMs, errors) {
100
700
  const scripts = [];
101
701
  for (const candidate of candidates.slice(0, 10)) {
102
702
  try {
103
- const response = await fetchText(candidate.toString(), timeoutMs);
703
+ const response = await fetchText(candidate.toString(), ctx);
104
704
  scripts.push(response.text);
105
705
  if (extractSpecUrlFromScripts(scripts, pageUrl) || scripts.some((script) => findObjectLiteralAfterKey(script, "spec"))) {
106
706
  break;
@@ -195,7 +795,7 @@ function findObjectLiteralAfterKey(source, key) {
195
795
  function parseInlineObjectLiteral(literal) {
196
796
  const candidates = [
197
797
  () => JSON.parse(literal),
198
- () => parseYaml(literal),
798
+ () => parseYaml2(literal),
199
799
  // Swagger config often uses JS object syntax with trailing commas.
200
800
  () => JSON.parse(literal.replace(/,\s*([}\]])/g, "$1"))
201
801
  ];
@@ -217,11 +817,26 @@ function extractInlineSpec(scripts) {
217
817
  }
218
818
  return void 0;
219
819
  }
220
- async function tryFetchSpec(url, timeoutMs) {
221
- const response = await fetchText(url, timeoutMs);
222
- return parseSpecText(response.text, url);
820
+ async function tryFetchSpec(url, ctx) {
821
+ const response = await fetchText(url, ctx);
822
+ const parsed = parseSpecText(response.text, url);
823
+ try {
824
+ return await bundleExternalRefs(parsed, url, async (docUrl) => (await fetchText(docUrl, ctx)).text);
825
+ } catch {
826
+ return parsed;
827
+ }
828
+ }
829
+ function headersForBrowserRequest(requestUrl, headers, allowedOrigin) {
830
+ if (!headers || Object.keys(headers).length === 0) return void 0;
831
+ let requestOrigin;
832
+ try {
833
+ requestOrigin = new URL(requestUrl).origin;
834
+ } catch {
835
+ return void 0;
836
+ }
837
+ return requestOrigin === allowedOrigin ? headers : void 0;
223
838
  }
224
- async function tryBrowserFallback(pageUrl) {
839
+ async function tryBrowserFallback(pageUrl, headers) {
225
840
  let playwright;
226
841
  try {
227
842
  const optionalModuleName = "playwright";
@@ -234,6 +849,20 @@ async function tryBrowserFallback(pageUrl) {
234
849
  const browser = await playwright.chromium.launch({ headless: true });
235
850
  try {
236
851
  const page = await browser.newPage();
852
+ const allowedOrigin = new URL(pageUrl).origin;
853
+ if (headers && Object.keys(headers).length > 0) {
854
+ const browserHeaders = Object.fromEntries(
855
+ Object.entries(headers).map(([name, value]) => [name.toLowerCase(), value])
856
+ );
857
+ await page.route("**/*", async (route) => {
858
+ const extra = headersForBrowserRequest(route.request().url(), browserHeaders, allowedOrigin);
859
+ if (!extra) {
860
+ await route.continue();
861
+ return;
862
+ }
863
+ await route.continue({ headers: { ...route.request().headers(), ...extra } });
864
+ });
865
+ }
237
866
  await page.goto(pageUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
238
867
  await page.waitForFunction(
239
868
  () => {
@@ -241,7 +870,8 @@ async function tryBrowserFallback(pageUrl) {
241
870
  try {
242
871
  const candidate = w.ui?.spec?.();
243
872
  const document = candidate && typeof candidate.toJSON === "function" ? candidate.toJSON() : candidate;
244
- return Boolean(document?.openapi || document?.swagger || w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
873
+ const doc = document;
874
+ return Boolean(doc?.openapi || doc?.swagger || w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
245
875
  } catch {
246
876
  return Boolean(w.swaggerDoc?.openapi || w.swaggerDoc?.swagger);
247
877
  }
@@ -286,18 +916,42 @@ async function fetchOpenApiSpec(input, options = {}) {
286
916
  throw new Error("Only HTTP and HTTPS URLs are supported.");
287
917
  }
288
918
  const timeoutMs = options.timeoutMs ?? 15e3;
919
+ const ctx = {
920
+ timeoutMs,
921
+ maxBytes: options.maxBytes,
922
+ maxRedirects: options.maxRedirects,
923
+ blockPrivateNetworks: options.blockPrivateNetworks,
924
+ allowedHeaderOrigin: pageUrl.origin,
925
+ headers: options.headers,
926
+ onCrossOriginHeadersDropped: (origin) => options.warn?.(
927
+ `Custom headers were withheld: ${origin} is a different origin from the URL you supplied. Only tool defaults were sent.`
928
+ )
929
+ };
289
930
  const errors = [];
931
+ const recordFailure = (label, error, subject) => {
932
+ if (isFailFastFetchError(error)) throw error;
933
+ const prefix = subject ? `${label} (${subject}):` : `${label}:`;
934
+ errors.push(`${prefix} ${error instanceof Error ? error.message : String(error)}`);
935
+ };
290
936
  if (isDirectSpecUrl(pageUrl)) {
291
937
  try {
292
- const spec = await tryFetchSpec(pageUrl.toString(), timeoutMs);
938
+ const spec = await tryFetchSpec(pageUrl.toString(), ctx);
293
939
  return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "direct-spec" };
294
940
  } catch (error) {
295
- errors.push(`Direct spec: ${error instanceof Error ? error.message : String(error)}`);
941
+ recordFailure("Direct spec", error);
942
+ }
943
+ }
944
+ if (options.specUrl) {
945
+ try {
946
+ const spec = await tryFetchSpec(options.specUrl, ctx);
947
+ return { spec, sourceUrl: options.specUrl, retrievalMethod: "direct-spec" };
948
+ } catch (error) {
949
+ recordFailure("Specified spec (--spec)", error, options.specUrl);
296
950
  }
297
951
  }
298
952
  let html;
299
953
  try {
300
- const response = await fetchText(pageUrl.toString(), timeoutMs);
954
+ const response = await fetchText(pageUrl.toString(), ctx);
301
955
  try {
302
956
  const spec = parseSpecText(response.text, pageUrl.toString());
303
957
  return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "direct-spec" };
@@ -305,35 +959,29 @@ async function fetchOpenApiSpec(input, options = {}) {
305
959
  html = response.text;
306
960
  }
307
961
  } catch (error) {
308
- errors.push(`Swagger UI page: ${error instanceof Error ? error.message : String(error)}`);
962
+ recordFailure("Swagger UI page", error);
309
963
  }
310
964
  if (html !== void 0) {
311
965
  const pageScripts = extractScripts(html);
312
- let scripts = pageScripts.inline;
313
- let specUrl = extractSpecUrl(scripts, pageUrl);
966
+ const scripts = pageScripts.inline;
314
967
  const inlineSpec = extractInlineSpec(scripts);
315
968
  if (inlineSpec) {
316
969
  return { spec: inlineSpec, sourceUrl: pageUrl.toString(), retrievalMethod: "inline-spec" };
317
970
  }
318
- if (specUrl) {
319
- try {
320
- const spec = await tryFetchSpec(specUrl, timeoutMs);
321
- return { spec, sourceUrl: specUrl, retrievalMethod: "html-config" };
322
- } catch (error) {
323
- errors.push(`Configured spec (${specUrl}): ${error instanceof Error ? error.message : String(error)}`);
324
- }
325
- }
326
- const externalScripts = await fetchExternalConfigScripts(pageScripts.external, pageUrl, timeoutMs, errors);
327
- scripts = [...scripts, ...externalScripts];
328
- specUrl = extractSpecUrl(scripts, pageUrl);
329
- if (specUrl && specUrl !== pageUrl.searchParams.get("url")) {
330
- try {
331
- const spec = await tryFetchSpec(specUrl, timeoutMs);
332
- return { spec, sourceUrl: specUrl, retrievalMethod: "html-config" };
333
- } catch (error) {
334
- errors.push(`Configured spec (${specUrl}): ${error instanceof Error ? error.message : String(error)}`);
971
+ const noteMultiple = (picked, all) => {
972
+ if (all.length > 1) {
973
+ options.warn?.(
974
+ `Multiple spec definitions found (${String(all.length)}); using ${picked}. Others: ${all.filter((url) => url !== picked).join(", ")}.`
975
+ );
335
976
  }
336
- }
977
+ };
978
+ const fromInline = await probeSpecUrls(extractSpecUrls(scripts, pageUrl), ctx, recordFailure, noteMultiple);
979
+ if (fromInline) return fromInline;
980
+ const externalScripts = await fetchExternalConfigScripts(pageScripts.external, pageUrl, ctx, errors);
981
+ const inlineUrls = extractSpecUrls(scripts, pageUrl);
982
+ const externalUrls = extractSpecUrls(externalScripts, pageUrl).filter((url) => !inlineUrls.includes(url));
983
+ const fromExternal = await probeSpecUrls([...inlineUrls, ...externalUrls], ctx, recordFailure, noteMultiple);
984
+ if (fromExternal) return fromExternal;
337
985
  for (const script of scripts) {
338
986
  const literal = findObjectLiteralAfterKey(script, "spec");
339
987
  if (!literal) continue;
@@ -341,17 +989,33 @@ async function fetchOpenApiSpec(input, options = {}) {
341
989
  if (spec) return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "inline-spec" };
342
990
  }
343
991
  }
344
- for (const candidate of getFallbackUrls(pageUrl)) {
345
- try {
346
- const spec = await tryFetchSpec(candidate, timeoutMs);
347
- return { spec, sourceUrl: candidate, retrievalMethod: "fallback-path" };
348
- } catch (error) {
349
- errors.push(`${candidate}: ${error instanceof Error ? error.message : String(error)}`);
992
+ const probeCtx = { ...ctx, timeoutMs: options.probeTimeoutMs ?? 5e3 };
993
+ const candidates = getFallbackUrls(pageUrl);
994
+ const probeBatchSize = 4;
995
+ for (let start = 0; start < candidates.length; start += probeBatchSize) {
996
+ const batch = candidates.slice(start, start + probeBatchSize);
997
+ const attempts = await Promise.all(
998
+ batch.map(async (candidate) => {
999
+ try {
1000
+ return { candidate, spec: await tryFetchSpec(candidate, probeCtx) };
1001
+ } catch (error) {
1002
+ return { candidate, error };
1003
+ }
1004
+ })
1005
+ );
1006
+ const winner = attempts.find(
1007
+ (attempt) => attempt.spec !== void 0
1008
+ );
1009
+ if (winner) {
1010
+ return { spec: winner.spec, sourceUrl: winner.candidate, retrievalMethod: "fallback-path" };
1011
+ }
1012
+ for (const attempt of attempts) {
1013
+ recordFailure(attempt.candidate, attempt.error);
350
1014
  }
351
1015
  }
352
1016
  if (options.useBrowserFallback !== false) {
353
1017
  try {
354
- const spec = await tryBrowserFallback(pageUrl.toString());
1018
+ const spec = await tryBrowserFallback(pageUrl.toString(), ctx.headers);
355
1019
  return { spec, sourceUrl: pageUrl.toString(), retrievalMethod: "headless-browser" };
356
1020
  } catch (error) {
357
1021
  errors.push(`Headless browser: ${error instanceof Error ? error.message : String(error)}`);
@@ -366,171 +1030,147 @@ Check that the URL is reachable from this terminal and points to a Swagger UI pa
366
1030
  );
367
1031
  }
368
1032
 
369
- // src/transformer.ts
370
- var HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
371
- var COMMON_ERROR_CODES = /* @__PURE__ */ new Set([
372
- "400",
373
- "401",
374
- "403",
375
- "404",
376
- "405",
377
- "409",
378
- "415",
379
- "422",
380
- "429",
381
- "500",
382
- "501",
383
- "502",
384
- "503",
385
- "default"
386
- ]);
387
- function isObject(value) {
388
- return value !== null && typeof value === "object" && !Array.isArray(value);
389
- }
390
- function entries(value) {
391
- return isObject(value) ? Object.entries(value) : [];
392
- }
393
- function cleanText(value) {
394
- return typeof value === "string" ? value.trim().replace(/\s+/g, " ") : "";
395
- }
396
- function markdownCell(value) {
397
- return cleanText(value).replace(/\|/g, "\\|").replace(/`/g, "\\`") || "\u2014";
398
- }
399
- function safeSourceUrl(value) {
400
- try {
401
- const url = new URL(value);
402
- url.username = "";
403
- url.password = "";
404
- for (const key of [...url.searchParams.keys()]) {
405
- if (/(token|secret|password|signature|credential|api[-_]?key|authorization|auth)/i.test(key)) {
406
- url.searchParams.set(key, "[redacted]");
1033
+ // src/tokens.ts
1034
+ var MODEL_CONFIGS = {
1035
+ claude: { name: "Claude (Sonnet / Opus)", contextWindow: 2e5, ratio: 1.08 },
1036
+ openai: { name: "OpenAI (GPT-4o / o-series)", contextWindow: 128e3, ratio: 0.95 },
1037
+ gemini: { name: "Google Gemini (Pro / Flash)", contextWindow: 1e6, ratio: 0.9 },
1038
+ deepseek: { name: "DeepSeek (V3 / R1)", contextWindow: 128e3, ratio: 0.97 },
1039
+ llama: { name: "Meta Llama (3.3 / 4)", contextWindow: 128e3, ratio: 0.98 }
1040
+ };
1041
+ function estimateTokens(text) {
1042
+ const characters = text.length;
1043
+ if (characters === 0) {
1044
+ const zeroModel = ({ name, contextWindow }) => ({
1045
+ name,
1046
+ tokens: 0,
1047
+ contextWindow,
1048
+ contextPercent: 0
1049
+ });
1050
+ return {
1051
+ characters: 0,
1052
+ models: {
1053
+ claude: zeroModel(MODEL_CONFIGS.claude),
1054
+ openai: zeroModel(MODEL_CONFIGS.openai),
1055
+ gemini: zeroModel(MODEL_CONFIGS.gemini),
1056
+ deepseek: zeroModel(MODEL_CONFIGS.deepseek),
1057
+ llama: zeroModel(MODEL_CONFIGS.llama)
407
1058
  }
408
- }
409
- return url.toString();
410
- } catch {
411
- return value;
1059
+ };
412
1060
  }
1061
+ const wordCount = (text.match(/\b[\w$]+\b/gu) || []).length;
1062
+ const syntaxSymbols = (text.match(/[{}[\]()<>=:;,."`|/-]/g) || []).length;
1063
+ const whitespaces = (text.match(/\s+/g) || []).length;
1064
+ const baseTokens = wordCount * 1.18 + syntaxSymbols * 0.42 + whitespaces * 0.05;
1065
+ const calculate = (name, ratio, contextWindow) => {
1066
+ const tokens = Math.max(1, Math.round(baseTokens * ratio));
1067
+ const contextPercent = Number((tokens / contextWindow * 100).toFixed(1));
1068
+ return { name, tokens, contextWindow, contextPercent };
1069
+ };
1070
+ return {
1071
+ characters,
1072
+ models: {
1073
+ claude: calculate(MODEL_CONFIGS.claude.name, MODEL_CONFIGS.claude.ratio, MODEL_CONFIGS.claude.contextWindow),
1074
+ openai: calculate(MODEL_CONFIGS.openai.name, MODEL_CONFIGS.openai.ratio, MODEL_CONFIGS.openai.contextWindow),
1075
+ gemini: calculate(MODEL_CONFIGS.gemini.name, MODEL_CONFIGS.gemini.ratio, MODEL_CONFIGS.gemini.contextWindow),
1076
+ deepseek: calculate(
1077
+ MODEL_CONFIGS.deepseek.name,
1078
+ MODEL_CONFIGS.deepseek.ratio,
1079
+ MODEL_CONFIGS.deepseek.contextWindow
1080
+ ),
1081
+ llama: calculate(MODEL_CONFIGS.llama.name, MODEL_CONFIGS.llama.ratio, MODEL_CONFIGS.llama.contextWindow)
1082
+ }
1083
+ };
413
1084
  }
414
- function safeComment(value) {
415
- return cleanText(value).replace(/\*\//g, "* /").replace(/[\r\n]+/g, " ");
1085
+
1086
+ // src/transformer/security.ts
1087
+ function describeSecurityScheme(name, scheme) {
1088
+ const type = cleanText(scheme.type).toLowerCase();
1089
+ const label = cleanText(scheme.description);
1090
+ let details = name;
1091
+ if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "bearer")
1092
+ details += " (Bearer token via Authorization header)";
1093
+ else if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "basic")
1094
+ details += " (HTTP Basic via Authorization header)";
1095
+ else if (type === "apikey" || type === "apiKey")
1096
+ details += ` (API key in ${cleanText(scheme.in) || "header"}: ${cleanText(scheme.name) || "key"})`;
1097
+ else if (type === "oauth2") {
1098
+ const flows = entries(scheme.flows).map(([flowName]) => flowName).join(", ") || cleanText(scheme.flow);
1099
+ details += ` (OAuth2${flows ? `; ${flows}` : ""})`;
1100
+ } else if (type === "openidconnect" || type === "openIdConnect") details += " (OpenID Connect)";
1101
+ else if (type === "basic") details += " (HTTP Basic)";
1102
+ else if (type) details += ` (${type})`;
1103
+ if (label) details += ` \u2014 ${label}`;
1104
+ return details;
416
1105
  }
417
- function schemaMetadata(schemaValue) {
418
- if (!isObject(schemaValue)) return [];
419
- const schema = schemaValue;
420
- const metadata = [];
421
- if (cleanText(schema.description)) metadata.push(cleanText(schema.description));
422
- if (cleanText(schema.format)) metadata.push(`format: ${cleanText(schema.format)}`);
423
- const constraints = [
424
- ["default", "default"],
425
- ["pattern", "pattern"],
426
- ["minLength", "minLength"],
427
- ["maxLength", "maxLength"],
428
- ["minimum", "minimum"],
429
- ["maximum", "maximum"],
430
- ["exclusiveMinimum", "exclusiveMinimum"],
431
- ["exclusiveMaximum", "exclusiveMaximum"],
432
- ["minItems", "minItems"],
433
- ["maxItems", "maxItems"],
434
- ["uniqueItems", "uniqueItems"],
435
- ["multipleOf", "multipleOf"]
436
- ];
437
- for (const [property, label] of constraints) {
438
- const value = schema[property];
439
- if (value === void 0) continue;
440
- if (property === "default" && (value === null || value === "")) continue;
441
- metadata.push(
442
- `${label}: ${property === "default" ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value)}`
443
- );
444
- }
445
- if (schema.readOnly === true) metadata.push("read-only");
446
- if (schema.writeOnly === true) metadata.push("write-only");
447
- if (schema.deprecated === true) metadata.push("deprecated");
448
- return [...new Set(metadata)];
1106
+ function getSecuritySchemes(spec) {
1107
+ const dictionary = spec.components?.securitySchemes ?? spec.securityDefinitions ?? {};
1108
+ return entries(dictionary).filter((entry) => isObject(entry[1]));
449
1109
  }
450
- function toTypeScriptIdentifier(value) {
451
- let identifier = value.replace(/[^\p{L}\p{N}_$]+/gu, "_").replace(/^([^\p{L}_$])/u, "_$1");
452
- if (!identifier) identifier = "Unnamed";
453
- const reserved = /* @__PURE__ */ new Set([
454
- "abstract",
455
- "any",
456
- "as",
457
- "asserts",
458
- "async",
459
- "await",
460
- "boolean",
461
- "break",
462
- "case",
463
- "catch",
464
- "class",
465
- "const",
466
- "constructor",
467
- "continue",
468
- "debugger",
469
- "declare",
470
- "default",
471
- "delete",
472
- "do",
473
- "else",
474
- "enum",
475
- "export",
476
- "extends",
477
- "false",
478
- "finally",
479
- "for",
480
- "from",
481
- "function",
482
- "get",
483
- "if",
484
- "implements",
485
- "import",
486
- "in",
487
- "infer",
488
- "instanceof",
489
- "interface",
490
- "is",
491
- "keyof",
492
- "let",
493
- "module",
494
- "namespace",
495
- "never",
496
- "new",
497
- "null",
498
- "number",
499
- "object",
500
- "of",
501
- "package",
502
- "private",
503
- "protected",
504
- "public",
505
- "readonly",
506
- "require",
507
- "return",
508
- "set",
509
- "static",
510
- "string",
511
- "super",
512
- "switch",
513
- "symbol",
514
- "this",
515
- "throw",
516
- "true",
517
- "try",
518
- "type",
519
- "typeof",
520
- "undefined",
521
- "unique",
522
- "unknown",
523
- "var",
524
- "void",
525
- "while",
526
- "with",
527
- "yield"
528
- ]);
529
- if (reserved.has(identifier)) identifier = `${identifier}Model`;
530
- return identifier;
1110
+ function securityRequirement(spec, operation) {
1111
+ const security = operation.security !== void 0 ? operation.security : spec.security;
1112
+ if (!Array.isArray(security) || security.length === 0) return "None explicitly required";
1113
+ const allSchemes = new Map(getSecuritySchemes(spec));
1114
+ return security.map((requirement) => {
1115
+ if (!isObject(requirement)) return "";
1116
+ const names = Object.entries(requirement).map(([name, scopes]) => {
1117
+ const label = allSchemes.has(name) ? name : `${name} (scheme not defined)`;
1118
+ return Array.isArray(scopes) && scopes.length ? `${label} [scopes: ${scopes.join(", ")}]` : label;
1119
+ });
1120
+ return names.length ? names.join(" + ") : "No auth";
1121
+ }).filter(Boolean).join(" OR ");
1122
+ }
1123
+ function authOverview(spec) {
1124
+ const schemes = getSecuritySchemes(spec);
1125
+ if (!schemes.length) return ["- **Authentication:** No security schemes declared."];
1126
+ const globalSecurity = securityRequirement(spec, {});
1127
+ return [
1128
+ `- **Authentication required globally:** ${globalSecurity}`,
1129
+ "- **Available schemes:**",
1130
+ ...schemes.map(([name, scheme]) => ` - ${describeSecurityScheme(name, scheme)}`)
1131
+ ];
531
1132
  }
532
- function propertyKey(value) {
533
- return /^[$A-Z_a-z][$\w]*$/.test(value) ? value : JSON.stringify(value);
1133
+
1134
+ // src/transformer/render-style.ts
1135
+ var FULL_STYLE = { includeExamples: true, collapsed: false };
1136
+ function shortenDescription(text, limit) {
1137
+ if (limit === void 0 || text.length <= limit) return text;
1138
+ return `${text.slice(0, limit).trimEnd()}\u2026`;
1139
+ }
1140
+
1141
+ // src/transformer/schema-type.ts
1142
+ function schemaMetadata(schemaValue) {
1143
+ if (!isObject(schemaValue)) return [];
1144
+ const schema = schemaValue;
1145
+ const metadata = [];
1146
+ if (cleanText(schema.description)) metadata.push(cleanText(schema.description));
1147
+ if (cleanText(schema.format)) metadata.push(`format: ${cleanText(schema.format)}`);
1148
+ const constraints = [
1149
+ ["default", "default"],
1150
+ ["pattern", "pattern"],
1151
+ ["minLength", "minLength"],
1152
+ ["maxLength", "maxLength"],
1153
+ ["minimum", "minimum"],
1154
+ ["maximum", "maximum"],
1155
+ ["exclusiveMinimum", "exclusiveMinimum"],
1156
+ ["exclusiveMaximum", "exclusiveMaximum"],
1157
+ ["minItems", "minItems"],
1158
+ ["maxItems", "maxItems"],
1159
+ ["uniqueItems", "uniqueItems"],
1160
+ ["multipleOf", "multipleOf"]
1161
+ ];
1162
+ for (const [property, label] of constraints) {
1163
+ const value = schema[property];
1164
+ if (value === void 0) continue;
1165
+ if (property === "default" && (value === null || value === "")) continue;
1166
+ metadata.push(
1167
+ `${label}: ${property === "default" ? JSON.stringify(value) : typeof value === "string" ? value : JSON.stringify(value)}`
1168
+ );
1169
+ }
1170
+ if (schema.readOnly === true) metadata.push("read-only");
1171
+ if (schema.writeOnly === true) metadata.push("write-only");
1172
+ if (schema.deprecated === true) metadata.push("deprecated");
1173
+ return [...new Set(metadata)];
534
1174
  }
535
1175
  function refName(ref) {
536
1176
  const parts = ref.split("/");
@@ -635,80 +1275,7 @@ function getSchemaDictionary(spec) {
635
1275
  const schemas = spec.components?.schemas ?? spec.definitions ?? {};
636
1276
  return isObject(schemas) ? schemas : {};
637
1277
  }
638
- function collectSchemaRefs(value, output, root, seen = /* @__PURE__ */ new Set(), followedRefs = /* @__PURE__ */ new Set()) {
639
- if (!value || typeof value !== "object" || seen.has(value)) return;
640
- seen.add(value);
641
- if (Array.isArray(value)) {
642
- for (const item of value) collectSchemaRefs(item, output, root, seen, followedRefs);
643
- return;
644
- }
645
- const object = value;
646
- if (typeof object.$ref === "string") {
647
- if (isSchemaRef(object.$ref)) {
648
- const name = refName(object.$ref);
649
- if (name) output.add(name);
650
- } else if (root && object.$ref.startsWith("#/") && !followedRefs.has(object.$ref)) {
651
- followedRefs.add(object.$ref);
652
- const target = resolvePointer(root, object.$ref);
653
- if (target !== void 0) collectSchemaRefs(target, output, root, seen, followedRefs);
654
- }
655
- }
656
- for (const [key, child] of Object.entries(object)) {
657
- if (key !== "example" && key !== "examples" && key !== "externalDocs")
658
- collectSchemaRefs(child, output, root, seen, followedRefs);
659
- }
660
- }
661
- function referencedSchemas(spec) {
662
- const dictionary = getSchemaDictionary(spec);
663
- const knownByName = /* @__PURE__ */ new Map();
664
- for (const [name, schema] of Object.entries(dictionary)) {
665
- knownByName.set(toTypeScriptIdentifier(name), [name, schema]);
666
- }
667
- const refs = /* @__PURE__ */ new Set();
668
- collectSchemaRefs(spec.paths ?? {}, refs, spec);
669
- const selected = /* @__PURE__ */ new Set();
670
- const queue = [...refs];
671
- while (queue.length) {
672
- const name = queue.shift();
673
- if (!name || selected.has(name)) continue;
674
- selected.add(name);
675
- const model = knownByName.get(name);
676
- if (!model) continue;
677
- const childRefs = /* @__PURE__ */ new Set();
678
- collectSchemaRefs(model[1], childRefs, spec);
679
- for (const child of childRefs) if (!selected.has(child)) queue.push(child);
680
- }
681
- return [...selected].map((name) => knownByName.get(name)).filter((entry) => Boolean(entry));
682
- }
683
- function modelDeclaration(name, schema) {
684
- const identifier = toTypeScriptIdentifier(name);
685
- const description = safeComment(schema.description);
686
- const comment = description ? `/** ${description} */
687
- ` : "";
688
- if (schema.$ref) return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
689
- if (Array.isArray(schema.enum) || schema.const !== void 0 || schema.oneOf || schema.anyOf || schema.allOf) {
690
- return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
691
- }
692
- const kind = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
693
- if (kind === "object" || schema.properties || schema.additionalProperties !== void 0) {
694
- const properties = entries(schema.properties);
695
- const required = new Set(Array.isArray(schema.required) ? schema.required : []);
696
- const fields = properties.map(([field, value]) => {
697
- const metadata = schemaMetadata(value).map(safeComment).filter(Boolean).join("; ");
698
- const doc = metadata ? ` /** ${metadata} */
699
- ` : "";
700
- return `${doc} ${propertyKey(field)}${required.has(field) ? "" : "?"}: ${typeExpression(value)};`;
701
- });
702
- if (schema.additionalProperties === true) fields.push(" [key: string]: unknown;");
703
- else if (isObject(schema.additionalProperties))
704
- fields.push(` [key: string]: ${typeExpression(schema.additionalProperties)};`);
705
- return `${comment}export interface ${identifier} {
706
- ${fields.join("\n")}
707
- }`;
708
- }
709
- return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
710
- }
711
- function resolvePointer(root, ref) {
1278
+ function resolvePointer2(root, ref) {
712
1279
  if (!ref.startsWith("#/")) return void 0;
713
1280
  let current = root;
714
1281
  for (const rawPart of ref.slice(2).split("/")) {
@@ -723,7 +1290,7 @@ function resolveReferenceObject(spec, value, seen = /* @__PURE__ */ new Set()) {
723
1290
  if (typeof value.$ref !== "string") return value;
724
1291
  const ref = value.$ref;
725
1292
  if (seen.has(ref)) return {};
726
- const target = resolvePointer(spec, ref);
1293
+ const target = resolvePointer2(spec, ref);
727
1294
  if (!isObject(target)) return value;
728
1295
  const nextSeen = new Set(seen).add(ref);
729
1296
  return {
@@ -731,112 +1298,8 @@ function resolveReferenceObject(spec, value, seen = /* @__PURE__ */ new Set()) {
731
1298
  ...Object.fromEntries(Object.entries(value).filter(([key]) => key !== "$ref"))
732
1299
  };
733
1300
  }
734
- function getEndpoints(spec) {
735
- const result = [];
736
- const paths = isObject(spec.paths) ? spec.paths : {};
737
- for (const [path2, pathItemValue] of Object.entries(paths)) {
738
- if (!isObject(pathItemValue)) continue;
739
- const pathParameters = Array.isArray(pathItemValue.parameters) ? pathItemValue.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
740
- for (const method of HTTP_METHODS) {
741
- const operationValue = pathItemValue[method];
742
- if (!isObject(operationValue)) continue;
743
- const firstTag = Array.isArray(operationValue.tags) && operationValue.tags.length ? String(operationValue.tags[0]) : "General";
744
- result.push({ method, path: path2, operation: operationValue, pathParameters, tag: firstTag });
745
- }
746
- }
747
- return result;
748
- }
749
- function getServers(spec) {
750
- if (Array.isArray(spec.servers) && spec.servers.length) {
751
- return spec.servers.map((server) => cleanText(server?.url)).filter(Boolean);
752
- }
753
- if (isObject(spec)) {
754
- const host = cleanText(spec.host);
755
- if (host) {
756
- const schemes = Array.isArray(spec.schemes) && spec.schemes.length ? spec.schemes : ["https"];
757
- const basePath = cleanText(spec.basePath) || "/";
758
- return schemes.map(
759
- (scheme) => `${scheme}://${host}${basePath.startsWith("/") ? basePath : `/${basePath}`}`
760
- );
761
- }
762
- }
763
- return [];
764
- }
765
- function describeSecurityScheme(name, scheme) {
766
- const type = cleanText(scheme.type).toLowerCase();
767
- const label = cleanText(scheme.description);
768
- let details = name;
769
- if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "bearer")
770
- details += " (Bearer token via Authorization header)";
771
- else if (type === "http" && cleanText(scheme.scheme).toLowerCase() === "basic")
772
- details += " (HTTP Basic via Authorization header)";
773
- else if (type === "apikey" || type === "apiKey")
774
- details += ` (API key in ${cleanText(scheme.in) || "header"}: ${cleanText(scheme.name) || "key"})`;
775
- else if (type === "oauth2") {
776
- const flows = entries(scheme.flows).map(([flowName]) => flowName).join(", ") || cleanText(scheme.flow);
777
- details += ` (OAuth2${flows ? `; ${flows}` : ""})`;
778
- } else if (type === "openidconnect" || type === "openIdConnect") details += " (OpenID Connect)";
779
- else if (type === "basic") details += " (HTTP Basic)";
780
- else if (type) details += ` (${type})`;
781
- if (label) details += ` \u2014 ${label}`;
782
- return details;
783
- }
784
- function getSecuritySchemes(spec) {
785
- const dictionary = spec.components?.securitySchemes ?? spec.securityDefinitions ?? {};
786
- return entries(dictionary).filter((entry) => isObject(entry[1]));
787
- }
788
- function securityRequirement(spec, operation) {
789
- const security = operation.security !== void 0 ? operation.security : spec.security;
790
- if (!Array.isArray(security) || security.length === 0) return "None explicitly required";
791
- const allSchemes = new Map(getSecuritySchemes(spec));
792
- return security.map((requirement) => {
793
- if (!isObject(requirement)) return "";
794
- const names = Object.entries(requirement).map(([name, scopes]) => {
795
- const label = allSchemes.has(name) ? name : `${name} (scheme not defined)`;
796
- return Array.isArray(scopes) && scopes.length ? `${label} [scopes: ${scopes.join(", ")}]` : label;
797
- });
798
- return names.length ? names.join(" + ") : "No auth";
799
- }).filter(Boolean).join(" OR ");
800
- }
801
- function mergedParameters(spec, endpoint) {
802
- const operationParameters = Array.isArray(endpoint.operation.parameters) ? endpoint.operation.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
803
- const byIdentity = /* @__PURE__ */ new Map();
804
- for (const parameter of [...endpoint.pathParameters, ...operationParameters]) {
805
- if (parameter.in === "body") continue;
806
- const key = `${cleanText(parameter.in)}:${cleanText(parameter.name)}`;
807
- if (key !== ":") byIdentity.set(key, parameter);
808
- }
809
- return [...byIdentity.values()];
810
- }
811
- function parameterType(parameter) {
812
- if (isObject(parameter.schema)) return typeExpression(parameter.schema);
813
- if (parameter.type === "array") return `${typeExpression(parameter.items)}[]`;
814
- if (parameter.type) return typeExpression({ type: parameter.type, format: parameter.format, enum: parameter.enum });
815
- return "unknown";
816
- }
817
- function parameterDisplayType(parameter) {
818
- return parameterType(parameter).replace(/\/\*\s*([^*]+?)\s*\*\//g, "($1)");
819
- }
820
- function parameterDefault(parameter) {
821
- if (parameter.default !== void 0) return parameter.default;
822
- if (isObject(parameter.schema)) return parameter.schema.default;
823
- return void 0;
824
- }
825
- function isMeaningfulDefault(value) {
826
- return value !== void 0 && value !== null && value !== "";
827
- }
828
- function parameterList(parameters) {
829
- if (!parameters.length) return "_None._";
830
- return parameters.map((parameter) => {
831
- const required = parameter.required === true || parameter.in === "path";
832
- const type = parameterDisplayType(parameter);
833
- const defaultValue = parameterDefault(parameter);
834
- const defaultSuffix = isMeaningfulDefault(defaultValue) ? ` (default: ${typeof defaultValue === "string" ? JSON.stringify(defaultValue) : String(defaultValue)})` : "";
835
- const description = cleanText(parameter.description);
836
- const details = description ? ` \u2014 ${description}` : "";
837
- return `- \`${cleanText(parameter.name)}\` (\`${cleanText(parameter.in)}\`${required ? ", required" : ""}) \xB7 \`${type}\`${defaultSuffix}${details}`;
838
- }).join("\n");
839
- }
1301
+
1302
+ // src/transformer/examples.ts
840
1303
  function chooseMediaType(content) {
841
1304
  const mediaTypes = Object.keys(content);
842
1305
  return mediaTypes.find((type) => type.toLowerCase() === "application/json") ?? mediaTypes.find((type) => type.toLowerCase().includes("+json")) ?? mediaTypes[0];
@@ -849,7 +1312,7 @@ function firstDeclaredExample(spec, schemaValue, seen = /* @__PURE__ */ new Set(
849
1312
  let schema = schemaValue;
850
1313
  if (typeof schema.$ref === "string") {
851
1314
  if (seen.has(schema.$ref)) return void 0;
852
- const target = resolvePointer(spec, schema.$ref);
1315
+ const target = resolvePointer2(spec, schema.$ref);
853
1316
  if (!isObject(target)) return void 0;
854
1317
  const nextSeen = new Set(seen).add(schema.$ref);
855
1318
  schema = { ...target, ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$ref")) };
@@ -866,7 +1329,7 @@ function generateSchemaExample(spec, schemaValue, depth = 0, seenRefs = /* @__PU
866
1329
  let nextSeen = seenRefs;
867
1330
  if (typeof schema.$ref === "string") {
868
1331
  if (seenRefs.has(schema.$ref)) return null;
869
- const target = resolvePointer(spec, schema.$ref);
1332
+ const target = resolvePointer2(spec, schema.$ref);
870
1333
  if (!isObject(target)) return void 0;
871
1334
  nextSeen = new Set(seenRefs).add(schema.$ref);
872
1335
  schema = { ...target, ...Object.fromEntries(Object.entries(schema).filter(([key]) => key !== "$ref")) };
@@ -946,25 +1409,76 @@ function generateSchemaExample(spec, schemaValue, depth = 0, seenRefs = /* @__PU
946
1409
  if (schema.properties || schema.additionalProperties !== void 0) return {};
947
1410
  return void 0;
948
1411
  }
949
- function payloadExample(spec, mediaValue, schemaValue, legacyExamples) {
950
- const media = isObject(mediaValue) ? mediaValue : {};
951
- if (hasOwn(media, "example")) return { value: media.example, origin: "declared" };
952
- for (const [name, exampleValue] of entries(media.examples)) {
953
- const example = resolveReferenceObject(spec, exampleValue);
954
- if (hasOwn(example, "value")) return { value: example.value, origin: "declared", name };
955
- }
956
- if (isObject(legacyExamples)) {
957
- const mediaType = Object.keys(legacyExamples).find((key) => key.toLowerCase() === "application/json") ?? Object.keys(legacyExamples)[0];
958
- if (mediaType && hasOwn(legacyExamples, mediaType)) {
959
- return { value: legacyExamples[mediaType], origin: "declared" };
960
- }
1412
+ function payloadExample(spec, mediaValue, schemaValue, legacyExamples) {
1413
+ const media = isObject(mediaValue) ? mediaValue : {};
1414
+ if (hasOwn(media, "example")) return { value: media.example, origin: "declared" };
1415
+ for (const [name, exampleValue] of entries(media.examples)) {
1416
+ const example = resolveReferenceObject(spec, exampleValue);
1417
+ if (hasOwn(example, "value")) return { value: example.value, origin: "declared", name };
1418
+ }
1419
+ if (isObject(legacyExamples)) {
1420
+ const mediaType = Object.keys(legacyExamples).find((key) => key.toLowerCase() === "application/json") ?? Object.keys(legacyExamples)[0];
1421
+ if (mediaType && hasOwn(legacyExamples, mediaType)) {
1422
+ return { value: legacyExamples[mediaType], origin: "declared" };
1423
+ }
1424
+ }
1425
+ const declaredSchemaExample = firstDeclaredExample(spec, schemaValue);
1426
+ if (declaredSchemaExample !== void 0) return { value: declaredSchemaExample, origin: "declared" };
1427
+ const generated = generateSchemaExample(spec, schemaValue);
1428
+ return generated === void 0 ? void 0 : { value: generated, origin: "schema-derived" };
1429
+ }
1430
+ function formattedExample(value) {
1431
+ try {
1432
+ return JSON.stringify(value, null, 2) ?? "null";
1433
+ } catch {
1434
+ return "null";
1435
+ }
1436
+ }
1437
+
1438
+ // src/transformer/parameters.ts
1439
+ function mergedParameters(spec, endpoint) {
1440
+ const operationParameters = Array.isArray(endpoint.operation.parameters) ? endpoint.operation.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
1441
+ const byIdentity = /* @__PURE__ */ new Map();
1442
+ for (const parameter of [...endpoint.pathParameters, ...operationParameters]) {
1443
+ if (parameter.in === "body") continue;
1444
+ const key = `${cleanText(parameter.in)}:${cleanText(parameter.name)}`;
1445
+ if (key !== ":") byIdentity.set(key, parameter);
1446
+ }
1447
+ return [...byIdentity.values()];
1448
+ }
1449
+ function parameterType(parameter) {
1450
+ if (isObject(parameter.schema)) return typeExpression(parameter.schema);
1451
+ if (parameter.type === "array") return `${typeExpression(parameter.items)}[]`;
1452
+ if (parameter.type) return typeExpression({ type: parameter.type, format: parameter.format, enum: parameter.enum });
1453
+ return "unknown";
1454
+ }
1455
+ function parameterDisplayType(parameter) {
1456
+ return parameterType(parameter).replace(/\/\*\s*([^*]+?)\s*\*\//g, "($1)");
1457
+ }
1458
+ function parameterDefault(parameter) {
1459
+ if (parameter.default !== void 0) return parameter.default;
1460
+ if (isObject(parameter.schema)) return parameter.schema.default;
1461
+ return void 0;
1462
+ }
1463
+ function isMeaningfulDefault(value) {
1464
+ return value !== void 0 && value !== null && value !== "";
1465
+ }
1466
+ function parameterList(parameters, style = FULL_STYLE) {
1467
+ if (!parameters.length) return "_None._";
1468
+ if (style.collapsed) {
1469
+ return `- _${parameters.length} parameter${parameters.length === 1 ? "" : "s"} (detail collapsed for token budget)._`;
961
1470
  }
962
- const declaredSchemaExample = firstDeclaredExample(spec, schemaValue);
963
- if (declaredSchemaExample !== void 0) return { value: declaredSchemaExample, origin: "declared" };
964
- const generated = generateSchemaExample(spec, schemaValue);
965
- return generated === void 0 ? void 0 : { value: generated, origin: "schema-derived" };
1471
+ return parameters.map((parameter) => {
1472
+ const required = parameter.required === true || parameter.in === "path";
1473
+ const type = parameterDisplayType(parameter);
1474
+ const defaultValue = parameterDefault(parameter);
1475
+ const defaultSuffix = isMeaningfulDefault(defaultValue) ? ` (default: ${typeof defaultValue === "string" ? JSON.stringify(defaultValue) : String(defaultValue)})` : "";
1476
+ const description = shortenDescription(cleanText(parameter.description), style.descriptionLimit);
1477
+ const details = description ? ` \u2014 ${description}` : "";
1478
+ return `- \`${cleanText(parameter.name)}\` (\`${cleanText(parameter.in)}\`${required ? ", required" : ""}) \xB7 \`${type}\`${defaultSuffix}${details}`;
1479
+ }).join("\n");
966
1480
  }
967
- function requestPayloadDescription(spec, operation) {
1481
+ function requestPayloadDescription(spec, operation, style = FULL_STYLE) {
968
1482
  if (isObject(operation.requestBody) || operation.requestBody?.$ref) {
969
1483
  const body = resolveReferenceObject(spec, operation.requestBody);
970
1484
  const content = isObject(body.content) ? body.content : {};
@@ -974,7 +1488,7 @@ function requestPayloadDescription(spec, operation) {
974
1488
  return {
975
1489
  type: schema ? typeExpression(schema) : "unknown",
976
1490
  mediaType: mediaType || "unknown media type",
977
- example: media || schema ? payloadExample(spec, media, schema) : void 0,
1491
+ example: style.includeExamples && (media || schema) ? payloadExample(spec, media, schema) : void 0,
978
1492
  required: body.required === true
979
1493
  };
980
1494
  }
@@ -985,13 +1499,31 @@ function requestPayloadDescription(spec, operation) {
985
1499
  return {
986
1500
  type: typeExpression(bodyParameter.schema),
987
1501
  mediaType: consumes[0] ?? "application/json",
988
- example: payloadExample(spec, void 0, bodyParameter.schema),
1502
+ example: style.includeExamples ? payloadExample(spec, void 0, bodyParameter.schema) : void 0,
989
1503
  required: bodyParameter.required === true
990
1504
  };
991
1505
  }
992
1506
  return void 0;
993
1507
  }
994
- function responseSchema(spec, responseValue) {
1508
+
1509
+ // src/transformer/responses.ts
1510
+ var COMMON_ERROR_CODES = /* @__PURE__ */ new Set([
1511
+ "400",
1512
+ "401",
1513
+ "403",
1514
+ "404",
1515
+ "405",
1516
+ "409",
1517
+ "415",
1518
+ "422",
1519
+ "429",
1520
+ "500",
1521
+ "501",
1522
+ "502",
1523
+ "503",
1524
+ "default"
1525
+ ]);
1526
+ function responseSchema(spec, responseValue, style = FULL_STYLE) {
995
1527
  const response = resolveReferenceObject(spec, responseValue);
996
1528
  if (isObject(response.content)) {
997
1529
  const mediaType = chooseMediaType(response.content);
@@ -1000,30 +1532,26 @@ function responseSchema(spec, responseValue) {
1000
1532
  return {
1001
1533
  type: schema ? typeExpression(schema) : void 0,
1002
1534
  mediaType,
1003
- example: schema || media ? payloadExample(spec, media, schema, response.examples) : void 0
1535
+ example: style.includeExamples && (schema || media) ? payloadExample(spec, media, schema, response.examples) : void 0
1004
1536
  };
1005
1537
  }
1006
1538
  if (isObject(response.schema)) {
1007
1539
  return {
1008
1540
  type: typeExpression(response.schema),
1009
1541
  mediaType: "application/json",
1010
- example: payloadExample(spec, void 0, response.schema, response.examples)
1542
+ example: style.includeExamples ? payloadExample(spec, void 0, response.schema, response.examples) : void 0
1011
1543
  };
1012
1544
  }
1013
1545
  return {};
1014
1546
  }
1015
- function formattedExample(value) {
1016
- try {
1017
- return JSON.stringify(value, null, 2) ?? "null";
1018
- } catch {
1019
- return "null";
1020
- }
1021
- }
1022
- function responseBlock(spec, code, responseValue) {
1547
+ function responseBlock(spec, code, responseValue, style = FULL_STYLE) {
1023
1548
  const response = resolveReferenceObject(spec, responseValue);
1024
- const description = cleanText(response.description);
1025
- const payload = responseSchema(spec, responseValue);
1549
+ const description = style.collapsed ? "" : shortenDescription(cleanText(response.description), style.descriptionLimit);
1550
+ const payload = responseSchema(spec, responseValue, style);
1026
1551
  const suffix = payload.type ? ` \u2014 \`${payload.type}\`${payload.mediaType ? ` (${payload.mediaType})` : ""}` : " \u2014 no body schema declared";
1552
+ if (style.collapsed) {
1553
+ return [`- **${markdownCell(code)}**${suffix}`].join("\n");
1554
+ }
1027
1555
  const lines = [`- **${markdownCell(code)}${description ? ` ${description}` : ""}**${suffix}`];
1028
1556
  if (payload.example?.origin === "declared") {
1029
1557
  const label = payload.example.name ? ` \xB7 ${payload.example.name}` : "";
@@ -1088,7 +1616,7 @@ function collapsibleErrorEntry(spec, code, responseValue) {
1088
1616
  if (schemaValue === void 0) return `\`${code}\``;
1089
1617
  return `\`${code}\` (\`${typeExpression(schemaValue)}\`)`;
1090
1618
  }
1091
- function responseSection(spec, operation) {
1619
+ function responseSection(spec, operation, style = FULL_STYLE) {
1092
1620
  const responses = entries(operation.responses).filter(([, value]) => isObject(value));
1093
1621
  if (!responses.length) return "_No responses declared._";
1094
1622
  const success = responses.filter(([code]) => /^2\d\d$/.test(code));
@@ -1096,42 +1624,96 @@ function responseSection(spec, operation) {
1096
1624
  const errors = responses.filter(
1097
1625
  ([code]) => (COMMON_ERROR_CODES.has(code) || /^([45])\d\d$/.test(code)) && !success.some(([successCode]) => successCode === code)
1098
1626
  );
1627
+ if (style.collapsed) {
1628
+ const collapsedLines = success.map(([code, response]) => responseBlock(spec, code, response, style));
1629
+ collapsedLines.push(...errors.map(([code, responseValue]) => responseBlock(spec, code, responseValue, style)));
1630
+ return collapsedLines.join("\n");
1631
+ }
1099
1632
  const detailedErrors = [];
1100
1633
  const collapsedErrors = [];
1101
1634
  for (const [code, responseValue] of errors) {
1102
1635
  const entry = collapsibleErrorEntry(spec, code, responseValue);
1103
- if (entry === void 0) detailedErrors.push(responseBlock(spec, code, responseValue));
1636
+ if (entry === void 0) detailedErrors.push(responseBlock(spec, code, responseValue, style));
1104
1637
  else collapsedErrors.push(entry);
1105
1638
  }
1106
1639
  const primarySuccess = success[0];
1107
- const lines = primarySuccess ? [responseBlock(spec, primarySuccess[0], primarySuccess[1])] : [];
1640
+ const lines = primarySuccess ? [responseBlock(spec, primarySuccess[0], primarySuccess[1], style)] : [];
1108
1641
  lines.push(...detailedErrors);
1109
1642
  if (collapsedErrors.length) lines.push(`- **Errors:** ${collapsedErrors.join(", ")}`);
1110
1643
  if (success.length > 1) {
1111
- const others = success.slice(1).map(([code, response]) => responseBlock(spec, code, response));
1644
+ const others = success.slice(1).map(([code, response]) => responseBlock(spec, code, response, style));
1112
1645
  lines.push("", "<details><summary>Other successful responses</summary>", "", ...others, "", "</details>");
1113
1646
  }
1114
1647
  if (!lines.length) {
1115
- return responses.map(([code, response]) => responseBlock(spec, code, response)).join("\n\n");
1648
+ return responses.map(([code, response]) => responseBlock(spec, code, response, style)).join("\n\n");
1116
1649
  }
1117
1650
  return lines.join("\n");
1118
1651
  }
1652
+
1653
+ // src/transformer/endpoints.ts
1654
+ var HTTP_METHODS = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
1655
+ function getEndpoints(spec) {
1656
+ const result = [];
1657
+ const paths = isObject(spec.paths) ? spec.paths : {};
1658
+ for (const [path2, pathItemValue] of Object.entries(paths)) {
1659
+ if (!isObject(pathItemValue)) continue;
1660
+ const pathParameters = Array.isArray(pathItemValue.parameters) ? pathItemValue.parameters.map((parameter) => resolveReferenceObject(spec, parameter)) : [];
1661
+ for (const method of HTTP_METHODS) {
1662
+ const operationValue = pathItemValue[method];
1663
+ if (!isObject(operationValue)) continue;
1664
+ const firstTag = Array.isArray(operationValue.tags) && operationValue.tags.length ? String(operationValue.tags[0]) : "General";
1665
+ result.push({ method, path: path2, operation: operationValue, pathParameters, tag: firstTag });
1666
+ }
1667
+ }
1668
+ return result;
1669
+ }
1670
+ function getServers(spec) {
1671
+ if (Array.isArray(spec.servers) && spec.servers.length) {
1672
+ return spec.servers.map((server) => cleanText(isObject(server) ? server.url : void 0)).filter(Boolean);
1673
+ }
1674
+ if (isObject(spec)) {
1675
+ const host = cleanText(spec.host);
1676
+ if (host) {
1677
+ const schemes = Array.isArray(spec.schemes) && spec.schemes.length ? spec.schemes : ["https"];
1678
+ const basePath = cleanText(spec.basePath) || "/";
1679
+ return schemes.map(
1680
+ (scheme) => `${scheme}://${host}${basePath.startsWith("/") ? basePath : `/${basePath}`}`
1681
+ );
1682
+ }
1683
+ }
1684
+ return [];
1685
+ }
1686
+ function collectTags(spec, endpoints) {
1687
+ const declared = Array.isArray(spec.tags) ? spec.tags.map((tag) => cleanText(isObject(tag) ? tag.name : void 0)).filter(Boolean) : [];
1688
+ const encountered = [...new Set(endpoints.map((endpoint) => endpoint.tag))];
1689
+ return [
1690
+ ...declared.filter((tag) => encountered.includes(tag)),
1691
+ ...encountered.filter((tag) => !declared.includes(tag))
1692
+ ];
1693
+ }
1694
+ function endpointTypeBase(method, path2, operation) {
1695
+ if (cleanText(operation.operationId)) return toTypeScriptIdentifier(cleanText(operation.operationId));
1696
+ const pathName = path2.replace(/[{}]/g, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1697
+ return toTypeScriptIdentifier(`${method.charAt(0).toUpperCase()}${method.slice(1)}${pathName}`);
1698
+ }
1119
1699
  function shortOperationDescription(operation) {
1120
1700
  return markdownCell(operation.summary || operation.operationId || operation.description || "\u2014");
1121
1701
  }
1122
- function endpointMarkdown(spec, endpoint) {
1702
+ function endpointMarkdown(spec, endpoint, style = FULL_STYLE) {
1123
1703
  const { operation, method, path: path2 } = endpoint;
1124
1704
  const heading = `#### \`${method.toUpperCase()} ${path2}\``;
1125
1705
  const summary = cleanText(operation.summary);
1126
1706
  const description = cleanText(operation.description);
1127
1707
  const lines = [heading];
1128
- if (summary) lines.push("", summary);
1129
- if (description && description !== summary) lines.push("", description);
1708
+ if (summary) lines.push("", shortenDescription(summary, style.descriptionLimit));
1709
+ if (description && description !== summary) {
1710
+ lines.push("", shortenDescription(description, style.descriptionLimit));
1711
+ }
1130
1712
  lines.push("", `**Authentication:** ${securityRequirement(spec, operation)}`);
1131
1713
  const parameters = mergedParameters(spec, endpoint);
1132
- const request = requestPayloadDescription(spec, operation);
1714
+ const request = requestPayloadDescription(spec, operation, style);
1133
1715
  lines.push("", "**Parameters**");
1134
- lines.push(parameterList(parameters));
1716
+ lines.push(parameterList(parameters, style));
1135
1717
  if (request) {
1136
1718
  lines.push(
1137
1719
  "",
@@ -1149,31 +1731,83 @@ function endpointMarkdown(spec, endpoint) {
1149
1731
  );
1150
1732
  }
1151
1733
  }
1152
- lines.push("", "**Responses**", responseSection(spec, operation));
1734
+ lines.push("", "**Responses**", responseSection(spec, operation, style));
1153
1735
  return lines.join("\n");
1154
1736
  }
1155
- function authOverview(spec) {
1156
- const schemes = getSecuritySchemes(spec);
1157
- if (!schemes.length) return ["- **Authentication:** No security schemes declared."];
1158
- const globalSecurity = securityRequirement(spec, {});
1159
- return [
1160
- `- **Authentication required globally:** ${globalSecurity}`,
1161
- "- **Available schemes:**",
1162
- ...schemes.map(([name, scheme]) => ` - ${describeSecurityScheme(name, scheme)}`)
1163
- ];
1737
+
1738
+ // src/transformer/models.ts
1739
+ function collectSchemaRefs(value, output, root, seen = /* @__PURE__ */ new Set(), followedRefs = /* @__PURE__ */ new Set()) {
1740
+ if (!value || typeof value !== "object" || seen.has(value)) return;
1741
+ seen.add(value);
1742
+ if (Array.isArray(value)) {
1743
+ for (const item of value) collectSchemaRefs(item, output, root, seen, followedRefs);
1744
+ return;
1745
+ }
1746
+ const object = value;
1747
+ if (typeof object.$ref === "string") {
1748
+ if (isSchemaRef(object.$ref)) {
1749
+ const name = refName(object.$ref);
1750
+ if (name) output.add(name);
1751
+ } else if (root && object.$ref.startsWith("#/") && !followedRefs.has(object.$ref)) {
1752
+ followedRefs.add(object.$ref);
1753
+ const target = resolvePointer2(root, object.$ref);
1754
+ if (target !== void 0) collectSchemaRefs(target, output, root, seen, followedRefs);
1755
+ }
1756
+ }
1757
+ for (const [key, child] of Object.entries(object)) {
1758
+ if (key !== "example" && key !== "examples" && key !== "externalDocs")
1759
+ collectSchemaRefs(child, output, root, seen, followedRefs);
1760
+ }
1164
1761
  }
1165
- function collectTags(spec, endpoints) {
1166
- const declared = Array.isArray(spec.tags) ? spec.tags.map((tag) => cleanText(tag?.name)).filter(Boolean) : [];
1167
- const encountered = [...new Set(endpoints.map((endpoint) => endpoint.tag))];
1168
- return [
1169
- ...declared.filter((tag) => encountered.includes(tag)),
1170
- ...encountered.filter((tag) => !declared.includes(tag))
1171
- ];
1762
+ function referencedSchemas(spec) {
1763
+ const dictionary = getSchemaDictionary(spec);
1764
+ const knownByName = /* @__PURE__ */ new Map();
1765
+ for (const [name, schema] of Object.entries(dictionary)) {
1766
+ knownByName.set(toTypeScriptIdentifier(name), [name, schema]);
1767
+ }
1768
+ const refs = /* @__PURE__ */ new Set();
1769
+ collectSchemaRefs(spec.paths ?? {}, refs, spec);
1770
+ const selected = /* @__PURE__ */ new Set();
1771
+ const queue = [...refs];
1772
+ while (queue.length) {
1773
+ const name = queue.shift();
1774
+ if (!name || selected.has(name)) continue;
1775
+ selected.add(name);
1776
+ const model = knownByName.get(name);
1777
+ if (!model) continue;
1778
+ const childRefs = /* @__PURE__ */ new Set();
1779
+ collectSchemaRefs(model[1], childRefs, spec);
1780
+ for (const child of childRefs) if (!selected.has(child)) queue.push(child);
1781
+ }
1782
+ return [...selected].map((name) => knownByName.get(name)).filter((entry) => Boolean(entry));
1172
1783
  }
1173
- function endpointTypeBase(method, path2, operation) {
1174
- if (cleanText(operation.operationId)) return toTypeScriptIdentifier(cleanText(operation.operationId));
1175
- const pathName = path2.replace(/[{}]/g, "").split(/[^a-zA-Z0-9]+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
1176
- return toTypeScriptIdentifier(`${method.charAt(0).toUpperCase()}${method.slice(1)}${pathName}`);
1784
+ function modelDeclaration(name, schema) {
1785
+ const identifier = toTypeScriptIdentifier(name);
1786
+ const description = safeComment(schema.description);
1787
+ const comment = description ? `/** ${description} */
1788
+ ` : "";
1789
+ if (schema.$ref) return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
1790
+ if (Array.isArray(schema.enum) || schema.const !== void 0 || schema.oneOf || schema.anyOf || schema.allOf) {
1791
+ return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
1792
+ }
1793
+ const kind = Array.isArray(schema.type) ? schema.type.find((item) => item !== "null") : schema.type;
1794
+ if (kind === "object" || schema.properties || schema.additionalProperties !== void 0) {
1795
+ const properties = entries(schema.properties);
1796
+ const required = new Set(Array.isArray(schema.required) ? schema.required : []);
1797
+ const fields = properties.map(([field, value]) => {
1798
+ const metadata = schemaMetadata(value).map(safeComment).filter(Boolean).join("; ");
1799
+ const doc = metadata ? ` /** ${metadata} */
1800
+ ` : "";
1801
+ return `${doc} ${propertyKey(field)}${required.has(field) ? "" : "?"}: ${typeExpression(value)};`;
1802
+ });
1803
+ if (schema.additionalProperties === true) fields.push(" [key: string]: unknown;");
1804
+ else if (isObject(schema.additionalProperties))
1805
+ fields.push(` [key: string]: ${typeExpression(schema.additionalProperties)};`);
1806
+ return `${comment}export interface ${identifier} {
1807
+ ${fields.join("\n")}
1808
+ }`;
1809
+ }
1810
+ return `${comment}export type ${identifier} = ${typeExpression(schema)};`;
1177
1811
  }
1178
1812
  function normalizeInlinePayloadSchemas(spec) {
1179
1813
  const document = structuredClone(spec);
@@ -1267,26 +1901,123 @@ function normalizeInlinePayloadSchemas(spec) {
1267
1901
  }
1268
1902
  return document;
1269
1903
  }
1270
- function transformOpenApiToMarkdown(spec, sourceUrl) {
1271
- const normalizedSpec = normalizeInlinePayloadSchemas(spec);
1272
- spec = normalizedSpec;
1904
+
1905
+ // src/transformer/index.ts
1906
+ var HTTP_METHODS2 = ["get", "put", "post", "delete", "options", "head", "patch", "trace"];
1907
+ function tagsOfEndpoint(endpoint) {
1908
+ const tags = endpoint.operation.tags;
1909
+ return Array.isArray(tags) && tags.length ? tags.map(String) : ["General"];
1910
+ }
1911
+ function filterSpec(spec, endpoints, options, warnings) {
1912
+ const includeTags = options.includeTags ?? [];
1913
+ const excludeTags = options.excludeTags ?? [];
1914
+ const hasFilter = includeTags.length > 0 || excludeTags.length > 0 || options.pathPrefix !== void 0;
1915
+ if (!hasFilter) return spec;
1916
+ const declared = Array.isArray(spec.tags) ? spec.tags.map((tag) => isObject(tag) && typeof tag.name === "string" ? tag.name : "").filter(Boolean) : [];
1917
+ const available = [.../* @__PURE__ */ new Set([...declared, ...endpoints.map((endpoint) => endpoint.tag)])];
1918
+ const unknownTags = [
1919
+ ...includeTags.filter((tag) => !available.includes(tag)),
1920
+ ...excludeTags.filter((tag) => !available.includes(tag))
1921
+ ];
1922
+ if (unknownTags.length > 0) {
1923
+ warnings.push(
1924
+ `Unknown tag(s): ${[...new Set(unknownTags)].join(", ")}. Available tags: ${available.join(", ") || "none"}.`
1925
+ );
1926
+ }
1927
+ const keep = (endpoint) => {
1928
+ if (options.pathPrefix && !endpoint.path.startsWith(options.pathPrefix)) return false;
1929
+ const tags = tagsOfEndpoint(endpoint);
1930
+ if (includeTags.length > 0 && !tags.some((tag) => includeTags.includes(tag))) return false;
1931
+ if (excludeTags.length > 0 && tags.some((tag) => excludeTags.includes(tag))) return false;
1932
+ return true;
1933
+ };
1934
+ const paths = {};
1935
+ for (const endpoint of endpoints.filter(keep)) {
1936
+ const source = isObject(spec.paths?.[endpoint.path]) ? spec.paths[endpoint.path] : {};
1937
+ const item = paths[endpoint.path] ?? { ...source };
1938
+ for (const method of HTTP_METHODS2) delete item[method];
1939
+ item[endpoint.method] = source[endpoint.method];
1940
+ paths[endpoint.path] = item;
1941
+ }
1942
+ return { ...spec, paths };
1943
+ }
1944
+ function buildTrimStages(_endpoints, tags) {
1945
+ const stages = [];
1946
+ const emptyTags = /* @__PURE__ */ new Set();
1947
+ stages.push({
1948
+ includeExamples: true,
1949
+ collapsedTags: emptyTags,
1950
+ indexOnly: false
1951
+ });
1952
+ stages.push({
1953
+ includeExamples: false,
1954
+ collapsedTags: emptyTags,
1955
+ indexOnly: false,
1956
+ action: "removed declared example payloads"
1957
+ });
1958
+ stages.push({
1959
+ includeExamples: false,
1960
+ descriptionLimit: 200,
1961
+ collapsedTags: emptyTags,
1962
+ indexOnly: false,
1963
+ action: "shortened long descriptions"
1964
+ });
1965
+ const collapsedSoFar = /* @__PURE__ */ new Set();
1966
+ for (const tag of [...tags].reverse()) {
1967
+ collapsedSoFar.add(tag);
1968
+ stages.push({
1969
+ includeExamples: false,
1970
+ descriptionLimit: 200,
1971
+ collapsedTags: new Set(collapsedSoFar),
1972
+ indexOnly: false,
1973
+ action: `collapsed parameter and response detail for tag "${tag}"`
1974
+ });
1975
+ }
1976
+ stages.push({
1977
+ includeExamples: false,
1978
+ descriptionLimit: 200,
1979
+ collapsedTags: new Set(tags),
1980
+ indexOnly: true,
1981
+ action: "kept only the Quick index entries for the remaining tags"
1982
+ });
1983
+ return stages;
1984
+ }
1985
+ function renderDigest(spec, sourceUrl, endpoints, tags, stage) {
1273
1986
  const info = isObject(spec.info) ? spec.info : {};
1274
1987
  const title = cleanText(info.title) || "API";
1275
1988
  const version = cleanText(info.version) || "unspecified";
1276
1989
  const openApiVersion = cleanText(spec.openapi) || (spec.swagger ? `Swagger ${spec.swagger}` : "OpenAPI");
1277
1990
  const servers = getServers(spec);
1278
- const endpoints = getEndpoints(spec);
1279
- const tags = collectTags(spec, endpoints);
1280
1991
  const indexRows = endpoints.map(
1281
1992
  (endpoint) => `| **${endpoint.method.toUpperCase()}** | \`${markdownCell(endpoint.path)}\` | ${shortOperationDescription(endpoint.operation)} | ${markdownCell(Array.isArray(endpoint.operation.tags) ? endpoint.operation.tags.join(", ") : endpoint.tag)} |`
1282
1993
  );
1994
+ const detailEndpoints = stage.indexOnly ? [] : endpoints.filter((endpoint) => !stage.collapsedTags.has(endpoint.tag));
1283
1995
  const groupedEndpoints = tags.map((tag) => {
1284
1996
  const members = endpoints.filter((endpoint) => endpoint.tag === tag);
1285
1997
  if (!members.length) return "";
1286
- return [`### ${tag}`, ...members.map((endpoint) => endpointMarkdown(spec, endpoint))].join("\n\n");
1998
+ const rendered = members.map(
1999
+ (endpoint) => endpointMarkdown(spec, endpoint, {
2000
+ includeExamples: stage.includeExamples,
2001
+ descriptionLimit: stage.descriptionLimit,
2002
+ collapsed: stage.collapsedTags.has(endpoint.tag)
2003
+ })
2004
+ );
2005
+ return [`### ${tag}`, ...rendered].join("\n\n");
1287
2006
  }).filter(Boolean);
1288
- const models = referencedSchemas(spec);
1289
- const modelSection = models.length ? [
2007
+ let modelSpec = spec;
2008
+ if (detailEndpoints.length !== endpoints.length) {
2009
+ const scopedPaths = {};
2010
+ for (const endpoint of detailEndpoints) {
2011
+ const source = isObject(spec.paths?.[endpoint.path]) ? spec.paths[endpoint.path] : {};
2012
+ const item = scopedPaths[endpoint.path] ?? { ...source };
2013
+ for (const method of HTTP_METHODS2) delete item[method];
2014
+ item[endpoint.method] = source[endpoint.method];
2015
+ scopedPaths[endpoint.path] = item;
2016
+ }
2017
+ modelSpec = { ...spec, paths: scopedPaths };
2018
+ }
2019
+ const models = referencedSchemas(modelSpec);
2020
+ const modelSection = !stage.indexOnly && models.length ? [
1290
2021
  "## Data types",
1291
2022
  "",
1292
2023
  "```typescript",
@@ -1309,62 +2040,67 @@ function transformOpenApiToMarkdown(spec, sourceUrl) {
1309
2040
  "",
1310
2041
  "| Method | Path | Summary | Tags |",
1311
2042
  "|---|---|---|---|",
1312
- ...indexRows.length ? indexRows : ["| \u2014 | \u2014 | No operations declared | \u2014 |"],
1313
- "",
1314
- "## Endpoints",
1315
- "",
1316
- ...groupedEndpoints.length ? groupedEndpoints : ["_No endpoints declared._"],
1317
- ...modelSection ? ["", modelSection] : []
2043
+ ...indexRows.length ? indexRows : ["| \u2014 | \u2014 | No operations declared | \u2014 |"]
1318
2044
  ];
2045
+ if (!stage.indexOnly) {
2046
+ sections.push(
2047
+ "",
2048
+ "## Endpoints",
2049
+ "",
2050
+ ...groupedEndpoints.length ? groupedEndpoints : ["_No endpoints declared._"]
2051
+ );
2052
+ }
2053
+ if (modelSection) {
2054
+ sections.push("", modelSection);
2055
+ }
1319
2056
  return `${sections.join("\n").replace(/\n{3,}/g, "\n\n").trim()}
1320
2057
  `;
1321
2058
  }
2059
+ function appendTrimNote(markdown, actions, budget) {
2060
+ return `${markdown}
2061
+ ## Trimmed
1322
2062
 
1323
- // src/tokens.ts
1324
- function estimateTokens(text) {
1325
- const characters = text.length;
1326
- if (characters === 0) {
1327
- const zeroModel = (name, window2) => ({
1328
- name,
1329
- tokens: 0,
1330
- contextWindow: window2,
1331
- contextPercent: 0
1332
- });
1333
- return {
1334
- characters: 0,
1335
- models: {
1336
- claude: zeroModel("Claude (Sonnet / Opus)", 2e5),
1337
- openai: zeroModel("OpenAI (GPT-4o / o-series)", 128e3),
1338
- gemini: zeroModel("Google Gemini (Pro / Flash)", 1e6),
1339
- deepseek: zeroModel("DeepSeek (V3 / R1)", 128e3),
1340
- llama: zeroModel("Meta Llama (3.3 / 4)", 128e3)
1341
- }
1342
- };
2063
+ > _Trimmed to fit the ~${budget.toLocaleString()}-token budget: ${actions.join("; ")}._
2064
+ `;
2065
+ }
2066
+ function transformOpenApiToMarkdown(spec, sourceUrl, options = {}) {
2067
+ const warnings = [];
2068
+ const filteredSpec = filterSpec(spec, getEndpoints(spec), options, warnings);
2069
+ for (const message of warnings) options.onWarning?.(message);
2070
+ const normalizedSpec = normalizeInlinePayloadSchemas(filteredSpec);
2071
+ const endpoints = getEndpoints(normalizedSpec);
2072
+ const tags = collectTags(normalizedSpec, endpoints);
2073
+ const fullStage = {
2074
+ includeExamples: true,
2075
+ collapsedTags: /* @__PURE__ */ new Set(),
2076
+ indexOnly: false
2077
+ };
2078
+ if (options.maxTokens === void 0) {
2079
+ return renderDigest(normalizedSpec, sourceUrl, endpoints, tags, fullStage);
1343
2080
  }
1344
- const wordCount = (text.match(/\b[\w$]+\b/gu) || []).length;
1345
- const syntaxSymbols = (text.match(/[{}[\]()<>=:;,."`|/-]/g) || []).length;
1346
- const whitespaces = (text.match(/\s+/g) || []).length;
1347
- const baseTokens = wordCount * 1.18 + syntaxSymbols * 0.42 + whitespaces * 0.05;
1348
- const calculate = (name, ratio, contextWindow) => {
1349
- const tokens = Math.max(1, Math.round(baseTokens * ratio));
1350
- const contextPercent = Number((tokens / contextWindow * 100).toFixed(1));
1351
- return { name, tokens, contextWindow, contextPercent };
2081
+ const worst = (markdown2) => {
2082
+ const report = estimateTokens(markdown2);
2083
+ return Math.max(...Object.values(report.models).map((model) => model.tokens));
1352
2084
  };
1353
- return {
1354
- characters,
1355
- models: {
1356
- // Claude BPE splits subwords and code tokens slightly finer
1357
- claude: calculate("Claude (Sonnet / Opus)", 1.08, 2e5),
1358
- // OpenAI o200k_base has efficient compression on whitespace and symbols
1359
- openai: calculate("OpenAI (GPT-4o / o-series)", 0.95, 128e3),
1360
- // Gemini 256k vocab is the most compressed on markdown & code
1361
- gemini: calculate("Google Gemini (Pro / Flash)", 0.9, 1e6),
1362
- // DeepSeek 128k tokenizer
1363
- deepseek: calculate("DeepSeek (V3 / R1)", 0.97, 128e3),
1364
- // Llama 3/4 128k tokenizer
1365
- llama: calculate("Meta Llama (3.3 / 4)", 0.98, 128e3)
2085
+ const fits = (markdown2) => worst(markdown2) <= options.maxTokens;
2086
+ const full = renderDigest(normalizedSpec, sourceUrl, endpoints, tags, fullStage);
2087
+ if (fits(full)) return full;
2088
+ const stages = buildTrimStages(endpoints, tags);
2089
+ for (let i = 1; i < stages.length; i += 1) {
2090
+ const stage = stages[i];
2091
+ const markdown2 = renderDigest(normalizedSpec, sourceUrl, endpoints, tags, stage);
2092
+ if (fits(markdown2)) {
2093
+ return appendTrimNote(
2094
+ markdown2,
2095
+ stages.slice(1, i + 1).map((entry) => entry.action).filter((action) => Boolean(action)),
2096
+ options.maxTokens
2097
+ );
1366
2098
  }
1367
- };
2099
+ }
2100
+ const last = stages[stages.length - 1];
2101
+ const markdown = renderDigest(normalizedSpec, sourceUrl, endpoints, tags, last);
2102
+ const allActions = stages.slice(1).map((stage) => stage.action).filter((action) => Boolean(action));
2103
+ return appendTrimNote(markdown, allActions, options.maxTokens);
1368
2104
  }
1369
2105
 
1370
2106
  // src/writer.ts
@@ -1398,12 +2134,13 @@ async function writeDigest(options) {
1398
2134
  }
1399
2135
 
1400
2136
  export {
2137
+ VERSION,
2138
+ toTypeScriptIdentifier,
1401
2139
  isOpenApiSpec,
1402
2140
  parseSpecText,
1403
2141
  fetchOpenApiSpec,
1404
- toTypeScriptIdentifier,
1405
- transformOpenApiToMarkdown,
1406
2142
  estimateTokens,
2143
+ transformOpenApiToMarkdown,
1407
2144
  defaultOutputDirectory,
1408
2145
  defaultFilename,
1409
2146
  resolveOutputDirectory,