@bedolla/enrivision 0.1.1 → 0.1.3

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.
@@ -0,0 +1,269 @@
1
+ /**
2
+ * BOUNDED MEDIA URL FETCHER
3
+ *
4
+ * Downloads one http(s) media resource into a temporary directory so the
5
+ * upload pipeline can treat it like any local file.
6
+ *
7
+ * Protections: 64 MiB size cap, 60 s timeout, SSRF guard (literal private
8
+ * addresses rejected, DNS-resolved addresses rejected, per-hop validation of
9
+ * up to 3 redirects), and an owned cleanup callback.
10
+ *
11
+ * @module shared/mediaUrlFetcher
12
+ */
13
+ import { isIP } from "node:net";
14
+ import { lookup as dnsLookup } from "node:dns/promises";
15
+ import { mkdtemp, rm, writeFile } from "node:fs/promises";
16
+ import { tmpdir } from "node:os";
17
+ import { join } from "node:path";
18
+ /**
19
+ * Immutable media URL fetch result record.
20
+ */
21
+ class MediaUrlFetchResultRecord {
22
+ localPath;
23
+ contentType;
24
+ cleanupCallback;
25
+ /**
26
+ * Creates one fetch result.
27
+ *
28
+ * @param localPath - Downloaded file path.
29
+ * @param contentType - Reported content type.
30
+ * @param cleanupCallback - Owned cleanup callback.
31
+ */
32
+ constructor(localPath, contentType, cleanupCallback) {
33
+ this.localPath = localPath;
34
+ this.contentType = contentType;
35
+ this.cleanupCallback = cleanupCallback;
36
+ }
37
+ /**
38
+ * @inheritdoc
39
+ */
40
+ async cleanup() {
41
+ await this.cleanupCallback();
42
+ }
43
+ }
44
+ /**
45
+ * Downloads bounded http(s) media into a temporary directory.
46
+ */
47
+ export class MediaUrlFetcher {
48
+ /**
49
+ * Maximum accepted download size in bytes.
50
+ */
51
+ static MAX_BYTES = 64 * 1024 * 1024;
52
+ /**
53
+ * Download timeout in milliseconds.
54
+ */
55
+ static TIMEOUT_MS = 60_000;
56
+ /**
57
+ * Maximum followed redirects (each hop re-validated).
58
+ */
59
+ static MAX_REDIRECTS = 3;
60
+ /**
61
+ * Fetch implementation (injectable for tests).
62
+ */
63
+ fetchImpl;
64
+ /**
65
+ * Host address resolver (injectable for tests).
66
+ */
67
+ resolveHostAddresses;
68
+ /**
69
+ * Creates one media URL fetcher.
70
+ *
71
+ * @param fetchImpl - Optional fetch override for tests.
72
+ * @param resolveHostAddresses - Optional DNS resolver override for tests.
73
+ */
74
+ constructor(fetchImpl, resolveHostAddresses) {
75
+ this.fetchImpl = fetchImpl ?? fetch;
76
+ this.resolveHostAddresses =
77
+ resolveHostAddresses ??
78
+ (async (hostname) => {
79
+ const results = await dnsLookup(hostname, { all: true });
80
+ return results.map((result) => result.address);
81
+ });
82
+ }
83
+ /**
84
+ * Reports whether one input looks like an accepted http(s) URL.
85
+ *
86
+ * @param value - Raw path argument.
87
+ * @returns True for http/https URLs.
88
+ */
89
+ static isHttpUrl(value) {
90
+ return /^https?:\/\//iu.test(String(value ?? "").trim());
91
+ }
92
+ /**
93
+ * Downloads one http(s) URL into a temporary directory.
94
+ *
95
+ * @param url - Absolute http(s) URL.
96
+ * @returns Bounded fetch result with owned cleanup.
97
+ * @throws Error when the destination is blocked, too large, or the download fails.
98
+ */
99
+ async fetch(url) {
100
+ let currentUrl = String(url ?? "").trim();
101
+ if (!MediaUrlFetcher.isHttpUrl(currentUrl)) {
102
+ throw new Error("Sólo se aceptan URLs http(s) en path/paths.");
103
+ }
104
+ const timeoutSignal = AbortSignal.timeout(MediaUrlFetcher.TIMEOUT_MS);
105
+ let response = await this.requestValidated(currentUrl, timeoutSignal);
106
+ for (let redirectCount = 0; redirectCount < MediaUrlFetcher.MAX_REDIRECTS; redirectCount += 1) {
107
+ if (response.status < 301 || response.status > 308) {
108
+ break;
109
+ }
110
+ const location = response.headers.get("location");
111
+ if (!location) {
112
+ break;
113
+ }
114
+ currentUrl = new URL(location, currentUrl).toString();
115
+ if (!MediaUrlFetcher.isHttpUrl(currentUrl)) {
116
+ throw new Error("La redirección apunta fuera de http(s).");
117
+ }
118
+ response = await this.requestValidated(currentUrl, timeoutSignal);
119
+ }
120
+ if (!response.ok) {
121
+ throw new Error(`La URL respondió HTTP ${String(response.status)}.`);
122
+ }
123
+ const declaredLengthHeader = response.headers.get("content-length");
124
+ if (declaredLengthHeader !== null &&
125
+ Number(declaredLengthHeader) > MediaUrlFetcher.MAX_BYTES) {
126
+ throw new Error(`El archivo remoto excede el límite de 64 MiB.`);
127
+ }
128
+ const buffer = await response.arrayBuffer();
129
+ if (buffer.byteLength > MediaUrlFetcher.MAX_BYTES) {
130
+ throw new Error(`El archivo remoto excede el límite de 64 MiB.`);
131
+ }
132
+ const contentType = (response.headers.get("content-type") ?? "")
133
+ .split(";", 1)[0]
134
+ .trim()
135
+ .toLowerCase();
136
+ const temporaryDirectory = await mkdtemp(join(tmpdir(), "enrivision-url-"));
137
+ const localPath = join(temporaryDirectory, this.deriveFileName(currentUrl, contentType));
138
+ try {
139
+ await writeFile(localPath, Buffer.from(buffer));
140
+ }
141
+ catch (error) {
142
+ await rm(temporaryDirectory, { recursive: true, force: true });
143
+ throw new Error(`No se pudo guardar la media descargada: ${error instanceof Error ? error.message : String(error)}`);
144
+ }
145
+ return new MediaUrlFetchResultRecord(localPath, contentType, async () => {
146
+ await rm(temporaryDirectory, { recursive: true, force: true });
147
+ });
148
+ }
149
+ /**
150
+ * Performs one fetch request after validating the destination addresses.
151
+ *
152
+ * @param url - Candidate URL (already normalized).
153
+ * @param timeoutSignal - Timeout signal shared by every hop.
154
+ * @returns Fetch response.
155
+ * @throws Error when the hostname resolves to a blocked address or the request fails.
156
+ */
157
+ async requestValidated(url, timeoutSignal) {
158
+ await this.assertPublicDestination(url);
159
+ try {
160
+ return await this.fetchImpl(url, { signal: timeoutSignal, redirect: "manual" });
161
+ }
162
+ catch (error) {
163
+ throw new Error(`La descarga de media falló: ${error instanceof Error ? error.message : String(error)}`);
164
+ }
165
+ }
166
+ /**
167
+ * Validates that the URL hostname is not a private/loopback/link-local target.
168
+ *
169
+ * @param url - Candidate URL.
170
+ * @throws Error when a literal or resolved address is blocked.
171
+ */
172
+ async assertPublicDestination(url) {
173
+ const hostname = new URL(url).hostname.replace(/^\[|\]$/gu, "");
174
+ if (hostname === "localhost") {
175
+ throw new Error("Destino bloqueado: no se permiten hosts locales ni redes privadas.");
176
+ }
177
+ if (isIP(hostname) !== 0) {
178
+ this.assertPublicAddress(hostname);
179
+ return;
180
+ }
181
+ const addresses = await this.resolveHostAddresses(hostname);
182
+ if (addresses.length === 0) {
183
+ throw new Error("No se pudo resolver el host antes de descargar la media.");
184
+ }
185
+ for (const address of addresses) {
186
+ this.assertPublicAddress(address);
187
+ }
188
+ }
189
+ /**
190
+ * Rejects private, loopback, link-local, and unspecified addresses.
191
+ *
192
+ * @param address - Literal IP address.
193
+ * @throws Error when the address belongs to a blocked range.
194
+ */
195
+ assertPublicAddress(address) {
196
+ const version = isIP(address);
197
+ if (version === 4) {
198
+ const octets = address.split(".").map((part) => Number(part));
199
+ const [first = 0, second = 0] = octets;
200
+ const blocked = first === 0 ||
201
+ first === 10 ||
202
+ first === 127 ||
203
+ (first === 169 && second === 254) ||
204
+ (first === 172 && second >= 16 && second <= 31) ||
205
+ (first === 192 && second === 168) ||
206
+ first >= 224;
207
+ if (blocked) {
208
+ throw new Error("Destino bloqueado: no se permiten hosts locales ni redes privadas.");
209
+ }
210
+ return;
211
+ }
212
+ if (version === 6) {
213
+ const normalized = address.toLowerCase();
214
+ const blocked = normalized === "::" ||
215
+ normalized === "::1" ||
216
+ normalized.startsWith("fc") ||
217
+ normalized.startsWith("fd") ||
218
+ normalized.startsWith("fe8") ||
219
+ normalized.startsWith("fe9") ||
220
+ normalized.startsWith("fea") ||
221
+ normalized.startsWith("feb");
222
+ const mappedV4 = /^::ffff:(\d+\.\d+\.\d+\.\d+)$/u.exec(normalized)?.[1] ?? null;
223
+ if (mappedV4 !== null) {
224
+ this.assertPublicAddress(mappedV4);
225
+ return;
226
+ }
227
+ if (blocked) {
228
+ throw new Error("Destino bloqueado: no se permiten hosts locales ni redes privadas.");
229
+ }
230
+ }
231
+ }
232
+ /**
233
+ * Derives one safe file name for the downloaded media.
234
+ *
235
+ * @param url - Source URL.
236
+ * @param contentType - Reported content type.
237
+ * @returns Bounded file name with a recognized extension.
238
+ */
239
+ deriveFileName(url, contentType) {
240
+ const withoutQuery = url.split("?")[0] ?? url;
241
+ const baseName = withoutQuery.split("/").pop() ?? "media";
242
+ const sanitized = baseName.replace(/[^A-Za-z0-9._-]/gu, "").slice(0, 80);
243
+ if (sanitized.length > 0 && /\.[A-Za-z0-9]{2,5}$/u.test(sanitized)) {
244
+ return sanitized;
245
+ }
246
+ const extension = this.extensionForContentType(contentType);
247
+ return `${sanitized.length > 0 ? sanitized : "media"}${extension}`;
248
+ }
249
+ /**
250
+ * Resolves one default extension for a content type.
251
+ *
252
+ * @param contentType - Reported content type.
253
+ * @returns Extension with leading dot.
254
+ */
255
+ extensionForContentType(contentType) {
256
+ if (contentType.startsWith("image/"))
257
+ return ".png";
258
+ if (contentType.startsWith("video/"))
259
+ return ".mp4";
260
+ if (contentType.startsWith("audio/"))
261
+ return ".mp3";
262
+ if (contentType.includes("pdf"))
263
+ return ".pdf";
264
+ if (contentType.includes("tar"))
265
+ return ".tar";
266
+ return ".bin";
267
+ }
268
+ }
269
+ //# sourceMappingURL=mediaUrlFetcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mediaUrlFetcher.js","sourceRoot":"","sources":["../../src/shared/mediaUrlFetcher.ts"],"names":[],"mappings":"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"}
@@ -2,26 +2,29 @@
2
2
  * ANALYZE MEDIA TOOL
3
3
  *
4
4
  * Implements the `analyze_media` MCP tool:
5
- * - Validates local file path
6
- * - Streams the file to EnriProxy via resumable uploads
5
+ * - Validates local file paths or http(s) URLs
6
+ * - Materializes URL inputs via bounded downloads (temporary, owned cleanup)
7
+ * - Streams the bytes to EnriProxy via resumable uploads
7
8
  * - Triggers server-side analysis and returns text-only results
8
9
  *
9
10
  * @module tools/AnalyzeMediaTool
10
11
  */
11
12
  import { type EnriProxyClient } from "../client/EnriProxyClient.js";
13
+ import { MediaUrlFetcher } from "../shared/mediaUrlFetcher.js";
12
14
  /**
13
15
  * Tool parameters for `analyze_media`.
14
16
  */
15
17
  export interface AnalyzeMediaToolParams {
16
18
  /**
17
- * Absolute local filesystem path on the MCP host.
19
+ * Absolute local filesystem path on the MCP host, or one http(s) URL to
20
+ * download (bounded, temporary) and analyze.
18
21
  *
19
22
  * @remarks
20
23
  * Use `paths` to analyze multiple images in a single call.
21
24
  */
22
25
  readonly path?: string;
23
26
  /**
24
- * Absolute local filesystem paths on the MCP host.
27
+ * Absolute local filesystem paths or http(s) URLs on the MCP host.
25
28
  *
26
29
  * @remarks
27
30
  * When provided, EnriVision uploads the files as a single media-set archive
@@ -198,19 +201,24 @@ export interface AnalyzeMediaToolDeps {
198
201
  readonly defaultTimeoutMs: number;
199
202
  }
200
203
  /**
201
- * MCP tool that uploads and analyzes local media.
204
+ * MCP tool that uploads and analyzes local or http(s) URL media.
202
205
  */
203
206
  export declare class AnalyzeMediaTool {
204
207
  /**
205
208
  * Tool dependencies.
206
209
  */
207
210
  private readonly deps;
211
+ /**
212
+ * Bounded http(s) media fetcher used when `path`/`paths` carry URLs.
213
+ */
214
+ private readonly urlFetcher;
208
215
  /**
209
216
  * Creates a new {@link AnalyzeMediaTool}.
210
217
  *
211
218
  * @param deps - Tool dependencies
219
+ * @param urlFetcher - Optional URL fetcher override for tests
212
220
  */
213
- constructor(deps: AnalyzeMediaToolDeps);
221
+ constructor(deps: AnalyzeMediaToolDeps, urlFetcher?: MediaUrlFetcher);
214
222
  /**
215
223
  * Validates raw MCP tool arguments.
216
224
  *
@@ -225,6 +233,19 @@ export declare class AnalyzeMediaTool {
225
233
  * @returns Tool result
226
234
  */
227
235
  execute(params: AnalyzeMediaToolParams): Promise<AnalyzeMediaToolResult>;
236
+ /**
237
+ * Resolves the upload content type for one media input.
238
+ *
239
+ * @remarks
240
+ * URL downloads keep their extension-derived type when the extension is
241
+ * recognized; otherwise the server-reported content type wins over the
242
+ * generic `application/octet-stream` fallback.
243
+ *
244
+ * @param localPath - Local file path (already materialized for URLs)
245
+ * @param urlContentType - Server-reported content type for downloaded URLs
246
+ * @returns Effective MIME type
247
+ */
248
+ private resolveEffectiveContentType;
228
249
  /**
229
250
  * Removes internal identifiers (upload ids, routing-only values, etc.) from the
230
251
  * extraction payload before returning it to the model.
@@ -270,7 +291,7 @@ export declare class AnalyzeMediaTool {
270
291
  * screenshot sets.
271
292
  *
272
293
  * @param client - EnriProxy client
273
- * @param filePaths - Absolute image file paths
294
+ * @param inputs - Resolved media inputs (local paths, URLs already materialized)
274
295
  * @param timeoutMs - Request timeout per HTTP request
275
296
  * @param clientTraceId - Client trace id for correlation
276
297
  * @returns Upload id for the created tar session
@@ -1 +1 @@
1
- {"version":3,"file":"AnalyzeMediaTool.d.ts","sourceRoot":"","sources":["../../src/tools/AnalyzeMediaTool.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AnalyzeMediaTool.d.ts","sourceRoot":"","sources":["../../src/tools/AnalyzeMediaTool.ts"],"names":[],"mappings":"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"}