docling-serve-client 0.4.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/images.ts CHANGED
@@ -2,7 +2,7 @@
2
2
  // SPDX-License-Identifier: Apache-2.0
3
3
 
4
4
  import { isPlainObject } from './core/validate.js';
5
- import type { DoclingDocument, PageItem, ProvenanceItem } from './document.js';
5
+ import type { BoundingBox, DoclingDocument, PageItem } from './document.js';
6
6
  import type {
7
7
  ExtractedImage,
8
8
  ImageOptions,
@@ -113,7 +113,6 @@ export function readPngSize(bytes: Uint8Array): ImageSize | undefined {
113
113
  if (!(bytes instanceof Uint8Array) || bytes.length < 24) {
114
114
  return undefined;
115
115
  }
116
- // IHDR holds 32-bit width and height at offsets 16 and 20.
117
116
  const isPng =
118
117
  PNG_SIGNATURE.every((byte, i) => bytes[i] === byte) && PNG_IHDR.every((byte, i) => bytes[12 + i] === byte);
119
118
  if (!isPng) {
@@ -154,7 +153,6 @@ export function extractImages(markdown: string, options: Required<ImageOptions>)
154
153
  }
155
154
  const size = readPngSize(bytes);
156
155
  if (options.omitSmall && size !== undefined && isSmallImage(size)) {
157
- // a marker, so removing it below also normalizes the blank lines around it
158
156
  output += OMITTED_IMAGE;
159
157
  continue;
160
158
  }
@@ -218,11 +216,11 @@ export function extractPageImages(document: DoclingDocument): ExtractedPageImage
218
216
  * - `prov`: any `prov[]` entry; for a table cell pass `{ page_no: table.prov[0].page_no, bbox: cell.bbox }`
219
217
  * - flips `BOTTOMLEFT` boxes using the page height from `document.pages`, then scales points by `PAGE_IMAGE_SCALE`
220
218
  * - works without `pageImages: true`; the boxes fit the page images of any conversion by this client
221
- * - returns `undefined` when page_no or a coordinate is not a finite number, or page is missing from `document.pages`
219
+ * - returns `undefined` when `page_no` or coordinate is not a finite number, or page is missing from `document.pages`
222
220
  */
223
221
  export function toPageImageBox(
224
222
  document: DoclingDocument,
225
- prov: Pick<ProvenanceItem, 'page_no' | 'bbox'>,
223
+ prov: { page_no: number; bbox?: BoundingBox | null },
226
224
  ): PageImageBox | undefined {
227
225
  const pageNo = prov?.page_no;
228
226
  if (!isFiniteNumber(pageNo)) {
@@ -263,7 +261,7 @@ export function removeImagePlaceholders(markdown: string): string {
263
261
  }
264
262
 
265
263
  // -------------------------------------------------------------------
266
- // --- 4. Internal Functions
264
+ // --- 4. Private Functions
267
265
  // -------------------------------------------------------------------
268
266
 
269
267
  /**
package/src/index.ts CHANGED
@@ -8,6 +8,9 @@
8
8
  export type * from './document.js';
9
9
  export type {
10
10
  CallOptions,
11
+ CapabilityLimits,
12
+ CapabilityPreset,
13
+ CapabilityStage,
11
14
  ClientOptions,
12
15
  ConversionFailure,
13
16
  ConvertError,
@@ -17,6 +20,7 @@ export type {
17
20
  DoclingClient,
18
21
  DoclingConfig,
19
22
  DoclingOptions,
23
+ DoclingSettings,
20
24
  DocumentImageMode,
21
25
  DocumentJob,
22
26
  DocumentOptions,
@@ -27,24 +31,29 @@ export type {
27
31
  ImageSize,
28
32
  ImageWarning,
29
33
  Job,
34
+ JobRef,
30
35
  JobState,
31
36
  MarkdownImageMode,
32
37
  MarkdownJob,
33
38
  MarkdownJobOutput,
34
39
  MarkdownOptions,
35
40
  MarkdownWarning,
41
+ OcrEngine,
36
42
  PageImage,
37
43
  PageImageBox,
38
44
  PageImageWarning,
39
45
  PdfBackend,
40
46
  PdfInput,
41
47
  Pipeline,
48
+ PollOptions,
42
49
  Result,
50
+ ServerCapabilities,
43
51
  ServerDetails,
44
52
  ServerError,
45
53
  ServerVersions,
46
54
  ServerWarning,
47
55
  TaskFailure,
56
+ WithSettings,
48
57
  } from './types.js';
49
58
 
50
59
  // -------------------------------------------------------------------
package/src/options.ts CHANGED
@@ -19,10 +19,11 @@ import type {
19
19
  DocumentImageMode,
20
20
  DocumentOptions,
21
21
  ImageOptions,
22
- Job,
22
+ JobRef,
23
23
  MarkdownImageMode,
24
24
  MarkdownJobOutput,
25
25
  MarkdownOptions,
26
+ PollOptions,
26
27
  Result,
27
28
  } from './types.js';
28
29
 
@@ -44,10 +45,18 @@ export type ResolvedConfig = {
44
45
  fetch: typeof fetch;
45
46
  /** Time limit per request in ms; unset means no limit. */
46
47
  timeoutMs?: number;
48
+ /** Delay between job helper status polls in ms; defaults to `DEFAULT_POLL_MS`. */
49
+ pollMs: number;
47
50
  /** Factory defaults merged over `DEFAULT_OPTIONS`. */
48
51
  defaults: ResolvedOptions;
49
52
  };
50
53
 
54
+ /** Validated poll settings for job helpers. */
55
+ export type PollCall = {
56
+ pollMs: number;
57
+ signal?: AbortSignal;
58
+ };
59
+
51
60
  /** Validated md call: server options, image handling and signal. */
52
61
  export type MarkdownCall = {
53
62
  options: ResolvedOptions;
@@ -89,6 +98,7 @@ export const DEFAULT_OPTIONS: Readonly<ResolvedOptions> = Object.freeze({
89
98
  pdfBackend: 'docling_parse',
90
99
  ocr: true,
91
100
  forceOcr: false,
101
+ ocrEngine: 'auto',
92
102
  });
93
103
 
94
104
  /** Rules for docling options, accepted by the factory and every conversion call. */
@@ -101,6 +111,7 @@ const CALL_OPTION_RULES: Record<keyof DoclingOptions, OptionRule> = {
101
111
  pdfBackend: (value) => isOneOf(value, PDF_BACKENDS),
102
112
  ocr: isBoolean,
103
113
  forceOcr: isBoolean,
114
+ ocrEngine: (value) => typeof value === 'string' && value !== '',
104
115
  };
105
116
 
106
117
  /** Rules for factory options: per-call options plus the factory-only ones. */
@@ -133,20 +144,42 @@ const IMAGE_OPTION_RULES: Record<keyof ImageOptions, OptionRule> = {
133
144
  omitSmall: isBoolean,
134
145
  };
135
146
 
136
- /** Rules for `check` options. */
147
+ /** Rules for `check` and `capabilities` options. */
137
148
  const CHECK_RULES: Record<keyof CallOptions, OptionRule> = {
138
149
  signal: isAbortSignal,
139
150
  };
140
151
 
152
+ /** Rules for `waitForJob` options. */
153
+ const POLL_RULES: Record<keyof PollOptions, OptionRule> = {
154
+ ...CHECK_RULES,
155
+ pollMs: isTimerDelay,
156
+ };
157
+
158
+ /** Rules for `runMarkdown` options: md call options plus `pollMs`. */
159
+ const RUN_MARKDOWN_RULES: Record<keyof (MarkdownOptions & PollOptions), OptionRule> = {
160
+ ...MARKDOWN_RULES,
161
+ ...POLL_RULES,
162
+ };
163
+
164
+ /** Rules for `runDocument` options: document call options plus `pollMs`. */
165
+ const RUN_DOCUMENT_RULES: Record<keyof (DocumentOptions & PollOptions), OptionRule> = {
166
+ ...DOCUMENT_RULES,
167
+ ...POLL_RULES,
168
+ };
169
+
141
170
  /** Rules for factory configuration; the keys of `options` are checked separately. */
142
171
  const CONFIG_RULES: Record<keyof DoclingConfig, OptionRule> = {
143
172
  baseUrl: isHttpUrl,
144
173
  apiKey: (value) => typeof value === 'string' && value !== '',
145
174
  fetch: (value) => typeof value === 'function',
146
- timeoutMs: isPositiveNumber,
175
+ timeoutMs: isTimerDelay,
176
+ pollMs: isTimerDelay,
147
177
  options: isPlainObject,
148
178
  };
149
179
 
180
+ /** Default delay in ms between job helper status polls. */
181
+ const DEFAULT_POLL_MS = 10;
182
+
150
183
  /** Job id of word characters and `-` only, so it cannot leave the URL path segment. */
151
184
  const MATCH_JOB_ID = /^[\w-]+$/;
152
185
 
@@ -162,6 +195,9 @@ const MATCH_TRAILING_SLASHES = /\/+$/;
162
195
  /** Matches HTTP or HTTPS protocol schemes. */
163
196
  const MATCH_HTTP_PROTOCOL = /^https?:$/;
164
197
 
198
+ /** Maximum timer delay in ms (2^31 - 1); Node timers overflow above this and fire after 1 ms. */
199
+ const MAX_TIMEOUT_MS = 2_147_483_647;
200
+
165
201
  /** Error message for a missing or malformed job object. */
166
202
  const INVALID_JOB = 'Expected the job object returned by submitMarkdown() or submitDocument()';
167
203
 
@@ -179,7 +215,14 @@ export function resolveConfig(config: unknown): ResolvedConfig {
179
215
  if (!isPlainObject(config)) {
180
216
  throw new TypeError('Invalid config: expected an object');
181
217
  }
182
- const { baseUrl, apiKey, fetch: fetchFn, timeoutMs, options } = validateKeys<DoclingConfig>(config, CONFIG_RULES);
218
+ const {
219
+ baseUrl,
220
+ apiKey,
221
+ fetch: fetchFn,
222
+ timeoutMs,
223
+ pollMs = DEFAULT_POLL_MS,
224
+ options,
225
+ } = validateKeys<DoclingConfig>(config, CONFIG_RULES);
183
226
  if (baseUrl === undefined) {
184
227
  throw new TypeError(invalidValue('baseUrl'));
185
228
  }
@@ -192,44 +235,83 @@ export function resolveConfig(config: unknown): ResolvedConfig {
192
235
  ...(apiKey === undefined ? {} : { apiKey }),
193
236
  fetch: fetchFn ?? fetch,
194
237
  ...(timeoutMs === undefined ? {} : { timeoutMs }),
238
+ pollMs,
195
239
  defaults,
196
240
  };
197
241
  }
198
242
 
199
- /** Validates md call options over factory defaults; throws TypeError on invalid options. */
243
+ /**
244
+ * Merges and validates md call options against factory defaults.
245
+ * @throws `TypeError` on invalid options.
246
+ */
200
247
  export function resolveMarkdownCall(defaults: Readonly<ResolvedOptions>, options: unknown): MarkdownCall {
201
- const {
202
- signal,
203
- images = 'placeholder',
204
- imageOptions,
205
- ...docling
206
- } = validateKeys<MarkdownOptions>(callObject(options), MARKDOWN_RULES);
207
- const output = resolveImageOutput(images, imageOptions);
208
- return { options: { ...defaults, ...docling }, output, ...(signal === undefined ? {} : { signal }) };
248
+ return toMarkdownCall(defaults, validateKeys<MarkdownOptions>(callObject(options), MARKDOWN_RULES));
209
249
  }
210
250
 
211
- /** Validates document call options over factory defaults; throws TypeError on invalid options. */
251
+ /**
252
+ * Merges and validates document call options against factory defaults.
253
+ * @throws `TypeError` on invalid options.
254
+ */
212
255
  export function resolveDocumentCall(defaults: Readonly<ResolvedOptions>, options: unknown): DocumentCall {
213
- const {
214
- signal,
215
- images = 'embedded',
216
- pageImages = false,
217
- ...docling
218
- } = validateKeys<DocumentOptions>(callObject(options), DOCUMENT_RULES);
219
- return { options: { ...defaults, ...docling }, images, pageImages, ...(signal === undefined ? {} : { signal }) };
256
+ return toDocumentCall(defaults, validateKeys<DocumentOptions>(callObject(options), DOCUMENT_RULES));
220
257
  }
221
258
 
222
- /** Validates `check` call options (signal only); throws TypeError on invalid options. */
259
+ /**
260
+ * Merges md call options and poll settings; `pollMs` defaults to factory `pollMs`.
261
+ * @throws `TypeError` on invalid options.
262
+ */
263
+ export function resolveRunMarkdownCall(
264
+ defaults: Readonly<ResolvedOptions>,
265
+ defaultPollMs: number,
266
+ options: unknown,
267
+ ): MarkdownCall & PollCall {
268
+ const { pollMs = defaultPollMs, ...call } = validateKeys<MarkdownOptions & PollOptions>(
269
+ callObject(options),
270
+ RUN_MARKDOWN_RULES,
271
+ );
272
+ return { ...toMarkdownCall(defaults, call), pollMs };
273
+ }
274
+
275
+ /**
276
+ * Merges document call options and poll settings; `pollMs` defaults to factory `pollMs`.
277
+ * @throws `TypeError` on invalid options.
278
+ */
279
+ export function resolveRunDocumentCall(
280
+ defaults: Readonly<ResolvedOptions>,
281
+ defaultPollMs: number,
282
+ options: unknown,
283
+ ): DocumentCall & PollCall {
284
+ const { pollMs = defaultPollMs, ...call } = validateKeys<DocumentOptions & PollOptions>(
285
+ callObject(options),
286
+ RUN_DOCUMENT_RULES,
287
+ );
288
+ return { ...toDocumentCall(defaults, call), pollMs };
289
+ }
290
+
291
+ /**
292
+ * Checks signal-only call options (`check`, `capabilities`).
293
+ * @throws `TypeError` on invalid options.
294
+ */
223
295
  export function resolveCheckOptions(options: unknown): CallOptions {
224
296
  return validateKeys<CallOptions>(callObject(options), CHECK_RULES);
225
297
  }
226
298
 
299
+ /**
300
+ * Checks `waitForJob` options; `pollMs` defaults to factory `pollMs`.
301
+ * @throws `TypeError` on invalid options.
302
+ */
303
+ export function resolvePollOptions(defaultPollMs: number, options: unknown): PollCall {
304
+ const { pollMs = defaultPollMs, signal } = validateKeys<PollOptions>(callObject(options), POLL_RULES);
305
+ return { pollMs, ...(signal === undefined ? {} : { signal }) };
306
+ }
307
+
227
308
  /**
228
309
  * **Checks a job object against the shape `submitMarkdown()` and `submitDocument()` return**.
229
310
  *
230
311
  * - never throws: a malformed job is `invalid_input`
312
+ * - `settings` and other extra keys are ignored and not returned
231
313
  */
232
- export function validateJob(job: unknown): Result<Job> {
314
+ export function validateJob(job: unknown): Result<JobRef> {
233
315
  if (!isPlainObject(job) || !isJobId(job.id)) {
234
316
  return fail(ERR.INVALID_INPUT(INVALID_JOB));
235
317
  }
@@ -256,10 +338,13 @@ export function isJobId(value: unknown): value is string {
256
338
  }
257
339
 
258
340
  // -------------------------------------------------------------------
259
- // --- 4. Internal Functions
341
+ // --- 4. Private Functions
260
342
  // -------------------------------------------------------------------
261
343
 
262
- /** Returns call options as a plain object (`undefined` becomes `{}`); throws TypeError otherwise. */
344
+ /**
345
+ * Returns call options as a plain object (`undefined` becomes `{}`).
346
+ * @throws `TypeError` when options is not a plain object.
347
+ */
263
348
  function callObject(options: unknown): Record<string, unknown> {
264
349
  if (options === undefined) {
265
350
  return {};
@@ -270,6 +355,22 @@ function callObject(options: unknown): Record<string, unknown> {
270
355
  return options;
271
356
  }
272
357
 
358
+ /**
359
+ * Splits validated md call options into server options over the defaults, image handling and signal.
360
+ * @throws `TypeError` on a missing, unexpected or invalid `imageOptions`.
361
+ */
362
+ function toMarkdownCall(defaults: Readonly<ResolvedOptions>, options: Partial<MarkdownOptions>): MarkdownCall {
363
+ const { signal, images = 'placeholder', imageOptions, ...docling } = options;
364
+ const output = resolveImageOutput(images, imageOptions);
365
+ return { options: { ...defaults, ...docling }, output, ...(signal === undefined ? {} : { signal }) };
366
+ }
367
+
368
+ /** Splits validated document call options into server options over the defaults, image modes and signal. */
369
+ function toDocumentCall(defaults: Readonly<ResolvedOptions>, options: Partial<DocumentOptions>): DocumentCall {
370
+ const { signal, images = 'embedded', pageImages = false, ...docling } = options;
371
+ return { options: { ...defaults, ...docling }, images, pageImages, ...(signal === undefined ? {} : { signal }) };
372
+ }
373
+
273
374
  /**
274
375
  * **Combines image mode and image options into the md job output**.
275
376
  *
@@ -301,6 +402,11 @@ function isPositiveNumber(value: unknown): value is number {
301
402
  return typeof value === 'number' && Number.isFinite(value) && value > 0;
302
403
  }
303
404
 
405
+ /** Integer ms from 1 to `MAX_TIMEOUT_MS`; `AbortSignal.timeout` throws on fractions, `setTimeout` overflows above. */
406
+ function isTimerDelay(value: unknown): value is number {
407
+ return typeof value === 'number' && Number.isInteger(value) && value >= 1 && value <= MAX_TIMEOUT_MS;
408
+ }
409
+
304
410
  /** 1-based ascending integer pair `[from, to]`. */
305
411
  function isPageRange(value: unknown): value is [number, number] {
306
412
  return (
@@ -328,7 +434,16 @@ function isAbortSignal(value: unknown): value is AbortSignal {
328
434
  return value instanceof AbortSignal;
329
435
  }
330
436
 
331
- /** Parseable HTTP or HTTPS URL. */
437
+ /**
438
+ * **HTTP or HTTPS URL that routes can be appended to**.
439
+ *
440
+ * - no `?` or `#`, even empty ones: `${baseUrl}${route}` would put the route into the query or fragment
441
+ * - no user name or password: Node's `fetch` refuses such URLs; the key belongs in `apiKey`
442
+ */
332
443
  function isHttpUrl(value: unknown): value is string {
333
- return typeof value === 'string' && URL.canParse(value) && MATCH_HTTP_PROTOCOL.test(new URL(value).protocol);
444
+ if (typeof value !== 'string' || !URL.canParse(value) || value.includes('?') || value.includes('#')) {
445
+ return false;
446
+ }
447
+ const url = new URL(value);
448
+ return MATCH_HTTP_PROTOCOL.test(url.protocol) && url.username === '' && url.password === '';
334
449
  }
package/src/response.ts CHANGED
@@ -135,7 +135,7 @@ export function readText(value: unknown): string | undefined {
135
135
  }
136
136
 
137
137
  // -------------------------------------------------------------------
138
- // --- 4. Internal Functions
138
+ // --- 4. Private Functions
139
139
  // -------------------------------------------------------------------
140
140
 
141
141
  /**
@@ -212,7 +212,7 @@ function readServerError(error: DoclingErrorItem): ServerWarning[] {
212
212
  ];
213
213
  }
214
214
 
215
- /** docling `page_no`: integer >= 1; `0`, negatives and fractions are rejected. */
215
+ /** Docling `page_no`: integer >= 1; 0, negatives and fractions are rejected. */
216
216
  function isPageNumber(value: unknown): value is number {
217
217
  return typeof value === 'number' && Number.isInteger(value) && value >= 1;
218
218
  }