@saplingai/sapling-js 1.0.54 → 1.0.55
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/README.md +76 -7
- package/package.json +1 -1
- package/sapling/client.d.ts +88 -7
- package/sapling/client.js +182 -2
package/README.md
CHANGED
|
@@ -144,12 +144,12 @@ numbered lines); show those results hedged rather than as a bare score. `sentenc
|
|
|
144
144
|
same model and 0.5 threshold, and their `start`/`end` (like a segment's) index `text` in Unicode code points (`end` exclusive) — slice with
|
|
145
145
|
`Array.from(text).slice(start, end).join('')` if the text may contain emoji. A response may also
|
|
146
146
|
carry an advisory `language` (`{ code, name, confidence }`) when the text is confidently
|
|
147
|
-
identified. English,
|
|
148
|
-
supported: unpinned text detected as `
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
scored by the English model and are less reliable. Options: `sent_scores` (default true), `score_string` (HTML
|
|
152
|
-
heatmap), `lang` (`'en'`, `'
|
|
147
|
+
identified. English, Arabic, Chinese, Dutch, French, German, Italian, Japanese, Korean, Polish,
|
|
148
|
+
Portuguese, Romanian, Russian, Spanish, Swedish and Turkish are supported: unpinned text detected as `ar`,
|
|
149
|
+
`de`, `es`, `fr`, `it`, `ja`, `ko`, `nl`, `pl`, `pt`, `ro`, `ru`, `sv`, `tr` or `zh` is scored by a
|
|
150
|
+
multilingual model (trained on all fifteen) and answers `version: 'ml20261104'`, while other
|
|
151
|
+
languages are scored by the English model and are less reliable. Options: `sent_scores` (default true), `score_string` (HTML
|
|
152
|
+
heatmap), `lang` (`'en'`, `'ar'`, `'de'`, `'es'`, `'fr'`, `'it'`, `'ja'`, `'ko'`, `'nl'`, `'pl'`, `'pt'`, `'ro'`, `'ru'`, `'sv'`, `'tr'` or `'zh'`, case-insensitive, region
|
|
153
153
|
subtags such as `'pt-BR'` accepted: use that language's model instead of detecting it, which helps
|
|
154
154
|
with short texts; omit it or pass `'auto'` to detect; other languages get a 400), `version` (pin
|
|
155
155
|
a model; a pin always overrides the language routing and `lang`). `language` still reports the
|
|
@@ -171,7 +171,7 @@ still scored and billed. `fileAidetect()` and `urlAidetect()` take it too, namin
|
|
|
171
171
|
the file or the final URL.
|
|
172
172
|
|
|
173
173
|
```javascript
|
|
174
|
-
const { data } = await client.aidetect(reviewText, { lang: 'pt-BR' }); // version: '
|
|
174
|
+
const { data } = await client.aidetect(reviewText, { lang: 'pt-BR' }); // version: 'ml20261104'
|
|
175
175
|
```
|
|
176
176
|
|
|
177
177
|
To score a document without extracting its text yourself, upload it with `fileAidetect()`. It
|
|
@@ -208,6 +208,27 @@ const { data } = await client.urlAidetect('https://example.com/blog/post', { sen
|
|
|
208
208
|
// characters: 5120, truncated: false } }
|
|
209
209
|
```
|
|
210
210
|
|
|
211
|
+
### Resume and job description overlap
|
|
212
|
+
|
|
213
|
+
`jdMatch()` measures how closely a resume overlaps with the job description it was submitted for:
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
const { data } = await client.jdMatch(resumeText, jobDescriptionText);
|
|
217
|
+
// { score: 0.786, level: 'high', keyword_coverage: 0.571,
|
|
218
|
+
// matched_keywords: [{ term: 'python', job_description_count: 1, resume_count: 1 }, ...],
|
|
219
|
+
// missing_keywords: [{ term: 'terraform', job_description_count: 1 }, ...],
|
|
220
|
+
// copied_fraction: 0.312, has_copied_span: true, copied_spans_total: 1,
|
|
221
|
+
// copied_spans: [{ start: 136, end: 207, text: '...', words: 10 }],
|
|
222
|
+
// resume_words: 32, job_description_words: 43, version: '...' }
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
`score` (0–1) is a heuristic over keyword coverage and passages of 6+ words shared verbatim, not a
|
|
226
|
+
probability. It says nothing about who wrote the resume, and high overlap can simply mean a good fit,
|
|
227
|
+
so pair it with `aidetect()` and don't use it alone to decide an application. It's unbilled and
|
|
228
|
+
tuned for English. The API caps the resume at 50,000 characters and the posting at 20,000, and a
|
|
229
|
+
posting with fewer than 5 distinct keywords gets a 400. Span offsets index the resume in Unicode code
|
|
230
|
+
points.
|
|
231
|
+
|
|
211
232
|
### Rephrase, tone, sentiment and readability statistics
|
|
212
233
|
|
|
213
234
|
```javascript
|
|
@@ -463,6 +484,37 @@ All three upload methods take a `File`/`Blob` (in the browser, or Node 18+) or t
|
|
|
463
484
|
the format. File problems come back as 400 `{ error: '...' }` and an oversized upload as 413.
|
|
464
485
|
Uploads are limited to 60 per minute per account across all three methods.
|
|
465
486
|
|
|
487
|
+
## Chunking
|
|
488
|
+
|
|
489
|
+
`chunkText()` splits long text into chunks of at most `maxLength` characters, e.g. to keep each
|
|
490
|
+
piece under another endpoint's length limit. Each split prefers a page break, then a paragraph
|
|
491
|
+
break, a line break, a tab, ending punctuation and any whitespace, before falling back to a hard
|
|
492
|
+
cut. Chunks don't overlap, so joining them gives back the original text. `chunkHtml()` does the
|
|
493
|
+
same for HTML after stripping the tags. Both are unbilled.
|
|
494
|
+
|
|
495
|
+
```javascript
|
|
496
|
+
const { data } = await client.chunkText('Here is one paragraph.\n\nHere is another.', 25);
|
|
497
|
+
// { chunks: ['Here is one paragraph.\n\n', 'Here is another.'] }
|
|
498
|
+
```
|
|
499
|
+
|
|
500
|
+
`maxLength` must be a positive integer. The optional `step_size` sets how far back from each cut
|
|
501
|
+
to look for a split point; it defaults to 10% of the text length. See
|
|
502
|
+
[Chunking](https://sapling.ai/docs/api/chunk/).
|
|
503
|
+
|
|
504
|
+
`pdfToText()` and `docxToText()` return the plain text of an uploaded PDF or Word document, for
|
|
505
|
+
example to chunk it before sending it to another endpoint. Pass a `File`/`Blob`, or the file's
|
|
506
|
+
bytes with `filename`, which must end in `.pdf` or `.docx`. Both are unbilled.
|
|
507
|
+
|
|
508
|
+
```javascript
|
|
509
|
+
import { readFile } from 'node:fs/promises';
|
|
510
|
+
|
|
511
|
+
const { data } = await client.pdfToText(await readFile('report.pdf'), { filename: 'report.pdf' });
|
|
512
|
+
const { data: chunks } = await client.chunkText(data.text, 4000);
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
On a file the API can't read (password-protected, empty or corrupt), the 400 body is `{ error }`
|
|
516
|
+
rather than `{ msg }`. See [File Uploads](https://sapling.ai/docs/api/files/).
|
|
517
|
+
|
|
466
518
|
## Translation
|
|
467
519
|
|
|
468
520
|
Translate text into a target language, named by ISO 639 code (`'fr'`, `'zh-TW'`) or English
|
|
@@ -606,3 +658,20 @@ console.log(data.total_characters, data.total_cost_usd, data.by_endpoint);
|
|
|
606
658
|
|
|
607
659
|
A window over a year gets a `403`. Unlike `accountStatus()`, these need a key that isn't
|
|
608
660
|
blocked. See [API Reporting](https://sapling.ai/docs/api/api-reporting/).
|
|
661
|
+
|
|
662
|
+
## Health checks
|
|
663
|
+
|
|
664
|
+
`version()` and `liveness()` are unbilled and need no API key. The client doesn't send its key
|
|
665
|
+
on these calls.
|
|
666
|
+
|
|
667
|
+
```javascript
|
|
668
|
+
const { data } = await client.version();
|
|
669
|
+
console.log(data.version, data.build); // 2026.6 v4626
|
|
670
|
+
|
|
671
|
+
await client.liveness(); // { msg: 'alive' }
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
`version()` reports which release a hostname is serving, which is useful on-prem. `build` is
|
|
675
|
+
`'unknown'` when the image carries no build number. `liveness()` only says the API process is up,
|
|
676
|
+
so it can succeed while a service behind the API is down. To check that a key works, use
|
|
677
|
+
`accountStatus()`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@saplingai/sapling-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.55",
|
|
4
4
|
"description": "JavaScript client for the Sapling.ai API (grammar checking, AI content detection, rephrasing and more) plus the browser SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./sapling/client.js",
|
package/sapling/client.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ export type AdvancedEditOptions = {
|
|
|
23
23
|
export type ConstructorOptions = {
|
|
24
24
|
timeout?: number,
|
|
25
25
|
hostname?: string,
|
|
26
|
+
/** API path override. Leading and trailing slashes are optional. */
|
|
26
27
|
pathname?: string,
|
|
27
28
|
/**
|
|
28
29
|
* When true, every request carries the API's `return_usage: true` flag and
|
|
@@ -148,11 +149,11 @@ export type AIDetectOptions = {
|
|
|
148
149
|
sent_scores?: boolean,
|
|
149
150
|
/** Include `score_string`, an HTML heatmap of the scored text. */
|
|
150
151
|
score_string?: boolean,
|
|
151
|
-
/** Pin a detector model version (e.g. `'
|
|
152
|
-
* model); omitted = current default, with text detected as `es`/`
|
|
152
|
+
/** Pin a detector model version (e.g. `'20260820'`, or `'ml20261104'` for the current multilingual
|
|
153
|
+
* model; the previous multilingual models `'ml20261009'`, `'ml20261031'`, `'ml20261102'` and `'ml20261103'` stay pinnable); omitted = current default, with text detected as `ar`/`de`/`es`/`fr`/`it`/`ja`/`ko`/`nl`/`pl`/`pt`/`ro`/`ru`/`sv`/`tr`/`zh`
|
|
153
154
|
* routed to the multilingual model. A pin always wins over that routing and over `lang`. */
|
|
154
155
|
version?: string,
|
|
155
|
-
/** Language of the text: `'en'`, `'
|
|
156
|
+
/** Language of the text: `'en'`, `'ar'`, `'de'`, `'es'`, `'fr'`, `'it'`, `'ja'`, `'ko'`, `'nl'`, `'pl'`, `'pt'`, `'ro'`, `'ru'`, `'sv'`, `'tr'` or `'zh'`
|
|
156
157
|
* (case-insensitive; region subtags such as `'pt-BR'` are accepted). Picks that language's
|
|
157
158
|
* model instead of relying on detection, which helps with short texts; omit it (or pass
|
|
158
159
|
* `'auto'`) to detect.
|
|
@@ -228,8 +229,8 @@ export type AIDetectResponse = {
|
|
|
228
229
|
text: string;
|
|
229
230
|
tokens: string[];
|
|
230
231
|
token_probs: number[];
|
|
231
|
-
/** The pinned `version` when one was requested; `'
|
|
232
|
-
* detected as (or `lang` named) `es`/`
|
|
232
|
+
/** The pinned `version` when one was requested; `'ml20261104'` when unpinned text was
|
|
233
|
+
* detected as (or `lang` named) `ar`/`de`/`es`/`fr`/`it`/`ja`/`ko`/`nl`/`pl`/`pt`/`ro`/`ru`/`sv`/`tr`/`zh` and scored by the
|
|
233
234
|
* multilingual model; otherwise an opaque identifier of the default model — log it, don't
|
|
234
235
|
* parse it. */
|
|
235
236
|
version: string;
|
|
@@ -239,8 +240,8 @@ export type AIDetectResponse = {
|
|
|
239
240
|
score_string?: string;
|
|
240
241
|
/** Advisory detected language; omitted unless detection is high-confidence. Always the
|
|
241
242
|
* detected language, even when `lang` was sent, so a mismatch is visible.
|
|
242
|
-
* English plus `
|
|
243
|
-
* model) are supported; other languages are scored by the English model and are less
|
|
243
|
+
* English plus `ar`, `de`, `es`, `fr`, `it`, `ja`, `ko`, `nl`, `pl`, `pt`, `ro`, `ru`, `sv`,
|
|
244
|
+
* `tr` and `zh` (routed to the multilingual model) are supported; other languages are scored by the English model and are less
|
|
244
245
|
* reliable. */
|
|
245
246
|
language?: AIDetectLanguage;
|
|
246
247
|
}
|
|
@@ -341,6 +342,61 @@ export type DeleteStyleguideRulesetResponse = {
|
|
|
341
342
|
deleted: string;
|
|
342
343
|
}
|
|
343
344
|
|
|
345
|
+
export type ChunkOptions = {
|
|
346
|
+
step_size?: number;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
export type ChunkResponse = {
|
|
350
|
+
chunks: string[];
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export type JdMatchLevel = 'low' | 'moderate' | 'high';
|
|
354
|
+
|
|
355
|
+
export type JdMatchMatchedKeyword = {
|
|
356
|
+
term: string;
|
|
357
|
+
job_description_count: number;
|
|
358
|
+
resume_count: number;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
export type JdMatchMissingKeyword = {
|
|
362
|
+
term: string;
|
|
363
|
+
job_description_count: number;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export type JdMatchCopiedSpan = {
|
|
367
|
+
/** Offsets into the submitted resume (`end` exclusive), in Unicode code points —
|
|
368
|
+
* slice with `Array.from(text)` if it may contain emoji. */
|
|
369
|
+
start: number;
|
|
370
|
+
end: number;
|
|
371
|
+
text: string;
|
|
372
|
+
words: number;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/** jdMatch(): a text overlap report. It says nothing about who wrote the resume. */
|
|
376
|
+
export type JdMatchResponse = {
|
|
377
|
+
/** 0–1. A heuristic over keyword coverage and copying, not a probability. */
|
|
378
|
+
score: number;
|
|
379
|
+
level: JdMatchLevel;
|
|
380
|
+
keyword_coverage: number;
|
|
381
|
+
matched_keywords: JdMatchMatchedKeyword[];
|
|
382
|
+
missing_keywords: JdMatchMissingKeyword[];
|
|
383
|
+
/** Share of the resume's words inside a copied span. */
|
|
384
|
+
copied_fraction: number;
|
|
385
|
+
/** Whether the resume shares any run of 6+ words verbatim with the posting. */
|
|
386
|
+
has_copied_span: boolean;
|
|
387
|
+
/** At most 50, in document order; `copied_spans_total` has the full count. */
|
|
388
|
+
copied_spans: JdMatchCopiedSpan[];
|
|
389
|
+
copied_spans_total: number;
|
|
390
|
+
resume_words: number;
|
|
391
|
+
job_description_words: number;
|
|
392
|
+
version: string;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
/** pdfToText()/docxToText(): the document's plain text. */
|
|
396
|
+
export type FileToTextResponse = {
|
|
397
|
+
text: string;
|
|
398
|
+
}
|
|
399
|
+
|
|
344
400
|
export type AccountBlockedReason =
|
|
345
401
|
| 'key_expired'
|
|
346
402
|
| 'subscription_expired'
|
|
@@ -353,6 +409,9 @@ export type AccountCreditSummary = {
|
|
|
353
409
|
enabled: boolean;
|
|
354
410
|
currency: string;
|
|
355
411
|
alert_threshold_percent: number;
|
|
412
|
+
// Prepaid-only account: API access needs a positive credit balance. Sent
|
|
413
|
+
// whether or not `enabled` is true (optional for older on-prem servers).
|
|
414
|
+
credits_only?: boolean;
|
|
356
415
|
// Present only when `enabled` is true.
|
|
357
416
|
balance_credits?: number;
|
|
358
417
|
purchased_credits?: number;
|
|
@@ -1058,6 +1117,17 @@ export type UserDetails = {
|
|
|
1058
1117
|
anonUser?: boolean,
|
|
1059
1118
|
}
|
|
1060
1119
|
|
|
1120
|
+
export type VersionResponse = {
|
|
1121
|
+
/** Release version, for example `'2026.6'`. */
|
|
1122
|
+
version: string;
|
|
1123
|
+
/** Build identifier; `'unknown'` when the image carries no build number. */
|
|
1124
|
+
build: string;
|
|
1125
|
+
};
|
|
1126
|
+
|
|
1127
|
+
export type LivenessResponse = {
|
|
1128
|
+
msg: 'alive';
|
|
1129
|
+
};
|
|
1130
|
+
|
|
1061
1131
|
export declare class Client {
|
|
1062
1132
|
constructor(apiKey: string, options?: ConstructorOptions);
|
|
1063
1133
|
|
|
@@ -1142,6 +1212,17 @@ export declare class Client {
|
|
|
1142
1212
|
classify(texts: string[], labels: ClassifyLabel[], options?: ClassifyOptions): Promise<AxiosResponse<WithUsage<ClassifyBatchResponse>>>;
|
|
1143
1213
|
classify(text: string | string[], labels: ClassifyLabel[], options?: ClassifyOptions): Promise<AxiosResponse<WithUsage<ClassifyResponse | ClassifyBatchResponse>>>;
|
|
1144
1214
|
seo(text: string, options?: SeoOptions): Promise<AxiosResponse<WithUsage<SeoResponse>>>;
|
|
1215
|
+
jdMatch(text: string, jobDescription: string): Promise<AxiosResponse<WithUsage<JdMatchResponse>>>;
|
|
1216
|
+
chunkText(text: string, maxLength: number, options?: ChunkOptions): Promise<AxiosResponse<WithUsage<ChunkResponse>>>;
|
|
1217
|
+
chunkHtml(html: string, maxLength: number, options?: ChunkOptions): Promise<AxiosResponse<WithUsage<ChunkResponse>>>;
|
|
1218
|
+
/** `file` must be named `*.pdf` (a File's own name, or options.filename). */
|
|
1219
|
+
pdfToText(file: UploadFile, options?: FileUploadOptions): Promise<AxiosResponse<WithUsage<FileToTextResponse>>>;
|
|
1220
|
+
/** `file` must be named `*.docx` (a File's own name, or options.filename). */
|
|
1221
|
+
docxToText(file: UploadFile, options?: FileUploadOptions): Promise<AxiosResponse<WithUsage<FileToTextResponse>>>;
|
|
1222
|
+
/** Not WithUsage<>: public and unbilled, and sends neither the API key nor return_usage. */
|
|
1223
|
+
version(): Promise<AxiosResponse<VersionResponse>>;
|
|
1224
|
+
/** Not WithUsage<>: public and unbilled, and sends neither the API key nor return_usage. */
|
|
1225
|
+
liveness(): Promise<AxiosResponse<LivenessResponse>>;
|
|
1145
1226
|
accountStatus(): Promise<AxiosResponse<AccountStatusResponse>>;
|
|
1146
1227
|
apiUsage(options?: ReportingWindowOptions): Promise<AxiosResponse<WithUsage<ApiUsageResponse>>>;
|
|
1147
1228
|
apiEndpointUsage(options?: ApiEndpointUsageOptions): Promise<AxiosResponse<WithUsage<ApiEndpointUsageResponse>>>;
|
package/sapling/client.js
CHANGED
|
@@ -45,6 +45,23 @@ const reportingWindowBody = (apiKey, options, method) => {
|
|
|
45
45
|
return data;
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
// Shared by chunkText() and chunkHtml(). The API reads both numbers with an
|
|
49
|
+
// integer type check (a numeric string like "25" is a 400), and max_length is
|
|
50
|
+
// required in practice: its default of 0 fails the positive-integer check.
|
|
51
|
+
const chunkBody = (apiKey, maxLength, options, method) => {
|
|
52
|
+
if (!Number.isInteger(maxLength) || maxLength < 1) {
|
|
53
|
+
throw new TypeError(`${method}: maxLength must be a positive integer.`);
|
|
54
|
+
}
|
|
55
|
+
const data = { key: apiKey, max_length: maxLength };
|
|
56
|
+
if (options?.step_size !== undefined) {
|
|
57
|
+
if (!Number.isInteger(options.step_size) || options.step_size < 0) {
|
|
58
|
+
throw new TypeError(`${method}: step_size must be a non-negative integer.`);
|
|
59
|
+
}
|
|
60
|
+
data.step_size = options.step_size;
|
|
61
|
+
}
|
|
62
|
+
return data;
|
|
63
|
+
};
|
|
64
|
+
|
|
48
65
|
// Shared by styleguide() and saveStyleguideRuleset(). Shape/type checks only —
|
|
49
66
|
// size limits (rule/description length) are server configuration and enforced
|
|
50
67
|
// there with a clear 400 (see classify()).
|
|
@@ -178,12 +195,24 @@ const buildUploadForm = (file, filename, jsonParams, method) => {
|
|
|
178
195
|
return form;
|
|
179
196
|
};
|
|
180
197
|
|
|
198
|
+
// Shared by pdfToText() and docxToText(): the key (and the client-level
|
|
199
|
+
// return_usage flag, which axiosPromise only injects into plain-object bodies)
|
|
200
|
+
// ride the jsonParams part.
|
|
201
|
+
const fileToTextForm = (client, file, options, method) => {
|
|
202
|
+
const params = { key: client.apiKey };
|
|
203
|
+
if (client.returnUsage) {
|
|
204
|
+
params.return_usage = true;
|
|
205
|
+
}
|
|
206
|
+
return buildUploadForm(file, options?.filename, params, method);
|
|
207
|
+
};
|
|
208
|
+
|
|
181
209
|
export class Client {
|
|
182
210
|
constructor(apiKey, options = {}) {
|
|
183
211
|
this.apiKey = apiKey;
|
|
184
212
|
this.timeout = options.timeout || 120000;
|
|
185
|
-
this.hostname = options.hostname || 'https://api.sapling.ai';
|
|
186
|
-
|
|
213
|
+
this.hostname = (options.hostname || 'https://api.sapling.ai').replace(/\/+$/, '');
|
|
214
|
+
const path = (options.pathname || '/api/v1/').replace(/^\/+|\/+$/g, '');
|
|
215
|
+
this.pathname = path ? `/${path}/` : '/';
|
|
187
216
|
this.urlEndpoint = `${this.hostname}${this.pathname}`;
|
|
188
217
|
this.defaultSessionId = uuidv4();
|
|
189
218
|
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a
|
|
@@ -2072,6 +2101,157 @@ export class Client {
|
|
|
2072
2101
|
return this.axiosPromise(url, 'post', data);
|
|
2073
2102
|
}
|
|
2074
2103
|
|
|
2104
|
+
/**
|
|
2105
|
+
* Measure how closely a resume overlaps with a job description
|
|
2106
|
+
* (POST /api/v1/jd-match): the share of the posting's keywords the resume
|
|
2107
|
+
* uses, the passages the two share word for word, and a low / moderate / high
|
|
2108
|
+
* level. Deterministic and unbilled; both texts count against the rate limit.
|
|
2109
|
+
*
|
|
2110
|
+
* `score` is a heuristic over those two signals, not a probability, and says
|
|
2111
|
+
* nothing about who wrote the resume. High overlap can simply mean a good fit,
|
|
2112
|
+
* so pair it with aidetect() and don't use it alone to decide an application.
|
|
2113
|
+
*
|
|
2114
|
+
* @param {string} text - The resume. The API caps it at 50,000 characters and
|
|
2115
|
+
* compares it as sent, so `copied_spans` offsets index this exact string, in
|
|
2116
|
+
* Unicode code points.
|
|
2117
|
+
* @param {string} jobDescription - The posting (sent as the API's
|
|
2118
|
+
* `job_description`). The API caps it at 20,000 characters and answers 400
|
|
2119
|
+
* when it has fewer than 5 distinct keywords.
|
|
2120
|
+
* @returns {Promise<AxiosResponse>}
|
|
2121
|
+
*/
|
|
2122
|
+
jdMatch(text, jobDescription) {
|
|
2123
|
+
// Length, empty-text and keyword-count checks stay on the server, which
|
|
2124
|
+
// answers each with a clear 400.
|
|
2125
|
+
if (typeof text !== 'string') {
|
|
2126
|
+
throw new TypeError('jdMatch: text must be a string.');
|
|
2127
|
+
}
|
|
2128
|
+
if (typeof jobDescription !== 'string') {
|
|
2129
|
+
throw new TypeError('jdMatch: jobDescription must be a string.');
|
|
2130
|
+
}
|
|
2131
|
+
const data = { key: this.apiKey, text, job_description: jobDescription };
|
|
2132
|
+
return this.axiosPromise(`${this.urlEndpoint}jd-match`, 'post', data);
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
/**
|
|
2136
|
+
* Split text into chunks of at most `maxLength` characters, e.g. to fit an
|
|
2137
|
+
* endpoint's length limit or an LLM context window. Each split prefers, in
|
|
2138
|
+
* order, a page break, a paragraph break, a line break, a tab, ending
|
|
2139
|
+
* punctuation (`.`, `?`, `!`) and then any whitespace, and falls back to a
|
|
2140
|
+
* hard cut at `maxLength`. Chunks don't overlap and keep their delimiters,
|
|
2141
|
+
* so joining them gives back the original text. Unbilled.
|
|
2142
|
+
*
|
|
2143
|
+
* Returns `chunks`, an array of strings in document order.
|
|
2144
|
+
*
|
|
2145
|
+
* @param {string} text - The text to chunk.
|
|
2146
|
+
* @param {number} maxLength - Maximum chunk length in characters (a
|
|
2147
|
+
* positive integer; sent as the API's `max_length`).
|
|
2148
|
+
* @param {Object} [options]
|
|
2149
|
+
* @param {number} [options.step_size] - Window, in characters, searched
|
|
2150
|
+
* backwards from each `maxLength` cut for a split point. Larger windows
|
|
2151
|
+
* find preferred split points more often but vary chunk sizes more.
|
|
2152
|
+
* Defaults to 10% of the text length.
|
|
2153
|
+
* @returns {Promise<AxiosResponse>}
|
|
2154
|
+
*/
|
|
2155
|
+
chunkText(text, maxLength, options = {}) {
|
|
2156
|
+
if (typeof text !== 'string') {
|
|
2157
|
+
throw new TypeError('chunkText: text must be a string.');
|
|
2158
|
+
}
|
|
2159
|
+
const data = chunkBody(this.apiKey, maxLength, options, 'chunkText');
|
|
2160
|
+
data.text = text;
|
|
2161
|
+
return this.axiosPromise(`${this.urlEndpoint}ingest/chunk_text`, 'post', data);
|
|
2162
|
+
}
|
|
2163
|
+
|
|
2164
|
+
/**
|
|
2165
|
+
* Extract the text of an HTML document and split it like chunkText(). Tags
|
|
2166
|
+
* are discarded, so the chunks are plain text. Unbilled.
|
|
2167
|
+
*
|
|
2168
|
+
* @param {string} html - The HTML to extract text from and chunk.
|
|
2169
|
+
* @param {number} maxLength - Maximum chunk length in characters.
|
|
2170
|
+
* @param {Object} [options]
|
|
2171
|
+
* @param {number} [options.step_size] - See chunkText().
|
|
2172
|
+
* @returns {Promise<AxiosResponse>}
|
|
2173
|
+
*/
|
|
2174
|
+
chunkHtml(html, maxLength, options = {}) {
|
|
2175
|
+
if (typeof html !== 'string') {
|
|
2176
|
+
throw new TypeError('chunkHtml: html must be a string.');
|
|
2177
|
+
}
|
|
2178
|
+
const data = chunkBody(this.apiKey, maxLength, options, 'chunkHtml');
|
|
2179
|
+
data.html = html;
|
|
2180
|
+
return this.axiosPromise(`${this.urlEndpoint}ingest/chunk_html`, 'post', data);
|
|
2181
|
+
}
|
|
2182
|
+
|
|
2183
|
+
/**
|
|
2184
|
+
* Extract the plain text of an uploaded PDF (POST /api/v1/ingest/pdf_to_text).
|
|
2185
|
+
* Resolves to `{text}`. The API requires a `.pdf` filename; a password-protected,
|
|
2186
|
+
* empty or malformed file gets a 400 whose body is `{error}` (not `{msg}`).
|
|
2187
|
+
* Unbilled.
|
|
2188
|
+
*
|
|
2189
|
+
* @param {Blob|File|Buffer|Uint8Array|ArrayBuffer} file - The PDF.
|
|
2190
|
+
* @param {object} [options]
|
|
2191
|
+
* @param {string} [options.filename] - Required unless `file` is a File,
|
|
2192
|
+
* e.g. 'report.pdf'.
|
|
2193
|
+
* @returns {Promise<AxiosResponse>}
|
|
2194
|
+
*/
|
|
2195
|
+
pdfToText(file, options = {}) {
|
|
2196
|
+
const form = fileToTextForm(this, file, options, 'pdfToText');
|
|
2197
|
+
return this.axiosPromise(`${this.urlEndpoint}ingest/pdf_to_text`, 'post', form);
|
|
2198
|
+
}
|
|
2199
|
+
|
|
2200
|
+
/**
|
|
2201
|
+
* Extract the plain text of an uploaded Word document
|
|
2202
|
+
* (POST /api/v1/ingest/docx_to_text). Resolves to `{text}`, with blocks
|
|
2203
|
+
* (paragraphs, tables) separated by blank lines. The API requires a `.docx`
|
|
2204
|
+
* filename; a corrupt or macro-enabled file gets a 400 whose body is
|
|
2205
|
+
* `{error}` (not `{msg}`). Unbilled.
|
|
2206
|
+
*
|
|
2207
|
+
* @param {Blob|File|Buffer|Uint8Array|ArrayBuffer} file - The DOCX.
|
|
2208
|
+
* @param {object} [options]
|
|
2209
|
+
* @param {string} [options.filename] - Required unless `file` is a File,
|
|
2210
|
+
* e.g. 'notes.docx'.
|
|
2211
|
+
* @returns {Promise<AxiosResponse>}
|
|
2212
|
+
*/
|
|
2213
|
+
docxToText(file, options = {}) {
|
|
2214
|
+
const form = fileToTextForm(this, file, options, 'docxToText');
|
|
2215
|
+
return this.axiosPromise(`${this.urlEndpoint}ingest/docx_to_text`, 'post', form);
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2218
|
+
/**
|
|
2219
|
+
* Release and build identifiers of the deployment answering the request:
|
|
2220
|
+
* `version` (for example `'2026.6'`) and `build`, which is `'unknown'` when the
|
|
2221
|
+
* image carries no build number. Unbilled, and needs no API key and sends
|
|
2222
|
+
* none, so it can check which release a hostname is serving.
|
|
2223
|
+
*
|
|
2224
|
+
* @returns {Promise<AxiosResponse>}
|
|
2225
|
+
*/
|
|
2226
|
+
version() {
|
|
2227
|
+
const url = `${this.urlEndpoint}version`;
|
|
2228
|
+
// Not axiosPromise, and no key: the route is public, so neither the API key
|
|
2229
|
+
// nor the client-level return_usage opt-in has any reason to leave the client.
|
|
2230
|
+
return axios({
|
|
2231
|
+
url,
|
|
2232
|
+
method: 'get',
|
|
2233
|
+
timeout: this.timeout,
|
|
2234
|
+
});
|
|
2235
|
+
}
|
|
2236
|
+
|
|
2237
|
+
/**
|
|
2238
|
+
* Whether the API process is up: answers 200 with `{ msg: 'alive' }`. Like
|
|
2239
|
+
* version(), it's unbilled, needs no API key and sends none. It doesn't
|
|
2240
|
+
* check the services behind the API, so it can succeed during an outage of
|
|
2241
|
+
* one of them.
|
|
2242
|
+
*
|
|
2243
|
+
* @returns {Promise<AxiosResponse>}
|
|
2244
|
+
*/
|
|
2245
|
+
liveness() {
|
|
2246
|
+
const url = `${this.urlEndpoint}liveness`;
|
|
2247
|
+
// Not axiosPromise, and no key: see version().
|
|
2248
|
+
return axios({
|
|
2249
|
+
url,
|
|
2250
|
+
method: 'get',
|
|
2251
|
+
timeout: this.timeout,
|
|
2252
|
+
});
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2075
2255
|
/**
|
|
2076
2256
|
* Check whether an account-wide block (expired key/subscription,
|
|
2077
2257
|
* depleted credits, billing limit, trial daily allowance, monthly quota)
|