@saplingai/sapling-js 1.0.45 → 1.0.53
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 +158 -0
- package/package.json +1 -1
- package/sapling/client.d.ts +411 -12
- package/sapling/client.js +1009 -13
package/sapling/client.js
CHANGED
|
@@ -3,6 +3,76 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
3
3
|
|
|
4
4
|
// Value types an extract() field may declare; 'string' is the default.
|
|
5
5
|
const EXTRACT_FIELD_TYPES = ['string', 'number', 'integer', 'boolean', 'date', 'list'];
|
|
6
|
+
// Registers translate() accepts; the API rejects anything else with a 400.
|
|
7
|
+
const TRANSLATE_FORMALITIES = ['formal', 'informal'];
|
|
8
|
+
const SUMMARIZE_LENGTHS = ['short', 'medium', 'long'];
|
|
9
|
+
// Reading levels simplify() accepts; the API rejects anything else with a 400.
|
|
10
|
+
const SIMPLIFY_READING_LEVELS = ['plain', 'elementary', 'middle_school', 'high_school'];
|
|
11
|
+
// rephrase() enums, mirroring the API's validators (utils/request_validator.py),
|
|
12
|
+
// which reject anything else with a 400.
|
|
13
|
+
const REPHRASE_MAPPINGS = [
|
|
14
|
+
'paraphrase', 'passive_to_active', 'active_to_passive', 'informal_to_formal',
|
|
15
|
+
'formal_to_informal', 'sentence_split', 'reword', 'generate_title', 'generate_acronym',
|
|
16
|
+
'generate_meta_description', 'summarize', 'expand', 'shorten', 'switch_tone', 'switch_tense',
|
|
17
|
+
];
|
|
18
|
+
const REPHRASE_TONE_MAPPINGS = [
|
|
19
|
+
'straightforward', 'confident', 'friendly', 'empathetic', 'professional', 'casual',
|
|
20
|
+
'diplomatic', 'persuasive',
|
|
21
|
+
];
|
|
22
|
+
const REPHRASE_TENSE_MAPPINGS = [
|
|
23
|
+
'simple past', 'past perfect', 'past continuous', 'past perfect continuous',
|
|
24
|
+
'simple present', 'present perfect', 'present continuous', 'present perfect continuous',
|
|
25
|
+
'simple future', 'future perfect', 'future continuous', 'future perfect continuous',
|
|
26
|
+
];
|
|
27
|
+
// Languages statistics() scores readability for (plus 'auto').
|
|
28
|
+
const STATISTICS_LANGS = ['en', 'de', 'es', 'fr', 'it', 'nl', 'pl', 'ru', 'ar'];
|
|
29
|
+
|
|
30
|
+
// Shared by styleguide() and saveStyleguideRuleset(). Shape/type checks only —
|
|
31
|
+
// size limits (rule/description length) are server configuration and enforced
|
|
32
|
+
// there with a clear 400 (see classify()).
|
|
33
|
+
const validateStyleguideRules = (rules, method) => {
|
|
34
|
+
const isValidRule = (rule) => {
|
|
35
|
+
if (typeof rule === 'string') {
|
|
36
|
+
return rule.trim().length > 0;
|
|
37
|
+
}
|
|
38
|
+
if (rule == null || typeof rule !== 'object' || Array.isArray(rule)) {
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
if (typeof rule.name !== 'string' || rule.name.trim().length === 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (rule.description !== undefined && typeof rule.description !== 'string') {
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Reject unknown keys so a typo like `{ name, descrption: '...' }` fails
|
|
48
|
+
// here rather than being forwarded to the API as a malformed rule (the
|
|
49
|
+
// classify()/extract() convention; the MCP object schema is `.strict()`).
|
|
50
|
+
const allowed = new Set(['name', 'description']);
|
|
51
|
+
if (Object.keys(rule).some((key) => !allowed.has(key))) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
return true;
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
if (
|
|
58
|
+
!Array.isArray(rules)
|
|
59
|
+
|| rules.length < 1
|
|
60
|
+
|| rules.length > 20
|
|
61
|
+
// Array.from() materializes holes in sparse arrays as `undefined` (which
|
|
62
|
+
// isValidRule rejects); a bare `.every()` would skip them and let a hole
|
|
63
|
+
// through to be serialized as `null`.
|
|
64
|
+
|| !Array.from(rules).every(isValidRule)
|
|
65
|
+
) {
|
|
66
|
+
throw new TypeError(`${method}: rules must be an array of 1 to 20 non-empty strings or `
|
|
67
|
+
+ '{name, description?} objects (string name, optional string description).');
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const names = rules.map(
|
|
71
|
+
(rule) => (typeof rule === 'string' ? rule : rule.name).trim().toLowerCase());
|
|
72
|
+
if (names.length !== new Set(names).size) {
|
|
73
|
+
throw new TypeError(`${method}: rule names must be unique (case-insensitive).`);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
6
76
|
|
|
7
77
|
export class Client {
|
|
8
78
|
constructor(apiKey, options = {}) {
|
|
@@ -60,6 +130,11 @@ export class Client {
|
|
|
60
130
|
data.advanced_edits = options.advancedEdits;
|
|
61
131
|
}
|
|
62
132
|
|
|
133
|
+
// Omitted unless set; an explicit null is forwarded so the API rejects it.
|
|
134
|
+
if (options.includeErrorCategories !== undefined) {
|
|
135
|
+
data.include_error_categories = options.includeErrorCategories;
|
|
136
|
+
}
|
|
137
|
+
|
|
63
138
|
if (userDetails) {
|
|
64
139
|
const { userId, anonUser } = userDetails;
|
|
65
140
|
data.user_id = userId;
|
|
@@ -180,6 +255,286 @@ export class Client {
|
|
|
180
255
|
return this.axiosPromise(url, 'post', data);
|
|
181
256
|
}
|
|
182
257
|
|
|
258
|
+
/**
|
|
259
|
+
* Rephrase / paraphrase: generate alternative phrasings of a text.
|
|
260
|
+
*
|
|
261
|
+
* Returns `results`: one `{hash, original, replacement, rephrase_type,
|
|
262
|
+
* model_version}` entry per alternative. HTML is stripped server-side.
|
|
263
|
+
*
|
|
264
|
+
* @param {string} text - The text to rephrase (the API caps its length).
|
|
265
|
+
* @param {Object} [options]
|
|
266
|
+
* @param {string} [options.mapping] - Transformation to apply (see
|
|
267
|
+
* REPHRASE_MAPPINGS). Defaults to 'paraphrase'.
|
|
268
|
+
* @param {string} [options.tone_mapping] - Target tone; only used with
|
|
269
|
+
* mapping 'switch_tone'. Defaults to 'straightforward'.
|
|
270
|
+
* @param {string} [options.tense_mapping] - Target tense; only used with
|
|
271
|
+
* mapping 'switch_tense'. Defaults to 'simple past'.
|
|
272
|
+
* @param {number} [options.num_results] - Number of alternatives, 1–8
|
|
273
|
+
* (default 5). Honored by the paraphrase, informal_to_formal,
|
|
274
|
+
* formal_to_informal, generate_title and generate_acronym mappings; other
|
|
275
|
+
* mappings return their default number of results.
|
|
276
|
+
* @param {string} [options.session_id] - Optional session identifier used to
|
|
277
|
+
* group feedback events.
|
|
278
|
+
* @returns {Promise<AxiosResponse>}
|
|
279
|
+
*/
|
|
280
|
+
rephrase(text, options = {}) {
|
|
281
|
+
const url = `${this.urlEndpoint}rephrase`;
|
|
282
|
+
|
|
283
|
+
// Shape/type/enum checks only; the length cap is server configuration and
|
|
284
|
+
// enforced there with a clear 400 (see seo()). The enums and the 1–8
|
|
285
|
+
// num_results bound mirror the API's validators, which reject anything else.
|
|
286
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
287
|
+
throw new TypeError('rephrase: text must be a non-empty string.');
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
let data = {
|
|
291
|
+
text,
|
|
292
|
+
key: this.apiKey,
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a bad
|
|
296
|
+
// type rather than silently treated as "omitted", matching classify()/seo().
|
|
297
|
+
if (options?.mapping !== undefined) {
|
|
298
|
+
if (!REPHRASE_MAPPINGS.includes(options.mapping)) {
|
|
299
|
+
throw new TypeError(`rephrase: mapping must be one of ${REPHRASE_MAPPINGS.join(', ')}.`);
|
|
300
|
+
}
|
|
301
|
+
data.mapping = options.mapping;
|
|
302
|
+
}
|
|
303
|
+
if (options?.tone_mapping !== undefined) {
|
|
304
|
+
if (!REPHRASE_TONE_MAPPINGS.includes(options.tone_mapping)) {
|
|
305
|
+
throw new TypeError(
|
|
306
|
+
`rephrase: tone_mapping must be one of ${REPHRASE_TONE_MAPPINGS.join(', ')}.`);
|
|
307
|
+
}
|
|
308
|
+
data.tone_mapping = options.tone_mapping;
|
|
309
|
+
}
|
|
310
|
+
if (options?.tense_mapping !== undefined) {
|
|
311
|
+
if (!REPHRASE_TENSE_MAPPINGS.includes(options.tense_mapping)) {
|
|
312
|
+
throw new TypeError(
|
|
313
|
+
`rephrase: tense_mapping must be one of ${REPHRASE_TENSE_MAPPINGS.join(', ')}.`);
|
|
314
|
+
}
|
|
315
|
+
data.tense_mapping = options.tense_mapping;
|
|
316
|
+
}
|
|
317
|
+
if (options?.num_results !== undefined) {
|
|
318
|
+
if (!Number.isInteger(options.num_results)
|
|
319
|
+
|| options.num_results < 1 || options.num_results > 8) {
|
|
320
|
+
throw new TypeError('rephrase: num_results must be an integer from 1 to 8.');
|
|
321
|
+
}
|
|
322
|
+
data.num_results = options.num_results;
|
|
323
|
+
}
|
|
324
|
+
if (options?.session_id !== undefined) {
|
|
325
|
+
if (typeof options.session_id !== 'string') {
|
|
326
|
+
throw new TypeError('rephrase: session_id must be a string.');
|
|
327
|
+
}
|
|
328
|
+
data.session_id = options.session_id;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return this.axiosPromise(url, 'post', data);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Sentiment analysis: positive / neutral / negative, overall and per sentence.
|
|
336
|
+
*
|
|
337
|
+
* Returns `sents` (the sentences found), `overall` (a list of
|
|
338
|
+
* `[probability, label]` pairs, most likely first) and `results` (one such
|
|
339
|
+
* list per sentence, aligned with `sents`). Labels are 'POSITIVE',
|
|
340
|
+
* 'NEUTRAL' or 'NEGATIVE'. A text with no sentences gets a 204 with no body
|
|
341
|
+
* (axios gives `data === ''`), and a text the API cannot split into
|
|
342
|
+
* sentences gets a 200 with `[]`.
|
|
343
|
+
*
|
|
344
|
+
* @param {string} text - The text to analyze, plain text or HTML (tags are
|
|
345
|
+
* stripped server-side; the API caps its length).
|
|
346
|
+
* @returns {Promise<AxiosResponse>}
|
|
347
|
+
*/
|
|
348
|
+
sentiment(text) {
|
|
349
|
+
const url = `${this.urlEndpoint}sentiment`;
|
|
350
|
+
|
|
351
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
352
|
+
throw new TypeError('sentiment: text must be a non-empty string.');
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
let data = {
|
|
356
|
+
text,
|
|
357
|
+
key: this.apiKey,
|
|
358
|
+
}
|
|
359
|
+
return this.axiosPromise(url, 'post', data);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/**
|
|
363
|
+
* Readability statistics: character / word / sentence counts, reading time
|
|
364
|
+
* and readability grades.
|
|
365
|
+
*
|
|
366
|
+
* Returns `chars`, `words`, `sentences`, `reading_time_sec`,
|
|
367
|
+
* `reading_time_min`, `readability` (a Flesch-Kincaid grade level, -3.4 when
|
|
368
|
+
* it cannot be scored) and `readability_scores` (a map of formula name to
|
|
369
|
+
* score; which formulas appear depends on `lang`, and it is empty for
|
|
370
|
+
* 'auto'). Unbilled.
|
|
371
|
+
*
|
|
372
|
+
* @param {string} text - The text to measure, plain text or HTML (tags are
|
|
373
|
+
* stripped server-side; the API caps it at 100,000 characters).
|
|
374
|
+
* @param {Object} [options]
|
|
375
|
+
* @param {string} [options.lang] - Language of the text: one of
|
|
376
|
+
* STATISTICS_LANGS ('en', 'de', 'es', 'fr', 'it', 'nl', 'pl', 'ru', 'ar')
|
|
377
|
+
* or 'auto'. Defaults to 'en'.
|
|
378
|
+
* @returns {Promise<AxiosResponse>}
|
|
379
|
+
*/
|
|
380
|
+
statistics(text, options = {}) {
|
|
381
|
+
const url = `${this.urlEndpoint}statistics`;
|
|
382
|
+
|
|
383
|
+
// An empty text is a valid request (all-zero counts), so only the type is
|
|
384
|
+
// checked here.
|
|
385
|
+
if (typeof text !== 'string') {
|
|
386
|
+
throw new TypeError('statistics: text must be a string.');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
let data = {
|
|
390
|
+
text,
|
|
391
|
+
key: this.apiKey,
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (options?.lang !== undefined) {
|
|
395
|
+
if (options.lang !== 'auto' && !STATISTICS_LANGS.includes(options.lang)) {
|
|
396
|
+
throw new TypeError(
|
|
397
|
+
`statistics: lang must be 'auto' or one of ${STATISTICS_LANGS.join(', ')}.`);
|
|
398
|
+
}
|
|
399
|
+
data.lang = options.lang;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
return this.axiosPromise(url, 'post', data);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Similar terms: thesaurus synonyms plus semantically similar terms for a
|
|
407
|
+
* word or short phrase.
|
|
408
|
+
*
|
|
409
|
+
* Returns `neighbors` (up to 5 `[similarity, term]` pairs from embedding
|
|
410
|
+
* similarity, similarity 0–1, most similar first; only alphabetic terms)
|
|
411
|
+
* and `synonyms` (WordNet lemma names — multi-word ones use underscores,
|
|
412
|
+
* e.g. 'domestic_dog'). Unbilled.
|
|
413
|
+
*
|
|
414
|
+
* @param {string} query - The word or phrase to look up (the API uses only
|
|
415
|
+
* its first 100 characters).
|
|
416
|
+
* @returns {Promise<AxiosResponse>}
|
|
417
|
+
*/
|
|
418
|
+
similarTerms(query) {
|
|
419
|
+
const url = `${this.urlEndpoint}thesaurus`;
|
|
420
|
+
|
|
421
|
+
// The API answers a blank query with a 400.
|
|
422
|
+
if (typeof query !== 'string' || query.trim().length === 0) {
|
|
423
|
+
throw new TypeError('similarTerms: query must be a non-empty string.');
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
let data = {
|
|
427
|
+
query,
|
|
428
|
+
key: this.apiKey,
|
|
429
|
+
}
|
|
430
|
+
return this.axiosPromise(url, 'post', data);
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
/**
|
|
434
|
+
* Profanity filter: label each whitespace-separated token of a text.
|
|
435
|
+
*
|
|
436
|
+
* Returns `toks` (the text split on whitespace; punctuation stays attached,
|
|
437
|
+
* e.g. 'stupid.') and `labels` (aligned with `toks`: 1 for profanity, 0
|
|
438
|
+
* otherwise). The text is not HTML-stripped. Unbilled.
|
|
439
|
+
*
|
|
440
|
+
* @param {string} text - The text to check.
|
|
441
|
+
* @returns {Promise<AxiosResponse>}
|
|
442
|
+
*/
|
|
443
|
+
profanity(text) {
|
|
444
|
+
const url = `${this.urlEndpoint}profanity`;
|
|
445
|
+
|
|
446
|
+
if (typeof text !== 'string' || text.trim().length === 0) {
|
|
447
|
+
throw new TypeError('profanity: text must be a non-empty string.');
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
let data = {
|
|
451
|
+
text,
|
|
452
|
+
key: this.apiKey,
|
|
453
|
+
}
|
|
454
|
+
return this.axiosPromise(url, 'post', data);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* List the custom dictionary of this API key's account: words the grammar
|
|
459
|
+
* and spelling checks accept as correct.
|
|
460
|
+
*
|
|
461
|
+
* Returns a bare array of `{id, entry, case_sensitive, created_at,
|
|
462
|
+
* updated_at}` entries, sorted by entry (timestamps are RFC 1123 strings).
|
|
463
|
+
*
|
|
464
|
+
* @returns {Promise<AxiosResponse>}
|
|
465
|
+
*/
|
|
466
|
+
dictionaryEntries() {
|
|
467
|
+
const url = `${this.urlEndpoint}dictionary`;
|
|
468
|
+
// Not axiosPromise: this is a GET and the key rides the query string,
|
|
469
|
+
// like styleguideRulesets().
|
|
470
|
+
return axios({
|
|
471
|
+
url,
|
|
472
|
+
method: 'get',
|
|
473
|
+
params: { key: this.apiKey },
|
|
474
|
+
timeout: this.timeout,
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Add a word to this API key's account dictionary so the grammar and
|
|
480
|
+
* spelling checks stop flagging it (product names, jargon...).
|
|
481
|
+
*
|
|
482
|
+
* Returns the created `{id, entry, case_sensitive, created_at, updated_at}`
|
|
483
|
+
* entry. The API answers 400 for a duplicate entry ("Entry already
|
|
484
|
+
* exists.") and for an entry with more than two digits.
|
|
485
|
+
*
|
|
486
|
+
* @param {string} entry - The word to add (surrounding whitespace is
|
|
487
|
+
* stripped server-side).
|
|
488
|
+
* @param {Object} [options]
|
|
489
|
+
* @param {boolean} [options.case_sensitive] - Whether matching is case
|
|
490
|
+
* sensitive. Defaults to true.
|
|
491
|
+
* @returns {Promise<AxiosResponse>}
|
|
492
|
+
*/
|
|
493
|
+
addDictionaryEntry(entry, options = {}) {
|
|
494
|
+
const url = `${this.urlEndpoint}dictionary`;
|
|
495
|
+
|
|
496
|
+
if (typeof entry !== 'string' || entry.trim().length === 0) {
|
|
497
|
+
throw new TypeError('addDictionaryEntry: entry must be a non-empty string.');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
let data = {
|
|
501
|
+
entry,
|
|
502
|
+
key: this.apiKey,
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (options?.case_sensitive !== undefined) {
|
|
506
|
+
if (typeof options.case_sensitive !== 'boolean') {
|
|
507
|
+
throw new TypeError('addDictionaryEntry: case_sensitive must be a boolean.');
|
|
508
|
+
}
|
|
509
|
+
data.case_sensitive = options.case_sensitive;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return this.axiosPromise(url, 'post', data);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
/**
|
|
516
|
+
* Delete a custom dictionary entry by the `id` dictionaryEntries() or
|
|
517
|
+
* addDictionaryEntry() returned. Answers 204 with no body; an id that is
|
|
518
|
+
* malformed or not this account's gets a 400 (not a 404).
|
|
519
|
+
*
|
|
520
|
+
* @param {string} entryId - The entry's id (a UUID).
|
|
521
|
+
* @returns {Promise<AxiosResponse>}
|
|
522
|
+
*/
|
|
523
|
+
deleteDictionaryEntry(entryId) {
|
|
524
|
+
if (typeof entryId !== 'string' || entryId.trim().length === 0) {
|
|
525
|
+
throw new TypeError('deleteDictionaryEntry: entryId must be a non-empty string.');
|
|
526
|
+
}
|
|
527
|
+
const url = `${this.urlEndpoint}dictionary/${encodeURIComponent(entryId)}`;
|
|
528
|
+
// The key rides the query string, like deleteStyleguideRuleset(): some
|
|
529
|
+
// HTTP stacks drop DELETE bodies.
|
|
530
|
+
return axios({
|
|
531
|
+
url,
|
|
532
|
+
method: 'delete',
|
|
533
|
+
params: { key: this.apiKey },
|
|
534
|
+
timeout: this.timeout,
|
|
535
|
+
});
|
|
536
|
+
}
|
|
537
|
+
|
|
183
538
|
/**
|
|
184
539
|
* Structured data extraction: pull caller-named fields out of unstructured text.
|
|
185
540
|
*
|
|
@@ -195,8 +550,11 @@ export class Client {
|
|
|
195
550
|
* from the text, so this is extraction, not inference: a value that isn't stated is
|
|
196
551
|
* reported missing rather than guessed. Finding nothing is a normal 200.
|
|
197
552
|
*
|
|
198
|
-
* @param {string} text - The document to extract from, plain text or HTML (the
|
|
199
|
-
* it at 10,000 characters).
|
|
553
|
+
* @param {string|string[]} text - The document to extract from, plain text or HTML (the
|
|
554
|
+
* API caps it at 10,000 characters). Pass an array of 1–10 strings to extract from a
|
|
555
|
+
* batch in one request (sent as the API's `texts` parameter; combined length capped at
|
|
556
|
+
* 10,000 chars) — the response is then `{results: [...]}` with one
|
|
557
|
+
* single-response-shaped entry per input, in order.
|
|
200
558
|
* @param {Array<string|{name: string, type?: string, description?: string, required?: boolean}>} fields -
|
|
201
559
|
* The fields to pull out (1–20): field names, or objects. `type` is one of 'string'
|
|
202
560
|
* (default), 'number', 'integer', 'boolean', 'date' (returned as YYYY-MM-DD) or 'list'.
|
|
@@ -215,7 +573,20 @@ export class Client {
|
|
|
215
573
|
// context length) are server configuration and enforced there with a clear
|
|
216
574
|
// 400 — mirroring them here would drift the day they change (and JS
|
|
217
575
|
// `length` counts UTF-16 units, not the characters the API counts).
|
|
218
|
-
|
|
576
|
+
// An array of texts is the batch form (sent as `texts`, one result per
|
|
577
|
+
// item); the 1–10 item bound is structural like the field count below.
|
|
578
|
+
const isBatch = Array.isArray(text);
|
|
579
|
+
if (isBatch) {
|
|
580
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
581
|
+
// undefined and rejected, rather than skipped.
|
|
582
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
583
|
+
for (const item of text) {
|
|
584
|
+
ok = ok && typeof item === 'string' && item.trim().length > 0;
|
|
585
|
+
}
|
|
586
|
+
if (!ok) {
|
|
587
|
+
throw new TypeError('extract: texts must be an array of 1 to 10 non-empty strings.');
|
|
588
|
+
}
|
|
589
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
219
590
|
throw new TypeError('extract: text must be a non-empty string.');
|
|
220
591
|
}
|
|
221
592
|
|
|
@@ -268,7 +639,7 @@ export class Client {
|
|
|
268
639
|
}
|
|
269
640
|
|
|
270
641
|
let data = {
|
|
271
|
-
text,
|
|
642
|
+
...(isBatch ? { texts: text } : { text }),
|
|
272
643
|
fields,
|
|
273
644
|
key: this.apiKey,
|
|
274
645
|
}
|
|
@@ -286,11 +657,182 @@ export class Client {
|
|
|
286
657
|
return this.axiosPromise(url, 'post', data);
|
|
287
658
|
}
|
|
288
659
|
|
|
660
|
+
/**
|
|
661
|
+
* Translate text into a target language with an LLM.
|
|
662
|
+
*
|
|
663
|
+
* Line breaks, markup and placeholders are preserved in place, and the response
|
|
664
|
+
* reports the language the text was actually written in (detected — never assumed
|
|
665
|
+
* from the hint): `translation`, `source_lang`/`source_lang_name` (`'und'`/`null`
|
|
666
|
+
* when undetermined) and the normalized `target_lang`/`target_lang_name`.
|
|
667
|
+
*
|
|
668
|
+
* @param {string|string[]} text - The text to translate (the API caps it at
|
|
669
|
+
* 5,000 characters). Markup and whitespace are preserved, not stripped.
|
|
670
|
+
* Pass an array of 1-10 strings to translate a batch in one request (sent
|
|
671
|
+
* as the API's `texts` parameter; combined length up to 5,000 characters,
|
|
672
|
+
* same target/source/formality applied to every item). A batch response is
|
|
673
|
+
* `{results: [...]}` with one single-response-shaped entry per input, in
|
|
674
|
+
* order.
|
|
675
|
+
* @param {string} targetLang - Language to translate into: an ISO 639 code
|
|
676
|
+
* ('fr', 'zh-TW') or an English language name ('French'). Region variants
|
|
677
|
+
* zh-CN/zh-TW/zh-HK, pt-BR/pt-PT, en-GB/en-US, fr-CA and es-419 are supported
|
|
678
|
+
* as targets.
|
|
679
|
+
* @param {Object} [options]
|
|
680
|
+
* @param {string} [options.source_lang] - Optional hint for the source language,
|
|
681
|
+
* same forms as targetLang. The text itself wins if they disagree.
|
|
682
|
+
* @param {string} [options.formality] - Optional register: 'formal'
|
|
683
|
+
* (vous/Sie/usted) or 'informal' (tu/du/tú). Omit for the model default.
|
|
684
|
+
* @returns {Promise<AxiosResponse>}
|
|
685
|
+
*/
|
|
686
|
+
translate(text, targetLang, options = {}) {
|
|
687
|
+
const url = `${this.urlEndpoint}translate`;
|
|
688
|
+
|
|
689
|
+
// Shape/type checks only; the language vocabulary and length caps are
|
|
690
|
+
// server configuration enforced there with a clear 400 (mirroring the
|
|
691
|
+
// 176-language table here would drift the day it changes).
|
|
692
|
+
// An array of texts is the batch form (SAP-398, sent as `texts`, one
|
|
693
|
+
// result per item); the 1–10 item bound is structural like safety's.
|
|
694
|
+
const isBatch = Array.isArray(text);
|
|
695
|
+
if (isBatch) {
|
|
696
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
697
|
+
// undefined and rejected, rather than skipped.
|
|
698
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
699
|
+
if (ok) {
|
|
700
|
+
for (const item of text) {
|
|
701
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
702
|
+
ok = false;
|
|
703
|
+
break;
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
if (!ok) {
|
|
708
|
+
throw new TypeError('translate: texts must be an array of 1 to 10 non-empty strings.');
|
|
709
|
+
}
|
|
710
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
711
|
+
throw new TypeError('translate: text must be a non-empty string.');
|
|
712
|
+
}
|
|
713
|
+
if (typeof targetLang !== 'string' || targetLang.trim().length === 0) {
|
|
714
|
+
throw new TypeError('translate: targetLang must be a non-empty string (an ISO 639 '
|
|
715
|
+
+ 'code like "fr" or an English language name like "French").');
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
let data = {
|
|
719
|
+
...(isBatch ? { texts: text } : { text }),
|
|
720
|
+
target_lang: targetLang,
|
|
721
|
+
key: this.apiKey,
|
|
722
|
+
}
|
|
723
|
+
|
|
724
|
+
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a
|
|
725
|
+
// bad type rather than silently treated as "omitted" (same as extract's
|
|
726
|
+
// context handling).
|
|
727
|
+
if (options?.source_lang !== undefined) {
|
|
728
|
+
if (typeof options.source_lang !== 'string' || options.source_lang.trim().length === 0) {
|
|
729
|
+
throw new TypeError('translate: source_lang must be a non-empty string.');
|
|
730
|
+
}
|
|
731
|
+
data.source_lang = options.source_lang;
|
|
732
|
+
}
|
|
733
|
+
if (options?.formality !== undefined) {
|
|
734
|
+
if (!TRANSLATE_FORMALITIES.includes(options.formality)) {
|
|
735
|
+
throw new TypeError('translate: formality must be one of '
|
|
736
|
+
+ `${TRANSLATE_FORMALITIES.join(', ')}.`);
|
|
737
|
+
}
|
|
738
|
+
data.formality = options.formality;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
return this.axiosPromise(url, 'post', data);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Summarize a document, webpage or email thread into a prose summary plus
|
|
746
|
+
* key points.
|
|
747
|
+
*
|
|
748
|
+
* Returns `summary` (the prose summary), `key_points` (an array of strings),
|
|
749
|
+
* `length` (the option the summary was generated with) and `result`
|
|
750
|
+
* (mirrors `summary` — the endpoint's original response key, kept for
|
|
751
|
+
* backward compatibility).
|
|
752
|
+
*
|
|
753
|
+
* @param {string|string[]} text - The document to summarize, plain text or
|
|
754
|
+
* HTML (tags are stripped; the API caps it at 20,000 characters). Pass an
|
|
755
|
+
* array of 1-10 strings to summarize a batch in one request (sent as the
|
|
756
|
+
* API's `texts` parameter; combined length up to 20,000 characters, same
|
|
757
|
+
* length option applied to every item). A batch response is
|
|
758
|
+
* `{results: [...]}` with one single-response-shaped entry per input, in
|
|
759
|
+
* order.
|
|
760
|
+
* @param {Object} [options]
|
|
761
|
+
* @param {string} [options.length] - Optional summary length: 'short',
|
|
762
|
+
* 'medium' (the default) or 'long'.
|
|
763
|
+
* @returns {Promise<AxiosResponse>}
|
|
764
|
+
*/
|
|
765
|
+
summarize(text, options = {}) {
|
|
766
|
+
const url = `${this.urlEndpoint}summarize`;
|
|
767
|
+
|
|
768
|
+
// An array of texts is the batch form (SAP-399, sent as `texts`, one
|
|
769
|
+
// result per item); the 1–10 item bound is structural like translate's.
|
|
770
|
+
const isBatch = Array.isArray(text);
|
|
771
|
+
if (isBatch) {
|
|
772
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
773
|
+
// undefined and rejected, rather than skipped.
|
|
774
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
775
|
+
if (ok) {
|
|
776
|
+
for (const item of text) {
|
|
777
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
778
|
+
ok = false;
|
|
779
|
+
break;
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
if (!ok) {
|
|
784
|
+
throw new TypeError('summarize: texts must be an array of 1 to 10 non-empty strings.');
|
|
785
|
+
}
|
|
786
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
787
|
+
throw new TypeError('summarize: text must be a non-empty string.');
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
let data = {
|
|
791
|
+
...(isBatch ? { texts: text } : { text }),
|
|
792
|
+
key: this.apiKey,
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a
|
|
796
|
+
// bad type rather than silently treated as "omitted" (same as translate's
|
|
797
|
+
// formality handling).
|
|
798
|
+
if (options?.length !== undefined) {
|
|
799
|
+
if (!SUMMARIZE_LENGTHS.includes(options.length)) {
|
|
800
|
+
throw new TypeError('summarize: length must be one of '
|
|
801
|
+
+ `${SUMMARIZE_LENGTHS.join(', ')}.`);
|
|
802
|
+
}
|
|
803
|
+
data.length = options.length;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return this.axiosPromise(url, 'post', data);
|
|
807
|
+
}
|
|
808
|
+
|
|
289
809
|
langdetect(text, options = {}) {
|
|
290
810
|
const url = `${this.urlEndpoint}langdetect`;
|
|
291
811
|
|
|
812
|
+
// An array of texts is the batch form (SAP-397, sent as `texts`, one
|
|
813
|
+
// result per item); the 1–10 item bound is structural like classify's.
|
|
814
|
+
// The single (string) form is passed through unvalidated, as it always
|
|
815
|
+
// has been — the server answers a clear 400.
|
|
816
|
+
const isBatch = Array.isArray(text);
|
|
817
|
+
if (isBatch) {
|
|
818
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
819
|
+
// undefined and rejected, rather than skipped.
|
|
820
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
821
|
+
if (ok) {
|
|
822
|
+
for (const item of text) {
|
|
823
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
824
|
+
ok = false;
|
|
825
|
+
break;
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
if (!ok) {
|
|
830
|
+
throw new TypeError('langdetect: texts must be an array of 1 to 10 non-empty strings.');
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
292
834
|
let data = {
|
|
293
|
-
text,
|
|
835
|
+
...(isBatch ? { texts: text } : { text }),
|
|
294
836
|
key: this.apiKey,
|
|
295
837
|
}
|
|
296
838
|
|
|
@@ -314,7 +856,11 @@ export class Client {
|
|
|
314
856
|
* empty), `scores` (one `{label, score}` per input label, descending), a one-sentence
|
|
315
857
|
* `rationale` and the echoed `multi_label` mode.
|
|
316
858
|
*
|
|
317
|
-
* @param {string} text - Text to classify, plain text or HTML (the API caps it
|
|
859
|
+
* @param {string|string[]} text - Text to classify, plain text or HTML (the API caps it
|
|
860
|
+
* at 10,000 chars). Pass an array of 1–10 strings to classify a batch in one request
|
|
861
|
+
* (sent as the API's `texts` parameter; combined length capped at 10,000 chars) — the
|
|
862
|
+
* response is then `{results: [...]}` with one single-response-shaped entry per input,
|
|
863
|
+
* in order.
|
|
318
864
|
* @param {Array<string|{name: string, description?: string}>} labels - Candidate labels
|
|
319
865
|
* (2–20). Names unique case-insensitively; the API caps names at 50 and descriptions at 200 chars.
|
|
320
866
|
* @param {Object} [options]
|
|
@@ -333,7 +879,20 @@ export class Client {
|
|
|
333
879
|
// context length) are server configuration and enforced there with a
|
|
334
880
|
// clear 400 — mirroring them here would drift the day they change (and
|
|
335
881
|
// JS `length` counts UTF-16 units, not the characters the API counts).
|
|
336
|
-
|
|
882
|
+
// An array of texts is the batch form (sent as `texts`, one result per
|
|
883
|
+
// item); the 1–10 item bound is structural like the label count below.
|
|
884
|
+
const isBatch = Array.isArray(text);
|
|
885
|
+
if (isBatch) {
|
|
886
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
887
|
+
// undefined and rejected, rather than skipped.
|
|
888
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
889
|
+
for (const item of text) {
|
|
890
|
+
ok = ok && typeof item === 'string' && item.trim().length > 0;
|
|
891
|
+
}
|
|
892
|
+
if (!ok) {
|
|
893
|
+
throw new TypeError('classify: texts must be an array of 1 to 10 non-empty strings.');
|
|
894
|
+
}
|
|
895
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
337
896
|
throw new TypeError('classify: text must be a non-empty string.');
|
|
338
897
|
}
|
|
339
898
|
|
|
@@ -379,7 +938,7 @@ export class Client {
|
|
|
379
938
|
}
|
|
380
939
|
|
|
381
940
|
let data = {
|
|
382
|
-
text,
|
|
941
|
+
...(isBatch ? { texts: text } : { text }),
|
|
383
942
|
labels,
|
|
384
943
|
key: this.apiKey,
|
|
385
944
|
}
|
|
@@ -477,12 +1036,30 @@ export class Client {
|
|
|
477
1036
|
const url = `${this.urlEndpoint}quality`;
|
|
478
1037
|
|
|
479
1038
|
// Shape/type checks only; size limits are server configuration (see seo()).
|
|
480
|
-
|
|
1039
|
+
// An array of texts is the batch form (SAP-400, sent as `texts`, one
|
|
1040
|
+
// result per item); the 1–10 item bound is structural like classify's.
|
|
1041
|
+
const isBatch = Array.isArray(text);
|
|
1042
|
+
if (isBatch) {
|
|
1043
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
1044
|
+
// undefined and rejected, rather than skipped.
|
|
1045
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
1046
|
+
if (ok) {
|
|
1047
|
+
for (const item of text) {
|
|
1048
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
1049
|
+
ok = false;
|
|
1050
|
+
break;
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
}
|
|
1054
|
+
if (!ok) {
|
|
1055
|
+
throw new TypeError('quality: texts must be an array of 1 to 10 non-empty strings.');
|
|
1056
|
+
}
|
|
1057
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
481
1058
|
throw new TypeError('quality: text must be a non-empty string.');
|
|
482
1059
|
}
|
|
483
1060
|
|
|
484
1061
|
let data = {
|
|
485
|
-
text,
|
|
1062
|
+
...(isBatch ? { texts: text } : { text }),
|
|
486
1063
|
key: this.apiKey,
|
|
487
1064
|
}
|
|
488
1065
|
|
|
@@ -508,12 +1085,30 @@ export class Client {
|
|
|
508
1085
|
const url = `${this.urlEndpoint}safety`;
|
|
509
1086
|
|
|
510
1087
|
// Shape/type checks only; size limits are server configuration (see seo()).
|
|
511
|
-
|
|
1088
|
+
// An array of texts is the batch form (SAP-397, sent as `texts`, one
|
|
1089
|
+
// result per item); the 1–10 item bound is structural like classify's.
|
|
1090
|
+
const isBatch = Array.isArray(text);
|
|
1091
|
+
if (isBatch) {
|
|
1092
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
1093
|
+
// undefined and rejected, rather than skipped.
|
|
1094
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
1095
|
+
if (ok) {
|
|
1096
|
+
for (const item of text) {
|
|
1097
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
1098
|
+
ok = false;
|
|
1099
|
+
break;
|
|
1100
|
+
}
|
|
1101
|
+
}
|
|
1102
|
+
}
|
|
1103
|
+
if (!ok) {
|
|
1104
|
+
throw new TypeError('safety: texts must be an array of 1 to 10 non-empty strings.');
|
|
1105
|
+
}
|
|
1106
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
512
1107
|
throw new TypeError('safety: text must be a non-empty string.');
|
|
513
1108
|
}
|
|
514
1109
|
|
|
515
1110
|
let data = {
|
|
516
|
-
text,
|
|
1111
|
+
...(isBatch ? { texts: text } : { text }),
|
|
517
1112
|
key: this.apiKey,
|
|
518
1113
|
}
|
|
519
1114
|
|
|
@@ -526,6 +1121,12 @@ export class Client {
|
|
|
526
1121
|
}
|
|
527
1122
|
data.threshold = options.threshold;
|
|
528
1123
|
}
|
|
1124
|
+
if (options?.spans !== undefined) {
|
|
1125
|
+
if (typeof options.spans !== 'boolean') {
|
|
1126
|
+
throw new TypeError('safety: spans must be a boolean.');
|
|
1127
|
+
}
|
|
1128
|
+
data.spans = options.spans;
|
|
1129
|
+
}
|
|
529
1130
|
|
|
530
1131
|
return this.axiosPromise(url, 'post', data);
|
|
531
1132
|
}
|
|
@@ -533,14 +1134,307 @@ export class Client {
|
|
|
533
1134
|
aidetect(text, options = {}) {
|
|
534
1135
|
const url = `${this.urlEndpoint}aidetect`;
|
|
535
1136
|
|
|
1137
|
+
// An array of texts is the batch form (SAP-403, sent as `texts`, one
|
|
1138
|
+
// result per item); the 1–10 item bound is structural like classify's.
|
|
1139
|
+
// The single form keeps its historical no-validation behavior (the
|
|
1140
|
+
// server 400s clearly on a bad `text`).
|
|
1141
|
+
const isBatch = Array.isArray(text);
|
|
1142
|
+
if (isBatch) {
|
|
1143
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
1144
|
+
// undefined and rejected, rather than skipped.
|
|
1145
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
1146
|
+
if (ok) {
|
|
1147
|
+
for (const item of text) {
|
|
1148
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
1149
|
+
ok = false;
|
|
1150
|
+
break;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
if (!ok) {
|
|
1155
|
+
throw new TypeError('aidetect: texts must be an array of 1 to 10 non-empty strings.');
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
536
1159
|
let data = {
|
|
537
|
-
text,
|
|
1160
|
+
...(isBatch ? { texts: text } : { text }),
|
|
538
1161
|
key: this.apiKey,
|
|
539
1162
|
}
|
|
540
1163
|
|
|
541
1164
|
if (options.sent_scores != null) {
|
|
542
1165
|
data.sent_scores = options.sent_scores;
|
|
543
1166
|
}
|
|
1167
|
+
if (options.score_string !== undefined) {
|
|
1168
|
+
if (typeof options.score_string !== 'boolean') {
|
|
1169
|
+
throw new TypeError('aidetect: score_string must be a boolean.');
|
|
1170
|
+
}
|
|
1171
|
+
data.score_string = options.score_string;
|
|
1172
|
+
}
|
|
1173
|
+
if (options.version !== undefined) {
|
|
1174
|
+
if (typeof options.version !== 'string') {
|
|
1175
|
+
throw new TypeError('aidetect: version must be a string.');
|
|
1176
|
+
}
|
|
1177
|
+
data.version = options.version;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
return this.axiosPromise(url, 'post', data);
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
/**
|
|
1184
|
+
* Check a text for compliance with style rules you supply (house style,
|
|
1185
|
+
* brand voice, editorial guidelines). Only the listed rules are enforced.
|
|
1186
|
+
*
|
|
1187
|
+
* Returns `violations` (one `{rule, text, start, end, note, suggestion}`
|
|
1188
|
+
* entry per violation, most important first — `start`/`end` index the
|
|
1189
|
+
* submitted text and are null when the passage cannot be located), `rules`
|
|
1190
|
+
* (the checked rule names) and `compliant` (true when no violations were
|
|
1191
|
+
* found). The text is checked exactly as submitted — markup is NOT
|
|
1192
|
+
* stripped, so offsets always index the string you sent.
|
|
1193
|
+
*
|
|
1194
|
+
* @param {string|string[]} text - The text to check (the API caps it at
|
|
1195
|
+
* 10,000 characters), or an array of 1–10 texts for the batch form (sent
|
|
1196
|
+
* as the API's `texts` parameter; combined length up to 10,000
|
|
1197
|
+
* characters, same rules applied to every item). A batch response is
|
|
1198
|
+
* `{results: [...]}` with one single-response-shaped entry per input, in
|
|
1199
|
+
* order, each item's violation offsets indexing that item's own text.
|
|
1200
|
+
* @param {Array<string|{name: string, description?: string}>|string} rules
|
|
1201
|
+
* - 1 to 20 style rules: a rule name (max 80 chars) or a
|
|
1202
|
+
* `{name, description}` object whose optional description (max 400
|
|
1203
|
+
* chars) carries the nuance the model follows. Names must be unique
|
|
1204
|
+
* case-insensitively. OR a string: the name of a saved ruleset (see
|
|
1205
|
+
* saveStyleguideRuleset()) to check against, matched case-insensitively
|
|
1206
|
+
* within the API key's account.
|
|
1207
|
+
* @returns {Promise<AxiosResponse>}
|
|
1208
|
+
*/
|
|
1209
|
+
styleguide(text, rules) {
|
|
1210
|
+
const url = `${this.urlEndpoint}styleguide`;
|
|
1211
|
+
|
|
1212
|
+
// Shape/type checks only. Size limits (text/rule/description length) are
|
|
1213
|
+
// server configuration and enforced there with a clear 400 (see
|
|
1214
|
+
// classify()).
|
|
1215
|
+
// An array of texts is the batch form (SAP-412, sent as `texts`, one
|
|
1216
|
+
// result per item); the 1–10 item bound is structural like classify's.
|
|
1217
|
+
const isBatch = Array.isArray(text);
|
|
1218
|
+
if (isBatch) {
|
|
1219
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
1220
|
+
// undefined and rejected, rather than skipped.
|
|
1221
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
1222
|
+
if (ok) {
|
|
1223
|
+
for (const item of text) {
|
|
1224
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
1225
|
+
ok = false;
|
|
1226
|
+
break;
|
|
1227
|
+
}
|
|
1228
|
+
}
|
|
1229
|
+
}
|
|
1230
|
+
if (!ok) {
|
|
1231
|
+
throw new TypeError('styleguide: texts must be an array of 1 to 10 non-empty strings.');
|
|
1232
|
+
}
|
|
1233
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
1234
|
+
throw new TypeError('styleguide: text must be a non-empty string.');
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// A string names a saved ruleset (see saveStyleguideRuleset()) and is
|
|
1238
|
+
// sent as the API's `ruleset` parameter in place of inline `rules`.
|
|
1239
|
+
const isSavedRuleset = typeof rules === 'string';
|
|
1240
|
+
if (isSavedRuleset) {
|
|
1241
|
+
if (rules.trim().length === 0) {
|
|
1242
|
+
throw new TypeError('styleguide: ruleset name must be a non-empty string.');
|
|
1243
|
+
}
|
|
1244
|
+
} else {
|
|
1245
|
+
validateStyleguideRules(rules, 'styleguide');
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
let data = {
|
|
1249
|
+
...(isBatch ? { texts: text } : { text }),
|
|
1250
|
+
...(isSavedRuleset ? { ruleset: rules } : { rules }),
|
|
1251
|
+
key: this.apiKey,
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
return this.axiosPromise(url, 'post', data);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
/**
|
|
1258
|
+
* List the saved style-guide rulesets of this API key's account (its team,
|
|
1259
|
+
* for team-scoped keys — a team's rulesets are shared by every caller of
|
|
1260
|
+
* its keys). Requires a per-account API key.
|
|
1261
|
+
*
|
|
1262
|
+
* Returns `rulesets`: `{name, rules, created_at, updated_at}` entries whose
|
|
1263
|
+
* `rules` are the normalized `{name, description}` objects the check
|
|
1264
|
+
* endpoint applies.
|
|
1265
|
+
*
|
|
1266
|
+
* @returns {Promise<AxiosResponse>}
|
|
1267
|
+
*/
|
|
1268
|
+
styleguideRulesets() {
|
|
1269
|
+
const url = `${this.urlEndpoint}styleguide/rulesets`;
|
|
1270
|
+
// Not axiosPromise: this is a GET and the key rides the query string.
|
|
1271
|
+
return axios({
|
|
1272
|
+
url,
|
|
1273
|
+
method: 'get',
|
|
1274
|
+
params: { key: this.apiKey },
|
|
1275
|
+
timeout: this.timeout,
|
|
1276
|
+
});
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
/**
|
|
1280
|
+
* Create — or replace, when the name already exists (matched
|
|
1281
|
+
* case-insensitively) — a saved style-guide ruleset, so later
|
|
1282
|
+
* `styleguide(text, name)` calls check against it by name. Owned by the
|
|
1283
|
+
* API key's account (its team, for team-scoped keys). Requires a
|
|
1284
|
+
* per-account API key.
|
|
1285
|
+
*
|
|
1286
|
+
* Returns `ruleset` (the saved `{name, rules, created_at, updated_at}`)
|
|
1287
|
+
* and `created` (false when an existing ruleset was replaced).
|
|
1288
|
+
*
|
|
1289
|
+
* @param {string} name - The ruleset name (the API caps it at 100
|
|
1290
|
+
* characters; surrounding/duplicate whitespace is normalized).
|
|
1291
|
+
* @param {Array<string|{name: string, description?: string}>} rules - The
|
|
1292
|
+
* rules to save; same shape and bounds as styleguide()'s inline rules.
|
|
1293
|
+
* @returns {Promise<AxiosResponse>}
|
|
1294
|
+
*/
|
|
1295
|
+
saveStyleguideRuleset(name, rules) {
|
|
1296
|
+
const url = `${this.urlEndpoint}styleguide/rulesets`;
|
|
1297
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
1298
|
+
throw new TypeError('saveStyleguideRuleset: name must be a non-empty string.');
|
|
1299
|
+
}
|
|
1300
|
+
validateStyleguideRules(rules, 'saveStyleguideRuleset');
|
|
1301
|
+
|
|
1302
|
+
let data = {
|
|
1303
|
+
name,
|
|
1304
|
+
rules,
|
|
1305
|
+
key: this.apiKey,
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
return this.axiosPromise(url, 'post', data);
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
/**
|
|
1312
|
+
* Delete a saved style-guide ruleset by name (matched case-insensitively;
|
|
1313
|
+
* 404 when no such ruleset exists). Requires a per-account API key.
|
|
1314
|
+
*
|
|
1315
|
+
* @param {string} name - The ruleset name to delete.
|
|
1316
|
+
* @returns {Promise<AxiosResponse>}
|
|
1317
|
+
*/
|
|
1318
|
+
deleteStyleguideRuleset(name) {
|
|
1319
|
+
const url = `${this.urlEndpoint}styleguide/rulesets`;
|
|
1320
|
+
if (typeof name !== 'string' || name.trim().length === 0) {
|
|
1321
|
+
throw new TypeError('deleteStyleguideRuleset: name must be a non-empty string.');
|
|
1322
|
+
}
|
|
1323
|
+
// The name rides the query string: some HTTP stacks drop DELETE bodies,
|
|
1324
|
+
// and the API accepts the query form for exactly that reason.
|
|
1325
|
+
return axios({
|
|
1326
|
+
url,
|
|
1327
|
+
method: 'delete',
|
|
1328
|
+
params: { key: this.apiKey, name },
|
|
1329
|
+
timeout: this.timeout,
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/**
|
|
1334
|
+
* Rewrite a text in plain language at a target reading level, keeping all
|
|
1335
|
+
* of its content, structure and language — nothing is summarized or
|
|
1336
|
+
* dropped.
|
|
1337
|
+
*
|
|
1338
|
+
* Returns `simplified` (the rewrite, structure preserved), the applied
|
|
1339
|
+
* `reading_level`, the `lang` the readability scores were computed for
|
|
1340
|
+
* (detected when not supplied) and, when that language supports scoring, a
|
|
1341
|
+
* `readability` block: `{before: {grade, ease}, after: {grade, ease}}`
|
|
1342
|
+
* Flesch-Kincaid scores, so the improvement is verifiable.
|
|
1343
|
+
*
|
|
1344
|
+
* @param {string|string[]} text - The text to simplify (the API caps it at
|
|
1345
|
+
* 5,000 characters), or an array of 1–10 texts for the batch form (sent
|
|
1346
|
+
* as the API's `texts` parameter; combined length up to 5,000
|
|
1347
|
+
* characters, same options applied to every item). A batch response is
|
|
1348
|
+
* `{results: [...]}` with one single-response-shaped entry per input, in
|
|
1349
|
+
* order; with `lang` omitted, each item's language (and readability
|
|
1350
|
+
* scores) is detected on its own. Sent to the model as submitted —
|
|
1351
|
+
* markup and line structure are preserved, not stripped.
|
|
1352
|
+
* @param {Object} [options]
|
|
1353
|
+
* @param {string} [options.reading_level] - Target audience: 'plain'
|
|
1354
|
+
* (plain-language style for a general audience, the API default),
|
|
1355
|
+
* 'elementary' (~US grade 3-5), 'middle_school' (~grade 6-8) or
|
|
1356
|
+
* 'high_school' (~grade 9-10).
|
|
1357
|
+
* @param {string[]} [options.preserve_terms] - Up to 20 terms (each up to
|
|
1358
|
+
* 100 characters) the rewrite must keep exactly as written — product
|
|
1359
|
+
* names, defined legal terms. A rewrite that drops one is refused
|
|
1360
|
+
* server-side rather than returned.
|
|
1361
|
+
* @param {string} [options.lang] - ISO 639-1 code used to pick the
|
|
1362
|
+
* readability formula for the before/after scores. Omit for
|
|
1363
|
+
* auto-detection (falls back to 'en'). The rewrite itself always stays
|
|
1364
|
+
* in the text's own language.
|
|
1365
|
+
* @returns {Promise<AxiosResponse>}
|
|
1366
|
+
*/
|
|
1367
|
+
simplify(text, options = {}) {
|
|
1368
|
+
const url = `${this.urlEndpoint}simplify`;
|
|
1369
|
+
|
|
1370
|
+
// Shape/type checks only. Size limits (text/term length) are server
|
|
1371
|
+
// configuration and enforced there with a clear 400 (see classify()).
|
|
1372
|
+
// An array of texts is the batch form (SAP-412, sent as `texts`, one
|
|
1373
|
+
// result per item); the 1–10 item bound is structural like classify's.
|
|
1374
|
+
const isBatch = Array.isArray(text);
|
|
1375
|
+
if (isBatch) {
|
|
1376
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
1377
|
+
// undefined and rejected, rather than skipped.
|
|
1378
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
1379
|
+
if (ok) {
|
|
1380
|
+
for (const item of text) {
|
|
1381
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
1382
|
+
ok = false;
|
|
1383
|
+
break;
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
if (!ok) {
|
|
1388
|
+
throw new TypeError('simplify: texts must be an array of 1 to 10 non-empty strings.');
|
|
1389
|
+
}
|
|
1390
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
1391
|
+
throw new TypeError('simplify: text must be a non-empty string.');
|
|
1392
|
+
}
|
|
1393
|
+
|
|
1394
|
+
let data = {
|
|
1395
|
+
...(isBatch ? { texts: text } : { text }),
|
|
1396
|
+
key: this.apiKey,
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1399
|
+
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a
|
|
1400
|
+
// bad type rather than silently treated as "omitted" (same as translate's
|
|
1401
|
+
// option handling).
|
|
1402
|
+
if (options?.reading_level !== undefined) {
|
|
1403
|
+
if (!SIMPLIFY_READING_LEVELS.includes(options.reading_level)) {
|
|
1404
|
+
throw new TypeError('simplify: reading_level must be one of '
|
|
1405
|
+
+ `${SIMPLIFY_READING_LEVELS.join(', ')}.`);
|
|
1406
|
+
}
|
|
1407
|
+
data.reading_level = options.reading_level;
|
|
1408
|
+
}
|
|
1409
|
+
if (options?.preserve_terms !== undefined) {
|
|
1410
|
+
const terms = options.preserve_terms;
|
|
1411
|
+
// An empty list is a valid no-op (callers may build it dynamically); it
|
|
1412
|
+
// is simply omitted from the payload below rather than rejected.
|
|
1413
|
+
// for...of (not terms.every) so holes in a sparse array are visited as
|
|
1414
|
+
// undefined and rejected, rather than skipped.
|
|
1415
|
+
let ok = Array.isArray(terms) && terms.length <= 20;
|
|
1416
|
+
if (ok) {
|
|
1417
|
+
for (const term of terms) {
|
|
1418
|
+
if (typeof term !== 'string' || term.trim().length === 0) {
|
|
1419
|
+
ok = false;
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
}
|
|
1424
|
+
if (!ok) {
|
|
1425
|
+
throw new TypeError('simplify: preserve_terms must be an array of up to 20 '
|
|
1426
|
+
+ 'non-empty strings.');
|
|
1427
|
+
}
|
|
1428
|
+
if (terms.length > 0) {
|
|
1429
|
+
data.preserve_terms = terms;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
if (options?.lang !== undefined) {
|
|
1433
|
+
if (typeof options.lang !== 'string' || options.lang.trim().length === 0) {
|
|
1434
|
+
throw new TypeError('simplify: lang must be a non-empty string.');
|
|
1435
|
+
}
|
|
1436
|
+
data.lang = options.lang;
|
|
1437
|
+
}
|
|
544
1438
|
|
|
545
1439
|
return this.axiosPromise(url, 'post', data);
|
|
546
1440
|
}
|
|
@@ -577,4 +1471,106 @@ export class Client {
|
|
|
577
1471
|
|
|
578
1472
|
return this.axiosPromise(url, 'post', data);
|
|
579
1473
|
}
|
|
1474
|
+
|
|
1475
|
+
/**
|
|
1476
|
+
* Named-entity recognition: people, organizations, locations, dates, times,
|
|
1477
|
+
* money amounts, percentages, quantities, products, and events, each with
|
|
1478
|
+
* character offsets into the exact text submitted (nothing is stripped or
|
|
1479
|
+
* normalized, so spans apply directly to your copy of the document).
|
|
1480
|
+
* Complements pii(): that finds checksum-validated identifiers, this finds
|
|
1481
|
+
* names and real-world references.
|
|
1482
|
+
*
|
|
1483
|
+
* @param {string|string[]} text - The text to analyze, or an array of 1–10
|
|
1484
|
+
* texts for the batch form (sent as the API's `texts` parameter; combined
|
|
1485
|
+
* length up to 10,000 characters, same types applied to every item). A
|
|
1486
|
+
* batch response is `{results: [...]}` with one single-response-shaped
|
|
1487
|
+
* entry per input, in order, each item's spans indexing that item's own
|
|
1488
|
+
* text.
|
|
1489
|
+
* @param {Object} [options]
|
|
1490
|
+
* @param {string[]} [options.types] - Entity types to recognize (subset of
|
|
1491
|
+
* the API's supported types). Default: all of them.
|
|
1492
|
+
*/
|
|
1493
|
+
ner(text, options = {}) {
|
|
1494
|
+
const url = `${this.urlEndpoint}ner`;
|
|
1495
|
+
|
|
1496
|
+
// Shape/type checks only; the set of valid entity types is server
|
|
1497
|
+
// configuration and enforced there with a clear 400 (see pii()).
|
|
1498
|
+
// An array of texts is the batch form (SAP-395 series, sent as `texts`,
|
|
1499
|
+
// one result per item); the 1–10 item bound is structural like classify's.
|
|
1500
|
+
const isBatch = Array.isArray(text);
|
|
1501
|
+
if (isBatch) {
|
|
1502
|
+
// for...of (not text.every) so holes in a sparse array are visited as
|
|
1503
|
+
// undefined and rejected, rather than skipped.
|
|
1504
|
+
let ok = text.length >= 1 && text.length <= 10;
|
|
1505
|
+
if (ok) {
|
|
1506
|
+
for (const item of text) {
|
|
1507
|
+
if (typeof item !== 'string' || item.trim().length === 0) {
|
|
1508
|
+
ok = false;
|
|
1509
|
+
break;
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
}
|
|
1513
|
+
if (!ok) {
|
|
1514
|
+
throw new TypeError('ner: texts must be an array of 1 to 10 non-empty strings.');
|
|
1515
|
+
}
|
|
1516
|
+
} else if (typeof text !== 'string' || text.trim().length === 0) {
|
|
1517
|
+
throw new TypeError('ner: text must be a non-empty string.');
|
|
1518
|
+
}
|
|
1519
|
+
|
|
1520
|
+
let data = {
|
|
1521
|
+
...(isBatch ? { texts: text } : { text }),
|
|
1522
|
+
key: this.apiKey,
|
|
1523
|
+
}
|
|
1524
|
+
|
|
1525
|
+
// `!== undefined` (not `!= null`) so an explicit `null` is rejected as a bad
|
|
1526
|
+
// type rather than silently treated as "omitted", matching pii().
|
|
1527
|
+
// Reject an empty list too: the MCP schema requires at least one type
|
|
1528
|
+
// (min(1)), so an empty array is typed-valid input that would otherwise
|
|
1529
|
+
// become an avoidable API error.
|
|
1530
|
+
if (options?.types !== undefined) {
|
|
1531
|
+
if (!Array.isArray(options.types) || options.types.length === 0
|
|
1532
|
+
|| !Array.from(options.types).every((type) => typeof type === 'string' && type.trim().length > 0)) {
|
|
1533
|
+
throw new TypeError('ner: types must be a non-empty array of non-empty strings.');
|
|
1534
|
+
}
|
|
1535
|
+
data.types = options.types;
|
|
1536
|
+
}
|
|
1537
|
+
|
|
1538
|
+
return this.axiosPromise(url, 'post', data);
|
|
1539
|
+
}
|
|
1540
|
+
|
|
1541
|
+
/**
|
|
1542
|
+
* Check whether an account-wide block (expired key/subscription,
|
|
1543
|
+
* depleted credits, billing limit, trial daily allowance, monthly quota)
|
|
1544
|
+
* stops this API key from making billable calls, and if so why and for
|
|
1545
|
+
* how long. Works while the key is blocked, which is when it is most
|
|
1546
|
+
* useful: call it after an unexpected 402 or monthly-quota 429 instead of
|
|
1547
|
+
* parsing the error text. It does not reflect a single endpoint's
|
|
1548
|
+
* short-term rate limit (each endpoint has its own bucket), so a
|
|
1549
|
+
* "Capacity used." 429 elsewhere can coexist with api_access: true — use
|
|
1550
|
+
* that 429's retry_after / X-RateLimit-* headers instead. Unbilled, but each call draws a fixed 2,000
|
|
1551
|
+
* characters from its own rate-limit bucket, so don't poll it per request.
|
|
1552
|
+
* Requires a per-account API key; the public demo key and MCP OAuth
|
|
1553
|
+
* access tokens get a 403.
|
|
1554
|
+
*
|
|
1555
|
+
* Always returns `api_access`, `blocked_reason` (null, or one of
|
|
1556
|
+
* key_expired, subscription_expired, credits_depleted,
|
|
1557
|
+
* monthly_billing_limit, trial_daily_limit, monthly_quota_exhausted),
|
|
1558
|
+
* `message` and `usage` ({monthly_quota_chars}); `retry_after` (seconds)
|
|
1559
|
+
* only when blocked. Keys that are not team-scoped also get
|
|
1560
|
+
* `usage.month_start`/`usage.chars_this_month`, `subscription`, `credits`
|
|
1561
|
+
* and, for free-trial accounts, `trial`.
|
|
1562
|
+
*
|
|
1563
|
+
* @returns {Promise<AxiosResponse>}
|
|
1564
|
+
*/
|
|
1565
|
+
accountStatus() {
|
|
1566
|
+
const url = `${this.urlEndpoint}account/status`;
|
|
1567
|
+
// Not axiosPromise: this is a GET and the key rides the query string,
|
|
1568
|
+
// like styleguideRulesets().
|
|
1569
|
+
return axios({
|
|
1570
|
+
url,
|
|
1571
|
+
method: 'get',
|
|
1572
|
+
params: { key: this.apiKey },
|
|
1573
|
+
timeout: this.timeout,
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
580
1576
|
}
|