@j0hanz/fetch-url-mcp 1.9.1 → 1.9.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.
- package/dist/http/auth.d.ts +0 -1
- package/dist/http/auth.d.ts.map +1 -1
- package/dist/http/auth.js +1 -13
- package/dist/http/native.d.ts.map +1 -1
- package/dist/http/native.js +2 -5
- package/dist/lib/content.d.ts.map +1 -1
- package/dist/lib/content.js +301 -350
- package/dist/lib/core.d.ts +78 -71
- package/dist/lib/core.d.ts.map +1 -1
- package/dist/lib/core.js +308 -372
- package/dist/lib/fetch-pipeline.d.ts +2 -6
- package/dist/lib/fetch-pipeline.d.ts.map +1 -1
- package/dist/lib/fetch-pipeline.js +51 -137
- package/dist/lib/http.d.ts.map +1 -1
- package/dist/lib/http.js +188 -130
- package/dist/lib/mcp-tools.d.ts +3 -5
- package/dist/lib/mcp-tools.d.ts.map +1 -1
- package/dist/lib/mcp-tools.js +22 -58
- package/dist/lib/task-handlers.js +4 -4
- package/dist/lib/utils.d.ts +6 -0
- package/dist/lib/utils.d.ts.map +1 -1
- package/dist/lib/utils.js +23 -0
- package/dist/resources/index.js +1 -1
- package/dist/schemas.d.ts +0 -1
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +4 -6
- package/dist/server.js +1 -1
- package/dist/tasks/owner.d.ts +1 -1
- package/dist/tasks/owner.d.ts.map +1 -1
- package/dist/tasks/tool-registry.d.ts +1 -1
- package/dist/tasks/tool-registry.d.ts.map +1 -1
- package/dist/tools/fetch-url.d.ts +2 -3
- package/dist/tools/fetch-url.d.ts.map +1 -1
- package/dist/tools/fetch-url.js +89 -152
- package/dist/transform/transform.d.ts +8 -0
- package/dist/transform/transform.d.ts.map +1 -1
- package/dist/transform/transform.js +109 -108
- package/dist/transform/worker-pool.d.ts +3 -6
- package/dist/transform/worker-pool.d.ts.map +1 -1
- package/dist/transform/worker-pool.js +148 -118
- package/package.json +2 -1
package/dist/lib/http.js
CHANGED
|
@@ -18,6 +18,9 @@ import { get as cacheGet, config, getOperationId, getRequestId, logDebug, logErr
|
|
|
18
18
|
import { BLOCKED_HOST_SUFFIXES, createDnsPreflight, IpBlocker, RawUrlTransformer, SafeDnsResolver, UrlNormalizer, VALIDATION_ERROR_CODE, } from './url.js';
|
|
19
19
|
import { createErrorWithCode, FetchError, isAbortError, isError, isObject, isSystemError, toError, } from './utils.js';
|
|
20
20
|
import { formatZodError } from './zod.js';
|
|
21
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
22
|
+
// FILENAME GENERATION & DOWNLOAD
|
|
23
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
21
24
|
const FILENAME_RULES = {
|
|
22
25
|
MAX_LEN: 200,
|
|
23
26
|
UNSAFE_CHARS: /[<>:"/\\|?*\p{C}]/gu,
|
|
@@ -113,6 +116,9 @@ export function handleDownload(res, namespace, hash) {
|
|
|
113
116
|
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
114
117
|
res.end(content);
|
|
115
118
|
}
|
|
119
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
120
|
+
// ENCODING DETECTION
|
|
121
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
116
122
|
const UTF8_ENCODING = 'utf-8';
|
|
117
123
|
function getCharsetFromContentType(contentType) {
|
|
118
124
|
if (!contentType)
|
|
@@ -152,14 +158,22 @@ function isUnicodeWideEncoding(encoding) {
|
|
|
152
158
|
normalized === 'unicodefffe' ||
|
|
153
159
|
normalized === 'unicodefeff');
|
|
154
160
|
}
|
|
155
|
-
const
|
|
156
|
-
// 4-byte BOMs must come
|
|
161
|
+
const BOM_ENTRIES = [
|
|
162
|
+
// 4-byte BOMs must come before shorter prefixes to avoid false matches
|
|
157
163
|
{ bytes: [0xff, 0xfe, 0x00, 0x00], encoding: 'utf-32le' },
|
|
158
164
|
{ bytes: [0x00, 0x00, 0xfe, 0xff], encoding: 'utf-32be' },
|
|
159
165
|
{ bytes: [0xef, 0xbb, 0xbf], encoding: 'utf-8' },
|
|
160
166
|
{ bytes: [0xff, 0xfe], encoding: 'utf-16le' },
|
|
161
167
|
{ bytes: [0xfe, 0xff], encoding: 'utf-16be' },
|
|
162
168
|
];
|
|
169
|
+
const BOM_BY_FIRST_BYTE = new Map();
|
|
170
|
+
for (const entry of BOM_ENTRIES) {
|
|
171
|
+
const key = entry.bytes[0];
|
|
172
|
+
if (key === undefined)
|
|
173
|
+
continue;
|
|
174
|
+
const existing = BOM_BY_FIRST_BYTE.get(key);
|
|
175
|
+
BOM_BY_FIRST_BYTE.set(key, existing ? [...existing, entry] : [entry]);
|
|
176
|
+
}
|
|
163
177
|
function startsWithBytes(buffer, signature) {
|
|
164
178
|
const sigLen = signature.length;
|
|
165
179
|
if (buffer.length < sigLen)
|
|
@@ -171,7 +185,15 @@ function startsWithBytes(buffer, signature) {
|
|
|
171
185
|
return true;
|
|
172
186
|
}
|
|
173
187
|
function detectBomEncoding(buffer) {
|
|
174
|
-
|
|
188
|
+
if (buffer.length === 0)
|
|
189
|
+
return undefined;
|
|
190
|
+
const first = buffer[0];
|
|
191
|
+
if (first === undefined)
|
|
192
|
+
return undefined;
|
|
193
|
+
const candidates = BOM_BY_FIRST_BYTE.get(first);
|
|
194
|
+
if (!candidates)
|
|
195
|
+
return undefined;
|
|
196
|
+
for (const { bytes, encoding } of candidates) {
|
|
175
197
|
if (startsWithBytes(buffer, bytes))
|
|
176
198
|
return encoding;
|
|
177
199
|
}
|
|
@@ -231,6 +253,9 @@ function resolveEncoding(declaredEncoding, sample) {
|
|
|
231
253
|
return declaredEncoding;
|
|
232
254
|
return detectHtmlDeclaredEncoding(sample);
|
|
233
255
|
}
|
|
256
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
257
|
+
// BINARY DETECTION
|
|
258
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
234
259
|
const BINARY_SIGNATURES = [
|
|
235
260
|
[0x25, 0x50, 0x44, 0x46],
|
|
236
261
|
[0x89, 0x50, 0x4e, 0x47],
|
|
@@ -275,6 +300,9 @@ function isBinaryContent(buffer, encoding) {
|
|
|
275
300
|
}
|
|
276
301
|
return !isUnicodeWideEncoding(encoding) && hasNullByte(buffer, 1000);
|
|
277
302
|
}
|
|
303
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
304
|
+
// FETCH ERRORS
|
|
305
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
278
306
|
function parseRetryAfter(header) {
|
|
279
307
|
if (!header)
|
|
280
308
|
return 60;
|
|
@@ -357,6 +385,9 @@ function mapFetchError(error, fallbackUrl, timeoutMs) {
|
|
|
357
385
|
}
|
|
358
386
|
return createFetchError({ kind: 'network', message: error.message }, url);
|
|
359
387
|
}
|
|
388
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
389
|
+
// REDIRECT FOLLOWING
|
|
390
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
360
391
|
const REDIRECT_STATUSES = new Set([301, 302, 303, 307, 308]);
|
|
361
392
|
function isRedirectStatus(status) {
|
|
362
393
|
return REDIRECT_STATUSES.has(status);
|
|
@@ -503,6 +534,9 @@ class RedirectFollower {
|
|
|
503
534
|
}
|
|
504
535
|
}
|
|
505
536
|
}
|
|
537
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
538
|
+
// CONTENT VALIDATION & DECOMPRESSION
|
|
539
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
506
540
|
function resolveResponseError(response, finalUrl) {
|
|
507
541
|
if (response.status === 429) {
|
|
508
542
|
return createFetchError({ kind: 'rate-limited', retryAfter: response.headers.get('retry-after') }, finalUrl);
|
|
@@ -633,27 +667,10 @@ function createPumpedStream(initialChunk, reader) {
|
|
|
633
667
|
},
|
|
634
668
|
});
|
|
635
669
|
}
|
|
636
|
-
|
|
637
|
-
const
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
return response;
|
|
641
|
-
const encodings = parsedEncodings.filter((token) => token !== 'identity');
|
|
642
|
-
if (encodings.length === 0)
|
|
643
|
-
return response;
|
|
644
|
-
for (const encoding of encodings) {
|
|
645
|
-
if (!isSupportedContentEncoding(encoding)) {
|
|
646
|
-
throw createUnsupportedContentEncodingError(url, encodingHeader ?? encoding);
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (!response.body)
|
|
650
|
-
return response;
|
|
651
|
-
const [decodeBranch, passthroughBranch] = response.body.tee();
|
|
652
|
-
const decodeOrder = encodings
|
|
653
|
-
.slice()
|
|
654
|
-
.reverse()
|
|
655
|
-
.filter(isSupportedContentEncoding);
|
|
656
|
-
const decompressors = decodeOrder.map((encoding) => createDecompressor(encoding));
|
|
670
|
+
function buildDecodePipeline(body, encodings, url, response, signal) {
|
|
671
|
+
const [decodeBranch, passthroughBranch] = body.tee();
|
|
672
|
+
const decodeOrder = encodings.slice().reverse();
|
|
673
|
+
const decompressors = decodeOrder.map((enc) => createDecompressor(enc));
|
|
657
674
|
const decodeSource = Readable.fromWeb(toNodeReadableStream(decodeBranch, url, 'response:decode-content-encoding'));
|
|
658
675
|
const decodedNodeStream = new PassThrough();
|
|
659
676
|
const decodedPipeline = pipeline([
|
|
@@ -664,7 +681,7 @@ async function decodeResponseIfNeeded(response, url, signal) {
|
|
|
664
681
|
const headers = new Headers(response.headers);
|
|
665
682
|
headers.delete('content-encoding');
|
|
666
683
|
headers.delete('content-length');
|
|
667
|
-
const
|
|
684
|
+
const cleanup = () => {
|
|
668
685
|
decodeSource.destroy();
|
|
669
686
|
for (const decompressor of decompressors) {
|
|
670
687
|
decompressor.destroy();
|
|
@@ -672,33 +689,57 @@ async function decodeResponseIfNeeded(response, url, signal) {
|
|
|
672
689
|
decodedNodeStream.destroy();
|
|
673
690
|
};
|
|
674
691
|
if (signal) {
|
|
675
|
-
signal.addEventListener('abort',
|
|
692
|
+
signal.addEventListener('abort', cleanup, { once: true });
|
|
676
693
|
}
|
|
677
694
|
void decodedPipeline.catch((error) => {
|
|
678
695
|
decodedNodeStream.destroy(toError(error));
|
|
679
696
|
});
|
|
680
697
|
const decodedBodyStream = toWebReadableStream(decodedNodeStream, url, 'response:decode-content-encoding');
|
|
681
698
|
const decodedReader = decodedBodyStream.getReader();
|
|
699
|
+
return {
|
|
700
|
+
decodedReader,
|
|
701
|
+
passthroughBranch,
|
|
702
|
+
decodedNodeStream,
|
|
703
|
+
headers,
|
|
704
|
+
cleanup,
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
async function decodeResponseIfNeeded(response, url, signal) {
|
|
708
|
+
const encodingHeader = response.headers.get('content-encoding');
|
|
709
|
+
const parsedEncodings = parseContentEncodings(encodingHeader);
|
|
710
|
+
if (!parsedEncodings)
|
|
711
|
+
return response;
|
|
712
|
+
const encodings = parsedEncodings.filter((token) => token !== 'identity');
|
|
713
|
+
if (encodings.length === 0)
|
|
714
|
+
return response;
|
|
715
|
+
for (const encoding of encodings) {
|
|
716
|
+
if (!isSupportedContentEncoding(encoding)) {
|
|
717
|
+
throw createUnsupportedContentEncodingError(url, encodingHeader ?? encoding);
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
if (!response.body)
|
|
721
|
+
return response;
|
|
722
|
+
const pipe = buildDecodePipeline(response.body, encodings.filter(isSupportedContentEncoding), url, response, signal);
|
|
682
723
|
const clearAbortListener = () => {
|
|
683
724
|
if (!signal)
|
|
684
725
|
return;
|
|
685
|
-
signal.removeEventListener('abort',
|
|
726
|
+
signal.removeEventListener('abort', pipe.cleanup);
|
|
686
727
|
};
|
|
687
728
|
try {
|
|
688
|
-
const first = await decodedReader.read();
|
|
729
|
+
const first = await pipe.decodedReader.read();
|
|
689
730
|
if (first.done) {
|
|
690
731
|
clearAbortListener();
|
|
691
|
-
void passthroughBranch.cancel().catch(() => undefined);
|
|
732
|
+
void pipe.passthroughBranch.cancel().catch(() => undefined);
|
|
692
733
|
return new Response(null, {
|
|
693
734
|
status: response.status,
|
|
694
735
|
statusText: response.statusText,
|
|
695
|
-
headers,
|
|
736
|
+
headers: pipe.headers,
|
|
696
737
|
});
|
|
697
738
|
}
|
|
698
|
-
void passthroughBranch.cancel().catch(() => undefined);
|
|
699
|
-
const body = createPumpedStream(first.value, decodedReader);
|
|
739
|
+
void pipe.passthroughBranch.cancel().catch(() => undefined);
|
|
740
|
+
const body = createPumpedStream(first.value, pipe.decodedReader);
|
|
700
741
|
if (signal) {
|
|
701
|
-
void finished(decodedNodeStream, { cleanup: true })
|
|
742
|
+
void finished(pipe.decodedNodeStream, { cleanup: true })
|
|
702
743
|
.catch(() => { })
|
|
703
744
|
.finally(() => {
|
|
704
745
|
clearAbortListener();
|
|
@@ -707,17 +748,76 @@ async function decodeResponseIfNeeded(response, url, signal) {
|
|
|
707
748
|
return new Response(body, {
|
|
708
749
|
status: response.status,
|
|
709
750
|
statusText: response.statusText,
|
|
710
|
-
headers,
|
|
751
|
+
headers: pipe.headers,
|
|
711
752
|
});
|
|
712
753
|
}
|
|
713
754
|
catch (error) {
|
|
714
755
|
clearAbortListener();
|
|
715
|
-
|
|
716
|
-
void decodedReader.cancel(error).catch(() => undefined);
|
|
717
|
-
void passthroughBranch.cancel().catch(() => undefined);
|
|
756
|
+
pipe.cleanup();
|
|
757
|
+
void pipe.decodedReader.cancel(error).catch(() => undefined);
|
|
758
|
+
void pipe.passthroughBranch.cancel().catch(() => undefined);
|
|
718
759
|
throw new FetchError(`Content-Encoding decode failed for ${redactUrl(url)}: ${isError(error) ? error.message : String(error)}`, url);
|
|
719
760
|
}
|
|
720
761
|
}
|
|
762
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
763
|
+
// RESPONSE READING
|
|
764
|
+
// ═══════════════════════════════════════════════════════════════════
|
|
765
|
+
class BoundedBufferTransform extends Transform {
|
|
766
|
+
byteLimit;
|
|
767
|
+
captureChunks;
|
|
768
|
+
url;
|
|
769
|
+
declaredEncoding;
|
|
770
|
+
total = 0;
|
|
771
|
+
chunks = [];
|
|
772
|
+
effectiveEncoding;
|
|
773
|
+
encodingResolved = false;
|
|
774
|
+
constructor(byteLimit, captureChunks, url, declaredEncoding) {
|
|
775
|
+
super();
|
|
776
|
+
this.byteLimit = byteLimit;
|
|
777
|
+
this.captureChunks = captureChunks;
|
|
778
|
+
this.url = url;
|
|
779
|
+
this.declaredEncoding = declaredEncoding;
|
|
780
|
+
this.effectiveEncoding = declaredEncoding ?? 'utf-8';
|
|
781
|
+
}
|
|
782
|
+
_transform(chunk, _encoding, callback) {
|
|
783
|
+
try {
|
|
784
|
+
const buf = Buffer.isBuffer(chunk)
|
|
785
|
+
? chunk
|
|
786
|
+
: Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
787
|
+
if (!this.encodingResolved) {
|
|
788
|
+
this.encodingResolved = true;
|
|
789
|
+
this.effectiveEncoding =
|
|
790
|
+
resolveEncoding(this.declaredEncoding, buf) ??
|
|
791
|
+
this.declaredEncoding ??
|
|
792
|
+
'utf-8';
|
|
793
|
+
}
|
|
794
|
+
if (isBinaryContent(buf, this.effectiveEncoding)) {
|
|
795
|
+
callback(new FetchError('Detailed content type check failed: binary content detected', this.url, 500, { reason: 'binary_content_detected' }));
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
const newTotal = this.total + buf.length;
|
|
799
|
+
if (newTotal > this.byteLimit) {
|
|
800
|
+
const remaining = this.byteLimit - this.total;
|
|
801
|
+
if (remaining > 0) {
|
|
802
|
+
const slice = buf.subarray(0, remaining);
|
|
803
|
+
this.total += remaining;
|
|
804
|
+
if (this.captureChunks)
|
|
805
|
+
this.chunks.push(slice);
|
|
806
|
+
this.push(slice);
|
|
807
|
+
}
|
|
808
|
+
callback(new MaxBytesError());
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
this.total = newTotal;
|
|
812
|
+
if (this.captureChunks)
|
|
813
|
+
this.chunks.push(buf);
|
|
814
|
+
callback(null, buf);
|
|
815
|
+
}
|
|
816
|
+
catch (error) {
|
|
817
|
+
callback(toError(error));
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
}
|
|
721
821
|
class ResponseTextReader {
|
|
722
822
|
async read(response, url, maxBytes, signal, encoding) {
|
|
723
823
|
const { buffer, encoding: effectiveEncoding, truncated, } = await this.readBuffer(response, url, maxBytes, signal, encoding);
|
|
@@ -738,27 +838,10 @@ class ResponseTextReader {
|
|
|
738
838
|
if (signal?.aborted)
|
|
739
839
|
throw createFetchError({ kind: 'canceled' }, url);
|
|
740
840
|
const limit = maxBytes <= 0 ? Number.POSITIVE_INFINITY : maxBytes;
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
const blob = await response.blob();
|
|
746
|
-
if (Number.isFinite(limit) && blob.size > limit) {
|
|
747
|
-
const sliced = blob.slice(0, limit);
|
|
748
|
-
buffer = new Uint8Array(await sliced.arrayBuffer());
|
|
749
|
-
truncated = true;
|
|
750
|
-
}
|
|
751
|
-
else {
|
|
752
|
-
buffer = new Uint8Array(await blob.arrayBuffer());
|
|
753
|
-
}
|
|
754
|
-
}
|
|
755
|
-
catch {
|
|
756
|
-
// Fallback if blob() fails
|
|
757
|
-
const arrayBuffer = await response.arrayBuffer();
|
|
758
|
-
const length = Math.min(arrayBuffer.byteLength, limit);
|
|
759
|
-
buffer = new Uint8Array(arrayBuffer, 0, length);
|
|
760
|
-
truncated = Number.isFinite(limit) && arrayBuffer.byteLength > limit;
|
|
761
|
-
}
|
|
841
|
+
const arrayBuffer = await response.arrayBuffer();
|
|
842
|
+
const truncated = Number.isFinite(limit) && arrayBuffer.byteLength > limit;
|
|
843
|
+
const length = truncated ? limit : arrayBuffer.byteLength;
|
|
844
|
+
const buffer = new Uint8Array(arrayBuffer, 0, length);
|
|
762
845
|
const effectiveEncoding = resolveEncoding(encoding, buffer) ?? encoding ?? 'utf-8';
|
|
763
846
|
if (isBinaryContent(buffer, effectiveEncoding)) {
|
|
764
847
|
throw new FetchError('Detailed content type check failed: binary content detected', url, 500, { reason: 'binary_content_detected' });
|
|
@@ -773,49 +856,8 @@ class ResponseTextReader {
|
|
|
773
856
|
async readStreamToBuffer(stream, url, maxBytes, signal, encoding) {
|
|
774
857
|
const byteLimit = maxBytes <= 0 ? Number.POSITIVE_INFINITY : maxBytes;
|
|
775
858
|
const captureChunks = byteLimit !== Number.POSITIVE_INFINITY;
|
|
776
|
-
let effectiveEncoding = encoding ?? 'utf-8';
|
|
777
|
-
let encodingResolved = false;
|
|
778
|
-
let total = 0;
|
|
779
|
-
const chunks = [];
|
|
780
859
|
const source = Readable.fromWeb(toNodeReadableStream(stream, url, 'response:read-stream-buffer'));
|
|
781
|
-
const guard = new
|
|
782
|
-
transform(chunk, _encoding, callback) {
|
|
783
|
-
try {
|
|
784
|
-
const buf = Buffer.isBuffer(chunk)
|
|
785
|
-
? chunk
|
|
786
|
-
: Buffer.from(chunk.buffer, chunk.byteOffset, chunk.byteLength);
|
|
787
|
-
if (!encodingResolved) {
|
|
788
|
-
encodingResolved = true;
|
|
789
|
-
effectiveEncoding =
|
|
790
|
-
resolveEncoding(encoding, buf) ?? encoding ?? 'utf-8';
|
|
791
|
-
}
|
|
792
|
-
if (isBinaryContent(buf, effectiveEncoding)) {
|
|
793
|
-
callback(new FetchError('Detailed content type check failed: binary content detected', url, 500, { reason: 'binary_content_detected' }));
|
|
794
|
-
return;
|
|
795
|
-
}
|
|
796
|
-
const newTotal = total + buf.length;
|
|
797
|
-
if (newTotal > byteLimit) {
|
|
798
|
-
const remaining = byteLimit - total;
|
|
799
|
-
if (remaining > 0) {
|
|
800
|
-
const slice = buf.subarray(0, remaining);
|
|
801
|
-
total += remaining;
|
|
802
|
-
if (captureChunks)
|
|
803
|
-
chunks.push(slice);
|
|
804
|
-
this.push(slice);
|
|
805
|
-
}
|
|
806
|
-
callback(new MaxBytesError());
|
|
807
|
-
return;
|
|
808
|
-
}
|
|
809
|
-
total = newTotal;
|
|
810
|
-
if (captureChunks)
|
|
811
|
-
chunks.push(buf);
|
|
812
|
-
callback(null, buf);
|
|
813
|
-
}
|
|
814
|
-
catch (error) {
|
|
815
|
-
callback(toError(error));
|
|
816
|
-
}
|
|
817
|
-
},
|
|
818
|
-
});
|
|
860
|
+
const guard = new BoundedBufferTransform(byteLimit, captureChunks, url, encoding);
|
|
819
861
|
const guarded = source.pipe(guard);
|
|
820
862
|
const abortHandler = () => {
|
|
821
863
|
source.destroy();
|
|
@@ -828,8 +870,8 @@ class ResponseTextReader {
|
|
|
828
870
|
const buffer = await consumeBuffer(guarded);
|
|
829
871
|
return {
|
|
830
872
|
buffer,
|
|
831
|
-
encoding: effectiveEncoding,
|
|
832
|
-
size: total,
|
|
873
|
+
encoding: guard.effectiveEncoding,
|
|
874
|
+
size: guard.total,
|
|
833
875
|
truncated: false,
|
|
834
876
|
};
|
|
835
877
|
}
|
|
@@ -842,9 +884,9 @@ class ResponseTextReader {
|
|
|
842
884
|
source.destroy();
|
|
843
885
|
guard.destroy();
|
|
844
886
|
return {
|
|
845
|
-
buffer: Buffer.concat(chunks, total),
|
|
846
|
-
encoding: effectiveEncoding,
|
|
847
|
-
size: total,
|
|
887
|
+
buffer: Buffer.concat(guard.chunks, guard.total),
|
|
888
|
+
encoding: guard.effectiveEncoding,
|
|
889
|
+
size: guard.total,
|
|
848
890
|
truncated: true,
|
|
849
891
|
};
|
|
850
892
|
}
|
|
@@ -914,14 +956,6 @@ class FetchTelemetry {
|
|
|
914
956
|
redact(url) {
|
|
915
957
|
return this.redactor.redact(url);
|
|
916
958
|
}
|
|
917
|
-
contextFields(ctx) {
|
|
918
|
-
return {
|
|
919
|
-
...(ctx.contextRequestId
|
|
920
|
-
? { contextRequestId: ctx.contextRequestId }
|
|
921
|
-
: {}),
|
|
922
|
-
...(ctx.operationId ? { operationId: ctx.operationId } : {}),
|
|
923
|
-
};
|
|
924
|
-
}
|
|
925
959
|
start(url, method) {
|
|
926
960
|
const safeUrl = this.redactor.redact(url);
|
|
927
961
|
const contextRequestId = this.context.getRequestId();
|
|
@@ -936,7 +970,12 @@ class FetchTelemetry {
|
|
|
936
970
|
ctx.contextRequestId = contextRequestId;
|
|
937
971
|
if (operationId)
|
|
938
972
|
ctx.operationId = operationId;
|
|
939
|
-
const ctxFields =
|
|
973
|
+
const ctxFields = {
|
|
974
|
+
...(ctx.contextRequestId
|
|
975
|
+
? { contextRequestId: ctx.contextRequestId }
|
|
976
|
+
: {}),
|
|
977
|
+
...(ctx.operationId ? { operationId: ctx.operationId } : {}),
|
|
978
|
+
};
|
|
940
979
|
this.publish({
|
|
941
980
|
v: 1,
|
|
942
981
|
type: 'start',
|
|
@@ -956,7 +995,12 @@ class FetchTelemetry {
|
|
|
956
995
|
recordResponse(context, response, contentSize) {
|
|
957
996
|
const duration = performance.now() - context.startTime;
|
|
958
997
|
const durationLabel = `${Math.round(duration)}ms`;
|
|
959
|
-
const ctxFields =
|
|
998
|
+
const ctxFields = {
|
|
999
|
+
...(context.contextRequestId
|
|
1000
|
+
? { contextRequestId: context.contextRequestId }
|
|
1001
|
+
: {}),
|
|
1002
|
+
...(context.operationId ? { operationId: context.operationId } : {}),
|
|
1003
|
+
};
|
|
960
1004
|
this.publish({
|
|
961
1005
|
v: 1,
|
|
962
1006
|
type: 'end',
|
|
@@ -991,7 +1035,12 @@ class FetchTelemetry {
|
|
|
991
1035
|
const duration = performance.now() - context.startTime;
|
|
992
1036
|
const err = toError(error);
|
|
993
1037
|
const code = isSystemError(err) ? err.code : undefined;
|
|
994
|
-
const ctxFields =
|
|
1038
|
+
const ctxFields = {
|
|
1039
|
+
...(context.contextRequestId
|
|
1040
|
+
? { contextRequestId: context.contextRequestId }
|
|
1041
|
+
: {}),
|
|
1042
|
+
...(context.operationId ? { operationId: context.operationId } : {}),
|
|
1043
|
+
};
|
|
995
1044
|
this.publish({
|
|
996
1045
|
v: 1,
|
|
997
1046
|
type: 'error',
|
|
@@ -1060,7 +1109,7 @@ class HttpFetcher {
|
|
|
1060
1109
|
}
|
|
1061
1110
|
async fetchNormalized(normalizedUrl, mode, options) {
|
|
1062
1111
|
const timeoutMs = this.fetcherConfig.timeout;
|
|
1063
|
-
const headers =
|
|
1112
|
+
const headers = DEFAULT_HEADERS;
|
|
1064
1113
|
const signal = buildRequestSignal(timeoutMs, options?.signal);
|
|
1065
1114
|
const init = buildRequestInit(headers, signal);
|
|
1066
1115
|
const ctx = this.telemetry.start(normalizedUrl, 'GET');
|
|
@@ -1107,9 +1156,6 @@ const DEFAULT_HEADERS = {
|
|
|
1107
1156
|
// The undici-based globalThis.fetch manages content negotiation and
|
|
1108
1157
|
// decompression transparently per the Fetch spec.
|
|
1109
1158
|
};
|
|
1110
|
-
function buildHeaders() {
|
|
1111
|
-
return DEFAULT_HEADERS;
|
|
1112
|
-
}
|
|
1113
1159
|
function buildRequestSignal(timeoutMs, external) {
|
|
1114
1160
|
if (timeoutMs <= 0)
|
|
1115
1161
|
return external;
|
|
@@ -1123,16 +1169,28 @@ function buildRequestInit(headers, signal) {
|
|
|
1123
1169
|
...(signal ? { signal } : {}),
|
|
1124
1170
|
};
|
|
1125
1171
|
}
|
|
1126
|
-
|
|
1127
|
-
const
|
|
1128
|
-
const
|
|
1129
|
-
const
|
|
1130
|
-
const
|
|
1131
|
-
const
|
|
1132
|
-
const
|
|
1133
|
-
const
|
|
1134
|
-
const
|
|
1135
|
-
const
|
|
1172
|
+
function createHttpModule() {
|
|
1173
|
+
const ipBlocker = new IpBlocker(config.security);
|
|
1174
|
+
const urlNormalizer = new UrlNormalizer(config.constants, config.security, ipBlocker, BLOCKED_HOST_SUFFIXES);
|
|
1175
|
+
const rawUrlTransformer = new RawUrlTransformer(defaultLogger);
|
|
1176
|
+
const dnsResolver = new SafeDnsResolver(ipBlocker, config.security, BLOCKED_HOST_SUFFIXES);
|
|
1177
|
+
const tel = new FetchTelemetry(defaultLogger, defaultContext, defaultRedactor);
|
|
1178
|
+
const normalizeRedirectUrl = (url) => urlNormalizer.validateAndNormalize(url);
|
|
1179
|
+
const dnsPreflight = createDnsPreflight(dnsResolver);
|
|
1180
|
+
const secureRedirectFollower = new RedirectFollower(defaultFetch, normalizeRedirectUrl, dnsPreflight);
|
|
1181
|
+
const responseReader = new ResponseTextReader();
|
|
1182
|
+
const httpFetcher = new HttpFetcher(config.fetcher, secureRedirectFollower, responseReader, tel);
|
|
1183
|
+
return {
|
|
1184
|
+
ipBlocker,
|
|
1185
|
+
urlNormalizer,
|
|
1186
|
+
rawUrlTransformer,
|
|
1187
|
+
telemetry: tel,
|
|
1188
|
+
secureRedirectFollower,
|
|
1189
|
+
responseReader,
|
|
1190
|
+
httpFetcher,
|
|
1191
|
+
};
|
|
1192
|
+
}
|
|
1193
|
+
const { ipBlocker, urlNormalizer, rawUrlTransformer, telemetry, secureRedirectFollower, responseReader, httpFetcher, } = createHttpModule();
|
|
1136
1194
|
export function isBlockedIp(ip) {
|
|
1137
1195
|
return ipBlocker.isBlockedIp(ip);
|
|
1138
1196
|
}
|
package/dist/lib/mcp-tools.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ declare const jsonRpcResponseSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
17
17
|
result: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
18
18
|
}, z.core.$strict>, z.ZodObject<{
|
|
19
19
|
jsonrpc: z.ZodLiteral<"2.0">;
|
|
20
|
-
id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber
|
|
20
|
+
id: z.ZodOptional<z.ZodUnion<[z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodNull]>>;
|
|
21
21
|
error: z.ZodObject<{
|
|
22
22
|
code: z.ZodNumber;
|
|
23
23
|
message: z.ZodString;
|
|
@@ -35,7 +35,7 @@ declare const jsonRpcMessageSchema: z.ZodUnion<readonly [z.ZodObject<{
|
|
|
35
35
|
result: z.ZodRecord<z.ZodString, z.ZodUnknown>;
|
|
36
36
|
}, z.core.$strict>, z.ZodObject<{
|
|
37
37
|
jsonrpc: z.ZodLiteral<"2.0">;
|
|
38
|
-
id: z.ZodOptional<z.ZodUnion<readonly [z.ZodString, z.ZodNumber
|
|
38
|
+
id: z.ZodOptional<z.ZodUnion<[z.ZodUnion<readonly [z.ZodString, z.ZodNumber]>, z.ZodNull]>>;
|
|
39
39
|
error: z.ZodObject<{
|
|
40
40
|
code: z.ZodNumber;
|
|
41
41
|
message: z.ZodString;
|
|
@@ -60,7 +60,5 @@ export declare function createToolErrorResponse(message: string, url: string, ex
|
|
|
60
60
|
details?: Record<string, unknown>;
|
|
61
61
|
}): ToolErrorResponse;
|
|
62
62
|
export declare function handleToolError(error: unknown, url: string, fallbackMessage?: string): ToolErrorResponse;
|
|
63
|
-
export {
|
|
64
|
-
export { readNestedRecord, withSignal, TRUNCATION_MARKER, type InlineContentResult, appendTruncationMarker, type PipelineResult, type SharedFetchStage, executeFetchPipeline, type MarkdownPipelineResult, parseCachedMarkdownResult, markdownTransform, serializeMarkdownResult, performSharedFetch, } from './fetch-pipeline.js';
|
|
65
|
-
export { type ProgressNotificationParams, type ProgressNotification, type ToolHandlerExtra, type ProgressReporter, createProgressReporter, } from './progress.js';
|
|
63
|
+
export {};
|
|
66
64
|
//# sourceMappingURL=mcp-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"mcp-tools.d.ts","sourceRoot":"","sources":["../../src/lib/mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAC/C,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,QAAA,MAAM,oBAAoB;;;;;kBAKxB,CAAC;AAeH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;oBAGzB,CAAC;AACH,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;sBAGxB,CAAC;AACH,KAAK,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3D,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACjE,KAAK,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC/D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAE5D;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc,CAEtE;AACD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,GACZ,IAAI,IAAI,mBAAmB,CAE7B;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,kBAAkB,CAE1E;
|
|
1
|
+
{"version":3,"file":"mcp-tools.d.ts","sourceRoot":"","sources":["../../src/lib/mcp-tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oCAAoC,CAAC;AACzE,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAQxB,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;AAC/C,UAAU,gBAAgB;IACxB,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAChC,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAGD,QAAA,MAAM,oBAAoB;;;;;kBAKxB,CAAC;AAeH,QAAA,MAAM,qBAAqB;;;;;;;;;;;;oBAGzB,CAAC;AACH,QAAA,MAAM,oBAAoB;;;;;;;;;;;;;;;;;sBAGxB,CAAC;AACH,KAAK,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC3D,KAAK,mBAAmB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACjE,KAAK,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAC/D,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAE5D;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,cAAc,CAEtE;AACD,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,OAAO,GACZ,IAAI,IAAI,mBAAmB,CAE7B;AACD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI,IAAI,kBAAkB,CAE1E;AAUD,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,OAAO,CAG7E;AACD,wBAAgB,yBAAyB,CACvC,MAAM,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAChC,OAAO,CAUT;AAMD,KAAK,iBAAiB,GAAG,cAAc,GAAG;IACxC,OAAO,EAAE,IAAI,CAAC;CACf,CAAC;AA6BF,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,GAAG,EAAE,MAAM,EACX,KAAK,CAAC,EAAE;IACN,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC,GACA,iBAAiB,CAenB;AAQD,wBAAgB,eAAe,CAC7B,KAAK,EAAE,OAAO,EACd,GAAG,EAAE,MAAM,EACX,eAAe,SAAqB,GACnC,iBAAiB,CA2BnB"}
|
package/dist/lib/mcp-tools.js
CHANGED
|
@@ -11,12 +11,12 @@ const jsonRpcRequestSchema = z.strictObject({
|
|
|
11
11
|
});
|
|
12
12
|
const jsonRpcResultResponseSchema = z.strictObject({
|
|
13
13
|
jsonrpc: z.literal('2.0'),
|
|
14
|
-
id:
|
|
14
|
+
id: jsonRpcRequestIdSchema,
|
|
15
15
|
result: z.record(z.string(), z.unknown()),
|
|
16
16
|
});
|
|
17
17
|
const jsonRpcErrorResponseSchema = z.strictObject({
|
|
18
18
|
jsonrpc: z.literal('2.0'),
|
|
19
|
-
id:
|
|
19
|
+
id: jsonRpcRequestIdSchema.or(z.null()).optional(),
|
|
20
20
|
error: z.strictObject({
|
|
21
21
|
code: z.number().int(),
|
|
22
22
|
message: z.string(),
|
|
@@ -48,29 +48,19 @@ function parseAcceptMediaTypes(header) {
|
|
|
48
48
|
return [];
|
|
49
49
|
return header
|
|
50
50
|
.split(',')
|
|
51
|
-
.map((
|
|
52
|
-
.filter((
|
|
53
|
-
}
|
|
54
|
-
function extractAcceptMediaType(value) {
|
|
55
|
-
return value.split(';', 1)[0]?.trim().toLowerCase() ?? '';
|
|
51
|
+
.map((v) => v.split(';', 1)[0]?.trim().toLowerCase() ?? '')
|
|
52
|
+
.filter((v) => v.length > 0);
|
|
56
53
|
}
|
|
57
54
|
export function acceptsEventStream(header) {
|
|
58
55
|
const mediaTypes = parseAcceptMediaTypes(header);
|
|
59
56
|
return mediaTypes.some((mediaType) => mediaType === 'text/event-stream');
|
|
60
57
|
}
|
|
61
|
-
function hasAcceptedMediaType(mediaTypes, exact, wildcardPrefix) {
|
|
62
|
-
return mediaTypes.some((mediaType) => typeof mediaType === 'string' &&
|
|
63
|
-
mediaType.length > 0 &&
|
|
64
|
-
(mediaType === '*/*' ||
|
|
65
|
-
mediaType === exact ||
|
|
66
|
-
mediaType === wildcardPrefix));
|
|
67
|
-
}
|
|
68
58
|
export function acceptsJsonAndEventStream(header) {
|
|
69
59
|
const mediaTypes = parseAcceptMediaTypes(header);
|
|
70
|
-
const acceptsJson =
|
|
60
|
+
const acceptsJson = mediaTypes.some((m) => m === '*/*' || m === 'application/json' || m === 'application/*');
|
|
71
61
|
if (!acceptsJson)
|
|
72
62
|
return false;
|
|
73
|
-
return
|
|
63
|
+
return mediaTypes.some((m) => m === '*/*' || m === 'text/event-stream' || m === 'text/*');
|
|
74
64
|
}
|
|
75
65
|
const PUBLIC_ERROR_REASONS = new Set(['aborted', 'queue_full', 'timeout']);
|
|
76
66
|
function sanitizeToolErrorDetails(details) {
|
|
@@ -89,14 +79,6 @@ function sanitizeToolErrorDetails(details) {
|
|
|
89
79
|
}
|
|
90
80
|
return Object.keys(sanitized).length > 0 ? sanitized : undefined;
|
|
91
81
|
}
|
|
92
|
-
function resolvePublicFetchErrorCode(error) {
|
|
93
|
-
const { code: detailsCode, reason } = error.details;
|
|
94
|
-
if (typeof detailsCode === 'string')
|
|
95
|
-
return detailsCode;
|
|
96
|
-
if (reason === 'queue_full')
|
|
97
|
-
return 'queue_full';
|
|
98
|
-
return undefined;
|
|
99
|
-
}
|
|
100
82
|
export function createToolErrorResponse(message, url, extra) {
|
|
101
83
|
const errorContent = {
|
|
102
84
|
error: message,
|
|
@@ -117,47 +99,29 @@ function isValidationError(error) {
|
|
|
117
99
|
isSystemError(error) &&
|
|
118
100
|
error.code === 'VALIDATION_ERROR');
|
|
119
101
|
}
|
|
120
|
-
function isHandledToolError(error) {
|
|
121
|
-
return error instanceof FetchError || isValidationError(error);
|
|
122
|
-
}
|
|
123
|
-
function resolveToolErrorMessage(error, fallbackMessage) {
|
|
124
|
-
if (isHandledToolError(error)) {
|
|
125
|
-
return error.message;
|
|
126
|
-
}
|
|
127
|
-
if (error instanceof Error) {
|
|
128
|
-
return `${fallbackMessage}: ${error.message}`;
|
|
129
|
-
}
|
|
130
|
-
return `${fallbackMessage}: Unknown error`;
|
|
131
|
-
}
|
|
132
|
-
function resolveToolErrorCode(error) {
|
|
133
|
-
if (error instanceof FetchError) {
|
|
134
|
-
return resolvePublicFetchErrorCode(error) ?? error.code;
|
|
135
|
-
}
|
|
136
|
-
if (isValidationError(error))
|
|
137
|
-
return 'VALIDATION_ERROR';
|
|
138
|
-
if (isAbortError(error))
|
|
139
|
-
return 'ABORTED';
|
|
140
|
-
return 'FETCH_ERROR';
|
|
141
|
-
}
|
|
142
102
|
export function handleToolError(error, url, fallbackMessage = 'Operation failed') {
|
|
143
|
-
const message = resolveToolErrorMessage(error, fallbackMessage);
|
|
144
|
-
const code = resolveToolErrorCode(error);
|
|
145
103
|
if (error instanceof FetchError) {
|
|
104
|
+
const { code: detailsCode, reason } = error.details;
|
|
105
|
+
const code = (typeof detailsCode === 'string'
|
|
106
|
+
? detailsCode
|
|
107
|
+
: reason === 'queue_full'
|
|
108
|
+
? 'queue_full'
|
|
109
|
+
: undefined) ?? error.code;
|
|
146
110
|
const details = sanitizeToolErrorDetails(error.details);
|
|
147
|
-
return createToolErrorResponse(message, url, {
|
|
111
|
+
return createToolErrorResponse(error.message, url, {
|
|
148
112
|
code,
|
|
149
113
|
statusCode: error.statusCode,
|
|
150
114
|
...(details ? { details } : {}),
|
|
151
115
|
});
|
|
152
116
|
}
|
|
117
|
+
if (isValidationError(error)) {
|
|
118
|
+
return createToolErrorResponse(error.message, url, {
|
|
119
|
+
code: 'VALIDATION_ERROR',
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
const code = isAbortError(error) ? 'ABORTED' : 'FETCH_ERROR';
|
|
123
|
+
const message = error instanceof Error
|
|
124
|
+
? `${fallbackMessage}: ${error.message}`
|
|
125
|
+
: `${fallbackMessage}: Unknown error`;
|
|
153
126
|
return createToolErrorResponse(message, url, { code });
|
|
154
127
|
}
|
|
155
|
-
/* -------------------------------------------------------------------------------------------------
|
|
156
|
-
* Re-exports from split modules
|
|
157
|
-
*
|
|
158
|
-
* Preserves backward compatibility — consumers import from 'lib/mcp-tools.js'
|
|
159
|
-
* without changes. Direct imports from the sub-modules are preferred for new code.
|
|
160
|
-
* ------------------------------------------------------------------------------------------------- */
|
|
161
|
-
export { registerServerLifecycleCleanup, registerTaskHandlers, cancelTasksForOwner, abortAllTaskExecutions, } from './task-handlers.js';
|
|
162
|
-
export { readNestedRecord, withSignal, TRUNCATION_MARKER, appendTruncationMarker, executeFetchPipeline, parseCachedMarkdownResult, markdownTransform, serializeMarkdownResult, performSharedFetch, } from './fetch-pipeline.js';
|
|
163
|
-
export { createProgressReporter, } from './progress.js';
|