@bedolla/enrivision 0.1.4 → 0.1.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. package/README.md +31 -6
  2. package/dist/client/EnriProxyClient.d.ts +289 -244
  3. package/dist/client/EnriProxyClient.d.ts.map +1 -1
  4. package/dist/client/EnriProxyClient.js +841 -115
  5. package/dist/client/EnriProxyClient.js.map +1 -1
  6. package/dist/client/EnriProxyClientContract.d.ts +425 -0
  7. package/dist/client/EnriProxyClientContract.d.ts.map +1 -0
  8. package/dist/client/EnriProxyClientContract.js +87 -0
  9. package/dist/client/EnriProxyClientContract.js.map +1 -0
  10. package/dist/index.js +23 -12
  11. package/dist/index.js.map +1 -1
  12. package/dist/package-info.d.ts +28 -0
  13. package/dist/package-info.d.ts.map +1 -1
  14. package/dist/package-info.js +28 -0
  15. package/dist/package-info.js.map +1 -1
  16. package/dist/server/EnriVisionServer.d.ts +186 -0
  17. package/dist/server/EnriVisionServer.d.ts.map +1 -1
  18. package/dist/server/EnriVisionServer.js +780 -93
  19. package/dist/server/EnriVisionServer.js.map +1 -1
  20. package/dist/shared/codepointTruncation.d.ts +61 -0
  21. package/dist/shared/codepointTruncation.d.ts.map +1 -0
  22. package/dist/shared/codepointTruncation.js +73 -0
  23. package/dist/shared/codepointTruncation.js.map +1 -0
  24. package/dist/shared/mediaUrlFetcher.d.ts +247 -9
  25. package/dist/shared/mediaUrlFetcher.d.ts.map +1 -1
  26. package/dist/shared/mediaUrlFetcher.js +712 -53
  27. package/dist/shared/mediaUrlFetcher.js.map +1 -1
  28. package/dist/shared/tar.d.ts +82 -2
  29. package/dist/shared/tar.d.ts.map +1 -1
  30. package/dist/shared/tar.js +106 -43
  31. package/dist/shared/tar.js.map +1 -1
  32. package/dist/shared/validation.d.ts +96 -2
  33. package/dist/shared/validation.d.ts.map +1 -1
  34. package/dist/shared/validation.js +169 -10
  35. package/dist/shared/validation.js.map +1 -1
  36. package/dist/tools/AnalyzeMediaContract.d.ts +457 -0
  37. package/dist/tools/AnalyzeMediaContract.d.ts.map +1 -0
  38. package/dist/tools/AnalyzeMediaContract.js +161 -0
  39. package/dist/tools/AnalyzeMediaContract.js.map +1 -0
  40. package/dist/tools/AnalyzeMediaExtractionSanitizer.d.ts +35 -0
  41. package/dist/tools/AnalyzeMediaExtractionSanitizer.d.ts.map +1 -0
  42. package/dist/tools/AnalyzeMediaExtractionSanitizer.js +214 -0
  43. package/dist/tools/AnalyzeMediaExtractionSanitizer.js.map +1 -0
  44. package/dist/tools/AnalyzeMediaInputResolver.d.ts +250 -0
  45. package/dist/tools/AnalyzeMediaInputResolver.d.ts.map +1 -0
  46. package/dist/tools/AnalyzeMediaInputResolver.js +430 -0
  47. package/dist/tools/AnalyzeMediaInputResolver.js.map +1 -0
  48. package/dist/tools/AnalyzeMediaParamParser.d.ts +299 -0
  49. package/dist/tools/AnalyzeMediaParamParser.d.ts.map +1 -0
  50. package/dist/tools/AnalyzeMediaParamParser.js +824 -0
  51. package/dist/tools/AnalyzeMediaParamParser.js.map +1 -0
  52. package/dist/tools/AnalyzeMediaResumableUploader.d.ts +244 -0
  53. package/dist/tools/AnalyzeMediaResumableUploader.d.ts.map +1 -0
  54. package/dist/tools/AnalyzeMediaResumableUploader.js +549 -0
  55. package/dist/tools/AnalyzeMediaResumableUploader.js.map +1 -0
  56. package/dist/tools/AnalyzeMediaTarPackager.d.ts +42 -0
  57. package/dist/tools/AnalyzeMediaTarPackager.d.ts.map +1 -0
  58. package/dist/tools/AnalyzeMediaTarPackager.js +245 -0
  59. package/dist/tools/AnalyzeMediaTarPackager.js.map +1 -0
  60. package/dist/tools/AnalyzeMediaTool.d.ts +155 -294
  61. package/dist/tools/AnalyzeMediaTool.d.ts.map +1 -1
  62. package/dist/tools/AnalyzeMediaTool.js +600 -457
  63. package/dist/tools/AnalyzeMediaTool.js.map +1 -1
  64. package/package.json +2 -1
package/README.md CHANGED
@@ -11,7 +11,7 @@ This is useful for media types that many MCP clients cannot read reliably (video
11
11
 
12
12
  ## Requirements
13
13
 
14
- - Node.js `>= 22` (recommended: Node 24 LTS)
14
+ - Node.js `>= 24`
15
15
  - A reachable EnriProxy server with these endpoints enabled:
16
16
  - `POST /v1/uploads`
17
17
  - `HEAD /v1/uploads/:id`
@@ -85,7 +85,7 @@ Example: no install (always uses whatever npm currently tags as `latest`)
85
85
  "EnriVision": {
86
86
  "type": "stdio",
87
87
  "command": "node",
88
- "args": ["C:\\\\Users\\\\Administrator\\\\Projects\\\\EnriVision\\\\dist\\\\index.js"],
88
+ "args": ["C:\\Users\\Administrator\\Projects\\EnriVision\\dist\\index.js"],
89
89
  "env": {
90
90
  "ENRIPROXY_URL": "http://127.0.0.1:8787",
91
91
  "ENRIPROXY_API_KEY": "YOUR_ENRIPROXY_API_KEY",
@@ -104,9 +104,34 @@ EnriVision is configured via environment variables:
104
104
  - `ENRIPROXY_URL` (`string`, optional, default: `http://127.0.0.1:8787`)
105
105
  - `ENRIPROXY_API_KEY` (`string`, required)
106
106
  - `ENRIVISION_TIMEOUT_MS` (`string`, optional, default: `1800000`)
107
- - Parsed as an integer (milliseconds). Uploads are performed in chunks; this timeout applies per request.
107
+ - Parsed as an integer (milliseconds). This is the operator cap: the per-call analyze timeout is `min(operator, mode budget)` with `single` = 10 min (one pass, fast/cheap), `multipass`/`auto` = 20 min (per-segment/batch map + reduce; `auto` may escalate to multipass server-side). Uploads are performed in chunks; per-chunk timeouts honor `min(operator, derived 30s..300s)` floored at 30 s (an operator budget below 30 s never forces tighter single-chunk budgets).
108
108
  - `ENRIVISION_DEFAULT_LANGUAGE` (`string`, optional)
109
109
  - Default language to send when the tool call does not provide `language`.
110
+ - `ENRIVISION_DENY_SYMLINKS` (`string`, optional)
111
+ - Set to `1` to reject symlinked `path`/`paths` inputs. Strict mode opens with `O_NOFOLLOW` (POSIX) and compares the `dev:ino` handle identity from `fstat`. On Windows (`win32`) `O_NOFOLLOW` is `0` (advisory only), so strict mode there rests solely on the `lstat`-vs-`fstat` comparison with a small swap window: prefer POSIX hosts when symlink races are in scope.
112
+ - `ENRIVISION_MODEL` (`string`, optional)
113
+ - Model id for server-side dispatch affinity; omit for auto-dispatch.
114
+ - `ENRIVISION_QUIET` (`string`, optional)
115
+ - Set to `1` to silence upload/retry progress lines on stderr.
116
+
117
+ ## Analysis budgets
118
+
119
+ The client analyze timeout is `min(ENRIVISION_TIMEOUT_MS, mode budget)`:
120
+
121
+ - `single` → 10 min (mirrors EnriCode and the EnriProxy single-pass stage budget).
122
+ - `multipass` → 20 min (mirrors EnriCode and the server multipass wall-clock budget).
123
+ - `auto` (default) → 20 min: the server picks the mode and may escalate to multipass, so the client cannot assume the short budget. If unsure, omit tuning (`auto`).
124
+
125
+ ## Error shape
126
+
127
+ Tool failures return MCP `isError` with Spanish-first bilingual text (ES first, EN second) plus machine-readable `structuredContent: { code, retryable, httpStatus? }` reusing the EnriCode vocabulary:
128
+
129
+ - `ENRICODE_ERR_TOOL_INPUT_INVALID` — argument/tuning errors (including proxy 400/422). Never retry unchanged (`retryable: false`).
130
+ - `ENRICODE_ERR_TOOL_EXECUTION_FAILED` — server/transport failures. `retryable` is true for 408/429/5xx, false otherwise.
131
+ - `ENRICODE_ERR_TOOL_EXECUTION_TIMEOUT` — expired upload/analyze budgets (`retryable: true`; retry with a smaller scope).
132
+ - `ENRICODE_ERR_TOOL_EXECUTION_ABORTED` — caller-cancelled (`retryable: false`).
133
+
134
+ `httpStatus` is present only when the failure carries a proxy HTTP status.
110
135
 
111
136
  ## MCP tools
112
137
 
@@ -120,7 +145,7 @@ EnriVision exposes this MCP tool:
120
145
  General notes:
121
146
 
122
147
  - The tool accepts a single JSON object as its input (the MCP `arguments`).
123
- - Exactly one of `path` or `paths` is required.
148
+ - At least one of `path` or `paths` is required. When `paths` carries at least one valid entry, `path` is ignored (explicit ignore-path contract: sending both is allowed, `path` is silently ignored — prefer oneOf semantics and send only one).
124
149
  - Paths must be absolute on the machine running the MCP server, or http(s) URLs. URLs are downloaded to a temporary directory on the MCP host (up to 64 MiB each; localhost and private-network destinations are blocked) and deleted after analysis.
125
150
  - EnriVision does not accept per-call `server_url`/`api_key` overrides (these are configured via env vars).
126
151
 
@@ -135,7 +160,7 @@ Inputs:
135
160
  - `language` (`string`, optional): preferred response language (ISO 639-1; e.g., `es`, `en`). If omitted, uses `ENRIVISION_DEFAULT_LANGUAGE` when set.
136
161
  - `analysis_mode` (`string`, optional): `auto` | `single` | `multipass`.
137
162
  - `max_frames` (`number`, optional): single-pass video frames (`1..20`).
138
- - `transcribe` (`boolean`, optional): enable/disable transcription (videos).
163
+ - `transcribe` (`boolean`, optional): enable/disable transcription (videos). Has no effect on images/documents (declared in `warnings`, ignored).
139
164
  - `transcription_language` (`string`, optional): whisper hint (`auto`, `es`, `en`, ...).
140
165
 
141
166
  Video targeting:
@@ -169,7 +194,7 @@ Example `arguments` object:
169
194
 
170
195
  ```jsonc
171
196
  {
172
- "path": "C:\\\\path\\\\to\\\\video.mp4",
197
+ "path": "C:\\path\\to\\video.mp4",
173
198
  "question": "What are the key steps demonstrated?",
174
199
  "analysis_mode": "auto",
175
200
  "transcribe": true,
@@ -9,95 +9,35 @@
9
9
  *
10
10
  * @module client/EnriProxyClient
11
11
  */
12
+ import { type AnalyzeVisionParams, type AnalyzeVisionResponse, type AppendUploadChunkParams, type CreateUploadSessionParams, type CreateUploadSessionResponse, type EnriProxyClientConfig, type FetchSegmentPageParams, type SegmentPageResponse } from "./EnriProxyClientContract.js";
13
+ export { EnriProxyHttpError } from "./EnriProxyClientContract.js";
14
+ export type { AnalyzeVisionElement, AnalyzeVisionParams, AnalyzeVisionResponse, AppendUploadChunkParams, CreateUploadSessionParams, CreateUploadSessionResponse, FetchSegmentPageParams, EnriProxyAudioTuning, EnriProxyClientConfig, EnriProxyDocumentTuning, EnriProxyHttpResult, EnriProxyImageRegion, EnriProxyImagesTuning, EnriProxyVideoTuning, SegmentPageResponse, } from "./EnriProxyClientContract.js";
12
15
  /**
13
- * Connection configuration for {@link EnriProxyClient}.
16
+ * Timeout for upload session creation (`POST /v1/uploads`, 60 s).
17
+ *
18
+ * @remarks
19
+ * Mirrors EnriCode `VisionAnalyzeMediaUploadCoordinator` (60 s create
20
+ * budget): session creation is a tiny metadata call, but it deserves more
21
+ * headroom than offset probes. Capped by the operator timeout via `Math.min`.
14
22
  */
15
- export interface EnriProxyClientConfig {
16
- /**
17
- * EnriProxy base URL (e.g., https://proxy.example.com).
18
- */
19
- readonly baseUrl: string;
20
- /**
21
- * EnriProxy API key (sent as Authorization: Bearer ...).
22
- */
23
- readonly apiKey: string;
24
- /**
25
- * Default request timeout in milliseconds.
26
- */
27
- readonly timeoutMs: number;
28
- }
23
+ export declare const CREATE_CONTROL_TIMEOUT_MS: number;
29
24
  /**
30
- * Response from POST `/v1/uploads`.
31
- */
32
- export interface CreateUploadSessionResponse {
33
- /**
34
- * Upload session identifier.
35
- */
36
- readonly upload_id: string;
37
- /**
38
- * Recommended chunk size in bytes.
39
- */
40
- readonly chunk_size_bytes: number;
41
- /**
42
- * Expiration timestamp in ms since epoch.
43
- */
44
- readonly expires_at: number;
45
- }
46
- /**
47
- * Response from POST `/v1/vision/analyze`.
25
+ * Timeout for upload offset probes (`HEAD /v1/uploads/:id`, 15 s).
26
+ *
27
+ * @remarks
28
+ * Mirrors EnriCode (15 s probe budget): offset queries are single-header
29
+ * reads and must fail fast. Capped by the operator timeout via `Math.min`.
48
30
  */
49
- export interface AnalyzeVisionResponse {
50
- /**
51
- * Text analysis.
52
- */
53
- readonly analysis: string;
54
- /**
55
- * Grounded element boxes for image analyses (original-relative [0,1]).
56
- */
57
- readonly elements?: ReadonlyArray<{
58
- readonly label: string;
59
- readonly box: {
60
- readonly x: number;
61
- readonly y: number;
62
- readonly width: number;
63
- readonly height: number;
64
- };
65
- }>;
66
- /**
67
- * Detected media type.
68
- */
69
- readonly media_type: string;
70
- /**
71
- * Extraction metadata.
72
- */
73
- readonly extraction: Record<string, unknown>;
74
- }
31
+ export declare const PROBE_CONTROL_TIMEOUT_MS: number;
75
32
  /**
76
- * Error thrown when EnriProxy returns a non-2xx HTTP response.
33
+ * Budget for the fail-open vision-capability probe (`GET /v1/account/models`, 15 s).
34
+ *
35
+ * @remarks
36
+ * Mirrors EnriCode `assertRemoteVisionCapable` and
37
+ * `ANALYZE_MEDIA_LIMITS.visionProbeTimeoutMs` (pinned equal by tests): the
38
+ * probe must never stall session creation.
77
39
  */
78
- export declare class EnriProxyHttpError extends Error {
79
- /**
80
- * HTTP status code returned by the server.
81
- */
82
- readonly status: number;
83
- /**
84
- * Response headers returned by the server.
85
- */
86
- readonly headers: Record<string, string | string[] | undefined>;
87
- /**
88
- * Response body returned by the server (best-effort UTF-8).
89
- */
90
- readonly body: string;
91
- /**
92
- * Creates a new {@link EnriProxyHttpError}.
93
- *
94
- * @param message - Error message
95
- * @param status - HTTP status code
96
- * @param headers - Response headers
97
- * @param body - Response body
98
- */
99
- constructor(message: string, status: number, headers: Record<string, string | string[] | undefined>, body: string);
100
- }
40
+ export declare const ACCOUNT_MODELS_PROBE_TIMEOUT_MS: number;
101
41
  /**
102
42
  * Minimal client for EnriProxy HTTP endpoints.
103
43
  */
@@ -125,192 +65,295 @@ export declare class EnriProxyClient {
125
65
  *
126
66
  * @param params - Session parameters
127
67
  * @returns Session response
68
+ * @throws Error with the parsed server detail when creation fails, or when the success body is not valid JSON.
128
69
  */
129
- createUploadSession(params: {
130
- /**
131
- * Original filename.
132
- */
133
- readonly filename: string;
134
- /**
135
- * Total file size in bytes.
136
- */
137
- readonly sizeBytes: number;
138
- /**
139
- * MIME type.
140
- */
141
- readonly contentType: string;
142
- /**
143
- * Optional client trace id.
144
- */
145
- readonly clientTraceId?: string;
146
- }): Promise<CreateUploadSessionResponse>;
70
+ createUploadSession(params: CreateUploadSessionParams): Promise<CreateUploadSessionResponse>;
147
71
  /**
148
72
  * Queries the current upload offset for a session.
149
73
  *
150
74
  * @param uploadId - Upload id
75
+ * @param signal - Optional cancellation signal.
151
76
  * @returns Offset in bytes
77
+ * @throws Error with the parsed server detail when the query fails.
152
78
  */
153
- getUploadOffset(uploadId: string): Promise<number>;
79
+ getUploadOffset(uploadId: string, signal?: AbortSignal): Promise<number>;
80
+ /**
81
+ * Deletes an upload session and its stored bytes (best-effort orphan cleanup).
82
+ *
83
+ * @remarks
84
+ * Mirrors `DELETE /v1/uploads/:id` on EnriProxy. Callers must invoke this
85
+ * with an independent timeout signal (never the already-cancelled caller
86
+ * signal) and swallow failures: cleanup must never mask the original
87
+ * upload/analysis error.
88
+ *
89
+ * @param uploadId - Upload id to delete.
90
+ * @param signal - Optional cancellation signal (prefer an independent timeout).
91
+ * @throws Error with an Spanish-first bilingual message when the server rejects the deletion.
92
+ */
93
+ deleteUploadSession(uploadId: string, signal?: AbortSignal): Promise<void>;
94
+ /**
95
+ * Fetches the account model catalog for the fail-open vision probe.
96
+ *
97
+ * @remarks
98
+ * Thin `GET /v1/account/models` reader for `AnalyzeMediaTool` (mirrors
99
+ * EnriCode `assertRemoteVisionCapable`): callers treat every failure as
100
+ * fail-open (proceed with the upload) and only reject on an explicit
101
+ * `vision === false` match, so a stale discovery snapshot never blocks a
102
+ * valid upload. Non-2xx responses throw {@link EnriProxyHttpError} (the
103
+ * caller uses the status to invalidate cached verdicts on 401/404);
104
+ * non-JSON 2xx bodies throw an Spanish-first bilingual error.
105
+ *
106
+ * @param signal - Optional cancellation signal.
107
+ * @returns Parsed response body.
108
+ * @throws Error with an Spanish-first bilingual message when the probe request fails.
109
+ */
110
+ getAccountModels(signal?: AbortSignal): Promise<unknown>;
154
111
  /**
155
112
  * Appends a chunk to an upload session.
156
113
  *
157
114
  * @param params - Chunk parameters
158
115
  * @returns New upload offset in bytes
159
116
  */
160
- appendUploadChunk(params: {
161
- /**
162
- * Upload session id.
163
- */
164
- readonly uploadId: string;
165
- /**
166
- * Expected offset in bytes.
167
- */
168
- readonly offset: number;
169
- /**
170
- * Chunk bytes.
171
- */
172
- readonly chunk: Buffer;
173
- /**
174
- * Optional timeout override in milliseconds.
175
- */
176
- readonly timeoutMs?: number;
177
- }): Promise<number>;
117
+ appendUploadChunk(params: AppendUploadChunkParams): Promise<number>;
178
118
  /**
179
119
  * Triggers server-side vision analysis for an uploaded file.
180
120
  *
181
121
  * @param params - Analysis parameters
182
122
  * @returns Analysis response
123
+ * @throws Error with the parsed server detail when analysis fails, or when the success body is not valid JSON.
124
+ */
125
+ analyze(params: AnalyzeVisionParams): Promise<AnalyzeVisionResponse>;
126
+ /**
127
+ * Reads one continuation window over a truncated media list.
128
+ *
129
+ * @param params - Cursor plus optional offset/limit.
130
+ * @returns Page entries with totals and continuation state.
131
+ * @throws Error with a Spanish-first bilingual message on invalid
132
+ * cursors, expired cursors, or malformed responses.
133
+ */
134
+ fetchSegmentPage(params: FetchSegmentPageParams): Promise<SegmentPageResponse>;
135
+ /**
136
+ * Validates one continuation page body.
137
+ *
138
+ * @param raw - Parsed response body.
139
+ * @returns Validated page.
140
+ * @throws Error with a Spanish-first bilingual message when the body
141
+ * carries no usable page.
142
+ */
143
+ private static requireValidSegmentPage;
144
+ /**
145
+ * Rejects oversized prompt text before any byte is uploaded.
146
+ *
147
+ * @remarks
148
+ * Mirrors the parser gate (2000 chars): the server 400s after upload cost.
149
+ *
150
+ * @param value - Optional prompt text.
151
+ * @param fieldName - `question` or `context`.
152
+ * @throws Error in Spanish naming the 2000-character cap.
153
+ */
154
+ private static requireBoundedPromptText;
155
+ /**
156
+ * Rejects mistyped tuning before any upload cost on the direct-client path.
157
+ *
158
+ * @remarks
159
+ * Mirrors `AnalyzeMediaParamParser` (enum, language pattern, section
160
+ * key closure): direct callers bypass the tool parser, and a typo would
161
+ * otherwise analyze the whole file at full cost. Exposed for reuse and
162
+ * unit-pinned against the parser key sets.
163
+ *
164
+ * @param params - Analysis parameters about to travel.
165
+ * @throws Error with a Spanish-first bilingual message on the first defect.
166
+ */
167
+ static requirePreUploadTuning(params: AnalyzeVisionParams): void;
168
+ /**
169
+ * Validates one language hint against the parser pattern.
170
+ *
171
+ * @param value - Candidate hint.
172
+ * @param fieldName - Dotted field name for error messages.
173
+ * @returns Nothing.
174
+ * @throws Error with a Spanish-first bilingual message on mismatch.
175
+ */
176
+ private static requireLanguageHint;
177
+ /**
178
+ * Rejects unknown keys inside one tuning section.
179
+ *
180
+ * @param section - Candidate section object.
181
+ * @param knownKeys - Parser-owned accepted spellings.
182
+ * @param sectionName - Section name for error messages.
183
+ * @returns Nothing.
184
+ * @throws Error with a Spanish-first bilingual message on unknown keys.
185
+ */
186
+ private static requireKnownSectionKeys;
187
+ /**
188
+ * Rejects per-batch sizes exceeding their totals on the direct-client path.
189
+ *
190
+ * @remarks
191
+ * The tool parser already guards this; direct callers bypass it, and the
192
+ * server would truncate after paid multipass work.
193
+ *
194
+ * @param perBatch - Per-batch value, or undefined when absent.
195
+ * @param total - Total value, or undefined when absent.
196
+ * @param family - `document` or `images`.
197
+ * @throws Error with an Spanish-first bilingual message when the batch exceeds the total.
198
+ */
199
+ private static throwOnBatchExceedingTotal;
200
+ /**
201
+ * Coerces an optional integer tuning knob, failing loudly on garbage.
202
+ *
203
+ * @remarks
204
+ * Parity with `AnalyzeMediaParamParser` (`optionalInt` accepts `"60"`):
205
+ * integers and complete integer strings travel floored; any other present
206
+ * value throws bilingually instead of being silently dropped (a dropped
207
+ * knob would analyze the whole file at full cost with default tuning).
208
+ *
209
+ * @param raw - Raw knob value.
210
+ * @param fieldName - Dotted field name for error messages.
211
+ * @param min - Inclusive minimum (shares the parser range table so direct
212
+ * callers fail pre-upload like parser-gated calls).
213
+ * @param max - Inclusive maximum (shares the parser range table).
214
+ * @returns Floored integer, or undefined when absent.
215
+ * @throws Error with an Spanish-first bilingual message when present but not an integer within range.
216
+ */
217
+ private static requireOptionalInt;
218
+ /**
219
+ * Coerces an optional float tuning knob, failing loudly on garbage.
220
+ *
221
+ * @remarks
222
+ * Parity with `AnalyzeMediaParamParser` (`optionalNumber` accepts
223
+ * `"12.5"`): numbers and complete numeric strings travel; any other
224
+ * present value throws bilingually instead of being silently dropped.
225
+ *
226
+ * @param raw - Raw knob value.
227
+ * @param fieldName - Dotted field name for error messages.
228
+ * @param min - Inclusive minimum (shares the parser range table).
229
+ * @param max - Inclusive maximum (shares the parser range table).
230
+ * @returns Finite number, or undefined when absent.
231
+ * @throws Error with an Spanish-first bilingual message when present but not a number within range.
232
+ */
233
+ private static requireOptionalNumber;
234
+ /**
235
+ * Coerces an optional boolean tuning knob, failing loudly on garbage.
236
+ *
237
+ * @remarks
238
+ * Parity with `AnalyzeMediaParamParser` (`assertOptionalBoolean` accepts
239
+ * `"true"`/`"false"`): booleans and true/false strings travel; any
240
+ * other present value throws bilingually instead of being silently dropped.
241
+ *
242
+ * @param raw - Raw knob value.
243
+ * @param fieldName - Dotted field name for error messages.
244
+ * @returns Boolean, or undefined when absent.
245
+ * @throws Error with an Spanish-first bilingual message when present but not a boolean.
246
+ */
247
+ private static requireOptionalBoolean;
248
+ /**
249
+ * Resolves the `transcribe` knob, defaulting to true like EnriCode.
250
+ *
251
+ * @remarks
252
+ * A `"false"` string must never collapse to the `true` default (that
253
+ * would pay a full transcription the caller explicitly disabled):
254
+ * true/false strings coerce, anything else present throws in Spanish.
255
+ *
256
+ * @param raw - Raw transcribe value.
257
+ * @returns Resolved transcribe flag.
258
+ * @throws Error with an Spanish-first bilingual message when present but not a boolean nor a true/false string.
183
259
  */
184
- analyze(params: {
185
- /**
186
- * Upload session id.
187
- */
188
- readonly uploadId: string;
189
- /**
190
- * Optional analysis context hint.
191
- */
192
- readonly context?: string;
193
- /**
194
- * Optional explicit question.
195
- */
196
- readonly question?: string;
197
- /**
198
- * Preferred response language.
199
- */
200
- readonly language?: string;
201
- /**
202
- * Optional max frames override for videos.
203
- */
204
- readonly maxFrames?: number;
205
- /**
206
- * Optional override for transcription on videos.
207
- */
208
- readonly transcribe?: boolean;
209
- /**
210
- * Optional transcription language hint.
211
- */
212
- readonly transcriptionLanguage?: string;
213
- /**
214
- * Optional analysis mode selector.
215
- */
216
- readonly analysisMode?: "auto" | "single" | "multipass";
217
- /**
218
- * Optional relative image region for native-resolution zoom (images only).
219
- */
220
- readonly region?: {
221
- readonly x: number;
222
- readonly y: number;
223
- readonly width: number;
224
- readonly height: number;
225
- };
226
- /**
227
- * Optional video multipass tuning.
228
- */
229
- readonly video?: {
230
- /**
231
- * Clip start offset in seconds for targeted analysis.
232
- */
233
- readonly clipStartSeconds?: number;
234
- /**
235
- * Clip duration in seconds for targeted analysis.
236
- */
237
- readonly clipDurationSeconds?: number;
238
- /**
239
- * Segment duration in seconds.
240
- */
241
- readonly segmentSeconds?: number;
242
- /**
243
- * Maximum number of segments to analyze.
244
- */
245
- readonly maxSegments?: number;
246
- /**
247
- * Maximum frames per segment.
248
- */
249
- readonly maxFramesPerSegment?: number;
250
- };
251
- /**
252
- * Optional document multipass tuning (PDF).
253
- */
254
- readonly document?: {
255
- /**
256
- * Maximum pages to analyze in total.
257
- */
258
- readonly maxPagesTotal?: number;
259
- /**
260
- * Pages per batch.
261
- */
262
- readonly pagesPerBatch?: number;
263
- /**
264
- * Maximum rendered pages per batch.
265
- */
266
- readonly maxImagesPerBatch?: number;
267
- /**
268
- * Minimum extracted text length to treat a page as textual.
269
- */
270
- readonly scannedTextThresholdChars?: number;
271
- };
272
- /**
273
- * Optional audio multipass tuning.
274
- */
275
- readonly audio?: {
276
- /**
277
- * Whether to include timestamped segments in the extracted transcript.
278
- */
279
- readonly timestamps?: boolean;
280
- /**
281
- * Segment duration in seconds for audio multipass.
282
- */
283
- readonly segmentSeconds?: number;
284
- /**
285
- * Maximum number of audio segments to analyze.
286
- */
287
- readonly maxSegments?: number;
288
- };
289
- /**
290
- * Optional image-set multipass tuning.
291
- */
292
- readonly images?: {
293
- /**
294
- * Maximum number of images to analyze in total.
295
- */
296
- readonly maxImagesTotal?: number;
297
- /**
298
- * Images per batch for multipass map calls.
299
- */
300
- readonly imagesPerBatch?: number;
301
- /**
302
- * Maximum dimension for images (width/height).
303
- */
304
- readonly maxDimension?: number;
305
- };
306
- }): Promise<AnalyzeVisionResponse>;
260
+ private static requireTranscribe;
261
+ /**
262
+ * Validates a `POST /v1/vision/analyze` response body for third-party callers.
263
+ *
264
+ * @remarks
265
+ * A malformed 200 body (`{}`, `analysis: 123`, missing `media_type`)
266
+ * must surface as a Spanish coaching error, never as an English
267
+ * `TypeError` from downstream formatters (model-facing text is always
268
+ * Spanish). Malformed `elements` entries are sanitized (dropped), not
269
+ * fatal: partial grounding still analyzes.
270
+ *
271
+ * @param raw - Parsed response body.
272
+ * @returns Validated analysis response.
273
+ * @throws Error with an Spanish-first bilingual message when the body shape is invalid.
274
+ */
275
+ private static requireValidAnalyzeResponse;
276
+ /**
277
+ * Keeps only well-formed grounded element boxes.
278
+ *
279
+ * @remarks
280
+ * Malformed entries (non-string label, non-finite box coordinates) are
281
+ * dropped so one bad box never fails the whole analysis; downstream
282
+ * formatters can assume `{label, box: {x, y, width, height}}`.
283
+ *
284
+ * @param elements - Raw elements array from the server.
285
+ * @returns Sanitized element boxes.
286
+ */
287
+ private static sanitizeAnalyzeElements;
288
+ /**
289
+ * Builds an {@link EnriProxyHttpError} embedding the parsed server detail.
290
+ *
291
+ * @param baseMessage - Base message naming the failed operation with its HTTP status.
292
+ * @param result - Raw HTTP result carrying headers and body.
293
+ * @returns HTTP error preserving status, headers, and body.
294
+ */
295
+ private static buildHttpError;
296
+ /**
297
+ * Parses a 2xx JSON body with a bilingual guard for non-JSON payloads.
298
+ *
299
+ * @param body - Raw response body.
300
+ * @param status - HTTP status code (reported in the error).
301
+ * @returns Parsed body.
302
+ * @throws Error with an Spanish-first bilingual message when the body is not valid JSON.
303
+ */
304
+ private static parseJsonBody;
307
305
  /**
308
306
  * Builds an absolute URL relative to the configured base URL.
309
307
  *
310
- * @param pathname - Pathname to append
308
+ * @remarks
309
+ * The base subpath is preserved (`http://host/proxy` + `/v1/uploads` =
310
+ * `http://host/proxy/v1/uploads`): `new URL(path, base)` alone would
311
+ * discard it. A root base keeps working exactly as before.
312
+ *
313
+ * @param pathname - Pathname to append (must start with `/`).
311
314
  * @returns URL instance
315
+ * @throws Error with an Spanish-first bilingual message when the configured base URL is malformed.
312
316
  */
313
317
  private buildUrl;
318
+ /**
319
+ * Parses a strict `Upload-Offset` response header.
320
+ *
321
+ * @remarks
322
+ * Strict by design: only `^\d+$` (after trimming) is accepted, so
323
+ * `"12abc"` or `"1.5"` fail bilingually instead of being prefix-parsed
324
+ * (`parseInt("12abc") === 12`) and resuming at a wrong offset.
325
+ *
326
+ * @param headers - Response headers.
327
+ * @returns Offset in bytes.
328
+ * @throws Error with an Spanish-first bilingual message when the header is missing or malformed.
329
+ */
330
+ private parseUploadOffsetHeader;
331
+ /**
332
+ * Validates a relative image region for native-resolution zoom.
333
+ *
334
+ * @remarks
335
+ * Mirrors the `AnalyzeMediaParamParser` region contract (`[0,1]` bounds,
336
+ * positive size, `x+width<=1`/`y+height<=1`, complete numeric strings
337
+ * coerced via `optionalFraction`) so direct client callers get the same
338
+ * Spanish coaching instead of a late server rejection.
339
+ *
340
+ * @param region - Raw region value.
341
+ * @returns Validated region payload.
342
+ * @throws Error with an Spanish-first bilingual message when the region is malformed or out of range.
343
+ */
344
+ private static requireValidRegion;
345
+ /**
346
+ * Resolves the session-creation control timeout (60 s, mirrors EnriCode).
347
+ *
348
+ * @returns Control timeout in milliseconds (never above 60 s).
349
+ */
350
+ private createControlTimeoutMs;
351
+ /**
352
+ * Resolves the offset-probe control timeout (15 s, mirrors EnriCode).
353
+ *
354
+ * @returns Control timeout in milliseconds (never above 15 s).
355
+ */
356
+ private probeControlTimeoutMs;
314
357
  /**
315
358
  * Extracts a response header as a single string.
316
359
  *
@@ -326,6 +369,7 @@ export declare class EnriProxyClient {
326
369
  * @param url - Target URL
327
370
  * @param jsonBody - JSON payload
328
371
  * @param timeoutMs - Timeout in milliseconds
372
+ * @param signal - Optional cancellation signal.
329
373
  * @returns HTTP result
330
374
  */
331
375
  private requestJson;
@@ -337,6 +381,7 @@ export declare class EnriProxyClient {
337
381
  * @param headers - Request headers
338
382
  * @param body - Request body
339
383
  * @param timeoutMs - Timeout in milliseconds
384
+ * @param signal - Optional cancellation signal; aborts with a Spanish error.
340
385
  * @returns HTTP result
341
386
  */
342
387
  private requestRaw;