@promptbook/utils
Version:
Promptbook: Run AI apps in plain human language across multiple models and platforms
1,509 lines (1,418 loc) โข 93.9 kB
JavaScript
import spaceTrim$1, { spaceTrim as spaceTrim$2 } from 'spacetrim';
import { basename } from 'path';
import { randomBytes } from 'crypto';
// โ ๏ธ WARNING: This code has been generated so that any manual changes will be overwritten
/**
* The version of the Book language
*
* @generated
* @see https://github.com/webgptorg/book
*/
const BOOK_LANGUAGE_VERSION = '1.0.0';
/**
* The version of the Promptbook engine
*
* @generated
* @see https://github.com/webgptorg/promptbook
*/
const PROMPTBOOK_ENGINE_VERSION = '0.101.0-5';
/**
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Name for the Promptbook
*
* TODO: [๐ฝ] Unite branding and make single place for it
*
* @public exported from `@promptbook/core`
*/
const NAME = `Promptbook`;
/**
* Email of the responsible person
*
* @public exported from `@promptbook/core`
*/
const ADMIN_EMAIL = 'pavol@ptbk.io';
/**
* Name of the responsible person for the Promptbook on GitHub
*
* @public exported from `@promptbook/core`
*/
const ADMIN_GITHUB_NAME = 'hejny';
// <- TODO: [๐ง ] Better system for generator warnings - not always "code" and "by `@promptbook/cli`"
/**
* The maximum number of iterations for a loops
*
* @private within the repository - too low-level in comparison with other `MAX_...`
*/
const LOOP_LIMIT = 1000;
/**
* Strings to represent various values in the context of parameter values
*
* @public exported from `@promptbook/utils`
*/
const VALUE_STRINGS = {
empty: '(nothing; empty string)',
null: '(no value; null)',
undefined: '(unknown value; undefined)',
nan: '(not a number; NaN)',
infinity: '(infinity; โ)',
negativeInfinity: '(negative infinity; -โ)',
unserializable: '(unserializable value)',
circular: '(circular JSON)',
};
/**
* Small number limit
*
* @public exported from `@promptbook/utils`
*/
const SMALL_NUMBER = 0.001;
// <- TODO: [๐งโโ๏ธ]
/**
* Default settings for parsing and generating CSV files in Promptbook.
*
* @public exported from `@promptbook/core`
*/
Object.freeze({
delimiter: ',',
quoteChar: '"',
newline: '\n',
skipEmptyLines: true,
});
/**
* API request timeout in milliseconds
* Can be overridden via API_REQUEST_TIMEOUT environment variable
*
* @public exported from `@promptbook/core`
*/
parseInt(process.env.API_REQUEST_TIMEOUT || '90000');
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
* TODO: [๐ง ][๐งโโ๏ธ] Maybe join remoteServerUrl and path into single value
*/
/**
* Orders JSON object by keys
*
* @returns The same type of object as the input re-ordered
* @public exported from `@promptbook/utils`
*/
function orderJson(options) {
const { value, order } = options;
const orderedValue = {
...(order === undefined ? {} : Object.fromEntries(order.map((key) => [key, undefined]))),
...value,
};
return orderedValue;
}
/**
* Freezes the given object and all its nested objects recursively
*
* Note: `$` is used to indicate that this function is not a pure function - it mutates given object
* Note: This function mutates the object and returns the original (but mutated-deep-freezed) object
*
* @returns The same object as the input, but deeply frozen
* @public exported from `@promptbook/utils`
*/
function $deepFreeze(objectValue) {
if (Array.isArray(objectValue)) {
return Object.freeze(objectValue.map((item) => $deepFreeze(item)));
}
const propertyNames = Object.getOwnPropertyNames(objectValue);
for (const propertyName of propertyNames) {
const value = objectValue[propertyName];
if (value && typeof value === 'object') {
$deepFreeze(value);
}
}
Object.freeze(objectValue);
return objectValue;
}
/**
* TODO: [๐ง ] Is there a way how to meaningfully test this utility
*/
/**
* Make error report URL for the given error
*
* @private private within the repository
*/
function getErrorReportUrl(error) {
const report = {
title: `๐ Error report from ${NAME}`,
body: spaceTrim$1((block) => `
\`${error.name || 'Error'}\` has occurred in the [${NAME}], please look into it @${ADMIN_GITHUB_NAME}.
\`\`\`
${block(error.message || '(no error message)')}
\`\`\`
## More info:
- **Promptbook engine version:** ${PROMPTBOOK_ENGINE_VERSION}
- **Book language version:** ${BOOK_LANGUAGE_VERSION}
- **Time:** ${new Date().toISOString()}
<details>
<summary>Stack trace:</summary>
## Stack trace:
\`\`\`stacktrace
${block(error.stack || '(empty)')}
\`\`\`
</details>
`),
};
const reportUrl = new URL(`https://github.com/webgptorg/promptbook/issues/new`);
reportUrl.searchParams.set('labels', 'bug');
reportUrl.searchParams.set('assignees', ADMIN_GITHUB_NAME);
reportUrl.searchParams.set('title', report.title);
reportUrl.searchParams.set('body', report.body);
return reportUrl;
}
/**
* This error type indicates that the error should not happen and its last check before crashing with some other error
*
* @public exported from `@promptbook/core`
*/
class UnexpectedError extends Error {
constructor(message) {
super(spaceTrim$2((block) => `
${block(message)}
Note: This error should not happen.
It's probably a bug in the pipeline collection
Please report issue:
${block(getErrorReportUrl(new Error(message)).href)}
Or contact us on ${ADMIN_EMAIL}
`));
this.name = 'UnexpectedError';
Object.setPrototypeOf(this, UnexpectedError.prototype);
}
}
/**
* This error type indicates that somewhere in the code non-Error object was thrown and it was wrapped into the `WrappedError`
*
* @public exported from `@promptbook/core`
*/
class WrappedError extends Error {
constructor(whatWasThrown) {
const tag = `[๐คฎ]`;
console.error(tag, whatWasThrown);
super(spaceTrim$2(`
Non-Error object was thrown
Note: Look for ${tag} in the console for more details
Please report issue on ${ADMIN_EMAIL}
`));
this.name = 'WrappedError';
Object.setPrototypeOf(this, WrappedError.prototype);
}
}
/**
* Helper used in catch blocks to assert that the error is an instance of `Error`
*
* @param whatWasThrown Any object that was thrown
* @returns Nothing if the error is an instance of `Error`
* @throws `WrappedError` or `UnexpectedError` if the error is not standard
*
* @private within the repository
*/
function assertsError(whatWasThrown) {
// Case 1: Handle error which was rethrown as `WrappedError`
if (whatWasThrown instanceof WrappedError) {
const wrappedError = whatWasThrown;
throw wrappedError;
}
// Case 2: Handle unexpected errors
if (whatWasThrown instanceof UnexpectedError) {
const unexpectedError = whatWasThrown;
throw unexpectedError;
}
// Case 3: Handle standard errors - keep them up to consumer
if (whatWasThrown instanceof Error) {
return;
}
// Case 4: Handle non-standard errors - wrap them into `WrappedError` and throw
throw new WrappedError(whatWasThrown);
}
/**
* Checks if the value is [๐] serializable as JSON
* If not, throws an UnexpectedError with a rich error message and tracking
*
* - Almost all primitives are serializable BUT:
* - `undefined` is not serializable
* - `NaN` is not serializable
* - Objects and arrays are serializable if all their properties are serializable
* - Functions are not serializable
* - Circular references are not serializable
* - `Date` objects are not serializable
* - `Map` and `Set` objects are not serializable
* - `RegExp` objects are not serializable
* - `Error` objects are not serializable
* - `Symbol` objects are not serializable
* - And much more...
*
* @throws UnexpectedError if the value is not serializable as JSON
* @public exported from `@promptbook/utils`
*/
function checkSerializableAsJson(options) {
const { value, name, message } = options;
if (value === undefined) {
throw new UnexpectedError(`${name} is undefined`);
}
else if (value === null) {
return;
}
else if (typeof value === 'boolean') {
return;
}
else if (typeof value === 'number' && !isNaN(value)) {
return;
}
else if (typeof value === 'string') {
return;
}
else if (typeof value === 'symbol') {
throw new UnexpectedError(`${name} is symbol`);
}
else if (typeof value === 'function') {
throw new UnexpectedError(`${name} is function`);
}
else if (typeof value === 'object' && Array.isArray(value)) {
for (let i = 0; i < value.length; i++) {
checkSerializableAsJson({ name: `${name}[${i}]`, value: value[i], message });
}
}
else if (typeof value === 'object') {
if (value instanceof Date) {
throw new UnexpectedError(spaceTrim$1((block) => `
\`${name}\` is Date
Use \`string_date_iso8601\` instead
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
else if (value instanceof Map) {
throw new UnexpectedError(`${name} is Map`);
}
else if (value instanceof Set) {
throw new UnexpectedError(`${name} is Set`);
}
else if (value instanceof RegExp) {
throw new UnexpectedError(`${name} is RegExp`);
}
else if (value instanceof Error) {
throw new UnexpectedError(spaceTrim$1((block) => `
\`${name}\` is unserialized Error
Use function \`serializeError\`
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
else {
for (const [subName, subValue] of Object.entries(value)) {
if (subValue === undefined) {
// Note: undefined in object is serializable - it is just omitted
continue;
}
checkSerializableAsJson({ name: `${name}.${subName}`, value: subValue, message });
}
try {
JSON.stringify(value); // <- TODO: [0]
}
catch (error) {
assertsError(error);
throw new UnexpectedError(spaceTrim$1((block) => `
\`${name}\` is not serializable
${block(error.stack || error.message)}
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
/*
TODO: [0] Is there some more elegant way to check circular references?
const seen = new Set();
const stack = [{ value }];
while (stack.length > 0) {
const { value } = stack.pop()!;
if (typeof value === 'object' && value !== null) {
if (seen.has(value)) {
throw new UnexpectedError(`${name} has circular reference`);
}
seen.add(value);
if (Array.isArray(value)) {
stack.push(...value.map((value) => ({ value })));
} else {
stack.push(...Object.values(value).map((value) => ({ value })));
}
}
}
*/
return;
}
}
else {
throw new UnexpectedError(spaceTrim$1((block) => `
\`${name}\` is unknown type
Additional message for \`${name}\`:
${block(message || '(nothing)')}
`));
}
}
/**
* TODO: Can be return type more type-safe? like `asserts options.value is JsonValue`
* TODO: [๐ง ][main] !!3 In-memory cache of same values to prevent multiple checks
* Note: [๐ ] This is how `checkSerializableAsJson` + `isSerializableAsJson` together can just retun true/false or rich error message
*/
/**
* Creates a deep clone of the given object
*
* Note: [๐] This function is idempotent.
* Note: This method only works for objects that are fully serializable to JSON and do not contain functions, Dates, or special types.
*
* @param objectValue The object to clone.
* @returns A deep, writable clone of the input object.
* @public exported from `@promptbook/utils`
*/
function deepClone(objectValue) {
return JSON.parse(JSON.stringify(objectValue));
/*
TODO: [๐ง ] Is there a better implementation?
> const propertyNames = Object.getOwnPropertyNames(objectValue);
> for (const propertyName of propertyNames) {
> const value = (objectValue as really_any)[propertyName];
> if (value && typeof value === 'object') {
> deepClone(value);
> }
> }
> return Object.assign({}, objectValue);
*/
}
/**
* TODO: [๐ง ] Is there a way how to meaningfully test this utility
*/
/**
* Utility to export a JSON object from a function
*
* 1) Checks if the value is serializable as JSON
* 2) Makes a deep clone of the object
* 2) Orders the object properties
* 2) Deeply freezes the cloned object
*
* Note: This function does not mutates the given object
*
* @returns The same type of object as the input but read-only and re-ordered
* @public exported from `@promptbook/utils`
*/
function exportJson(options) {
const { name, value, order, message } = options;
checkSerializableAsJson({ name, value, message });
const orderedValue =
// TODO: Fix error "Type instantiation is excessively deep and possibly infinite."
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
order === undefined
? deepClone(value)
: orderJson({
value: value,
// <- Note: checkSerializableAsJson asserts that the value is serializable as JSON
order: order,
});
$deepFreeze(orderedValue);
return orderedValue;
}
/**
* TODO: [๐ง ] Is there a way how to meaningfully test this utility
*/
/**
* Nonce which is used for replacing things in strings
*
* @private within the repository
*/
const REPLACING_NONCE = 'ptbkauk42kV2dzao34faw7FudQUHYPtW';
/**
* Placeholder value indicating a parameter is missing its value.
*
* @private within the repository
*/
const RESERVED_PARAMETER_MISSING_VALUE = 'MISSING-' + REPLACING_NONCE;
/**
* Placeholder value indicating a parameter is restricted and cannot be used directly.
*
* @private within the repository
*/
const RESERVED_PARAMETER_RESTRICTED = 'RESTRICTED-' + REPLACING_NONCE;
/**
* The names of the parameters that are reserved for special purposes
*
* @public exported from `@promptbook/core`
*/
const RESERVED_PARAMETER_NAMES = exportJson({
name: 'RESERVED_PARAMETER_NAMES',
message: `The names of the parameters that are reserved for special purposes`,
value: [
'content',
'context',
'knowledge',
'examples',
'modelName',
'currentDate',
// <- TODO: list here all command names
// <- TODO: Add more like 'date', 'modelName',...
// <- TODO: Add [emoji] + instructions ACRY when adding new reserved parameter
],
});
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Normalizes a given text to camelCase format.
*
* @param text The text to be normalized.
* @param _isFirstLetterCapital Whether the first letter should be capitalized.
* @returns The camelCase formatted string.
* @example 'helloWorld'
* @example 'iLovePromptbook'
* @public exported from `@promptbook/utils`
*/
function normalizeTo_camelCase(text, _isFirstLetterCapital = false) {
let charType;
let lastCharType = null;
let normalizedName = '';
for (const char of text) {
let normalizedChar;
if (/^[a-z]$/.test(char)) {
charType = 'LOWERCASE';
normalizedChar = char;
}
else if (/^[A-Z]$/.test(char)) {
charType = 'UPPERCASE';
normalizedChar = char.toLowerCase();
}
else if (/^[0-9]$/.test(char)) {
charType = 'NUMBER';
normalizedChar = char;
}
else {
charType = 'OTHER';
normalizedChar = '';
}
if (!lastCharType) {
if (_isFirstLetterCapital) {
normalizedChar = normalizedChar.toUpperCase(); //TODO: DRY
}
}
else if (charType !== lastCharType &&
!(charType === 'LOWERCASE' && lastCharType === 'UPPERCASE') &&
!(lastCharType === 'NUMBER') &&
!(charType === 'NUMBER')) {
normalizedChar = normalizedChar.toUpperCase(); //TODO: [๐บ] DRY
}
normalizedName += normalizedChar;
lastCharType = charType;
}
return normalizedName;
}
/**
* TODO: [๐บ] Use some intermediate util splitWords
*/
/**
* Removes emojis from a string and fix whitespaces
*
* Note: [๐] This function is idempotent.
*
* @param text with emojis
* @returns text without emojis
* @public exported from `@promptbook/utils`
*/
function removeEmojis(text) {
// Replace emojis (and also ZWJ sequence) with hyphens
text = text.replace(/(\p{Extended_Pictographic})\p{Modifier_Symbol}/gu, '$1');
text = text.replace(/(\p{Extended_Pictographic})[\u{FE00}-\u{FE0F}]/gu, '$1');
text = text.replace(/(\p{Extended_Pictographic})(\u{200D}\p{Extended_Pictographic})*/gu, '$1');
text = text.replace(/\p{Extended_Pictographic}/gu, '');
return text;
}
/**
* Tests if given string is valid file path.
*
* Note: This does not check if the file exists only if the path is valid
* @public exported from `@promptbook/utils`
*/
function isValidFilePath(filename) {
if (typeof filename !== 'string') {
return false;
}
if (filename.split('\n').length > 1) {
return false;
}
// Normalize slashes early so heuristics can detect path-like inputs
const filenameSlashes = filename.replace(/\\/g, '/');
// Reject strings that look like sentences (informational text)
// Heuristic: contains multiple spaces and ends with a period, or contains typical sentence punctuation
// But skip this heuristic if the string looks like a path (contains '/' or starts with a drive letter)
if (filename.trim().length > 60 && // long enough to be a sentence
/[.!?]/.test(filename) && // contains sentence punctuation
filename.split(' ').length > 8 && // has many words
!/\/|^[A-Z]:/i.test(filenameSlashes) // do NOT treat as sentence if looks like a path
) {
return false;
}
// Absolute Unix path: /hello.txt
if (/^(\/)/i.test(filenameSlashes)) {
// console.log(filename, 'Absolute Unix path: /hello.txt');
return true;
}
// Absolute Windows path: C:/ or C:\ (allow spaces and multiple dots in filename)
if (/^[A-Z]:\/.+$/i.test(filenameSlashes)) {
// console.log(filename, 'Absolute Windows path: /hello.txt');
return true;
}
// Relative path: ./hello.txt
if (/^(\.\.?\/)+/i.test(filenameSlashes)) {
// console.log(filename, 'Relative path: ./hello.txt');
return true;
}
// Allow paths like foo/hello
if (/^[^/]+\/[^/]+/i.test(filenameSlashes)) {
// console.log(filename, 'Allow paths like foo/hello');
return true;
}
// Allow paths like hello.book
if (/^[^/]+\.[^/]+$/i.test(filenameSlashes)) {
// console.log(filename, 'Allow paths like hello.book');
return true;
}
return false;
}
/**
* TODO: [๐] Implement for MacOs
*/
/**
* Tests if given string is valid URL.
*
* Note: [๐] This function is idempotent.
* Note: Dataurl are considered perfectly valid.
* Note: There are two similar functions:
* - `isValidUrl` which tests any URL
* - `isValidPipelineUrl` *(this one)* which tests just promptbook URL
*
* @public exported from `@promptbook/utils`
*/
function isValidUrl(url) {
if (typeof url !== 'string') {
return false;
}
try {
if (url.startsWith('blob:')) {
url = url.replace(/^blob:/, '');
}
const urlObject = new URL(url /* because fail is handled */);
if (!['http:', 'https:', 'data:'].includes(urlObject.protocol)) {
return false;
}
return true;
}
catch (error) {
return false;
}
}
const defaultDiacriticsRemovalMap = [
{
base: 'A',
letters: '\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F',
},
{ base: 'AA', letters: '\uA732' },
{ base: 'AE', letters: '\u00C6\u01FC\u01E2' },
{ base: 'AO', letters: '\uA734' },
{ base: 'AU', letters: '\uA736' },
{ base: 'AV', letters: '\uA738\uA73A' },
{ base: 'AY', letters: '\uA73C' },
{
base: 'B',
letters: '\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181',
},
{
base: 'C',
letters: '\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E',
},
{
base: 'D',
letters: '\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0',
},
{ base: 'DZ', letters: '\u01F1\u01C4' },
{ base: 'Dz', letters: '\u01F2\u01C5' },
{
base: 'E',
letters: '\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E',
},
{ base: 'F', letters: '\u0046\u24BB\uFF26\u1E1E\u0191\uA77B' },
{
base: 'G',
letters: '\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E',
},
{
base: 'H',
letters: '\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D',
},
{
base: 'I',
letters: '\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197',
},
{ base: 'J', letters: '\u004A\u24BF\uFF2A\u0134\u0248' },
{
base: 'K',
letters: '\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2',
},
{
base: 'L',
letters: '\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780',
},
{ base: 'LJ', letters: '\u01C7' },
{ base: 'Lj', letters: '\u01C8' },
{ base: 'M', letters: '\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C' },
{
base: 'N',
letters: '\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4',
},
{ base: 'NJ', letters: '\u01CA' },
{ base: 'Nj', letters: '\u01CB' },
{
base: 'O',
letters: '\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C',
},
{ base: 'OI', letters: '\u01A2' },
{ base: 'OO', letters: '\uA74E' },
{ base: 'OU', letters: '\u0222' },
{ base: 'OE', letters: '\u008C\u0152' },
{ base: 'oe', letters: '\u009C\u0153' },
{
base: 'P',
letters: '\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754',
},
{ base: 'Q', letters: '\u0051\u24C6\uFF31\uA756\uA758\u024A' },
{
base: 'R',
letters: '\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782',
},
{
base: 'S',
letters: '\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784',
},
{
base: 'T',
letters: '\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786',
},
{ base: 'TZ', letters: '\uA728' },
{
base: 'U',
letters: '\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244',
},
{ base: 'V', letters: '\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245' },
{ base: 'VY', letters: '\uA760' },
{
base: 'W',
letters: '\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72',
},
{ base: 'X', letters: '\u0058\u24CD\uFF38\u1E8A\u1E8C' },
{
base: 'Y',
letters: '\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE',
},
{
base: 'Z',
letters: '\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762',
},
{
base: 'a',
letters: '\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250',
},
{ base: 'aa', letters: '\uA733' },
{ base: 'ae', letters: '\u00E6\u01FD\u01E3' },
{ base: 'ao', letters: '\uA735' },
{ base: 'au', letters: '\uA737' },
{ base: 'av', letters: '\uA739\uA73B' },
{ base: 'ay', letters: '\uA73D' },
{
base: 'b',
letters: '\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253',
},
{
base: 'c',
letters: '\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184',
},
{
base: 'd',
letters: '\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A',
},
{ base: 'dz', letters: '\u01F3\u01C6' },
{
base: 'e',
letters: '\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD',
},
{ base: 'f', letters: '\u0066\u24D5\uFF46\u1E1F\u0192\uA77C' },
{
base: 'g',
letters: '\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F',
},
{
base: 'h',
letters: '\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265',
},
{ base: 'hv', letters: '\u0195' },
{
base: 'i',
letters: '\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131',
},
{ base: 'j', letters: '\u006A\u24D9\uFF4A\u0135\u01F0\u0249' },
{
base: 'k',
letters: '\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3',
},
{
base: 'l',
letters: '\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747',
},
{ base: 'lj', letters: '\u01C9' },
{ base: 'm', letters: '\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F' },
{
base: 'n',
letters: '\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5',
},
{ base: 'nj', letters: '\u01CC' },
{
base: 'o',
letters: '\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275',
},
{ base: 'oi', letters: '\u01A3' },
{ base: 'ou', letters: '\u0223' },
{ base: 'oo', letters: '\uA74F' },
{
base: 'p',
letters: '\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755',
},
{ base: 'q', letters: '\u0071\u24E0\uFF51\u024B\uA757\uA759' },
{
base: 'r',
letters: '\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783',
},
{
base: 's',
letters: '\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B',
},
{
base: 't',
letters: '\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787',
},
{ base: 'tz', letters: '\uA729' },
{
base: 'u',
letters: '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289',
},
{ base: 'v', letters: '\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C' },
{ base: 'vy', letters: '\uA761' },
{
base: 'w',
letters: '\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73',
},
{ base: 'x', letters: '\u0078\u24E7\uFF58\u1E8B\u1E8D' },
{
base: 'y',
letters: '\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF',
},
{
base: 'z',
letters: '\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763',
},
];
/**
* Map of letters from diacritic variant to diacritless variant
* Contains lowercase and uppercase separatelly
*
* > "รก" => "a"
* > "ฤ" => "e"
* > "ฤ" => "A"
* > ...
*
* @public exported from `@promptbook/utils`
*/
const DIACRITIC_VARIANTS_LETTERS = {};
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < defaultDiacriticsRemovalMap.length; i++) {
const letters = defaultDiacriticsRemovalMap[i].letters;
// tslint:disable-next-line: prefer-for-of
for (let j = 0; j < letters.length; j++) {
DIACRITIC_VARIANTS_LETTERS[letters[j]] = defaultDiacriticsRemovalMap[i].base;
}
}
// <- TODO: [๐] Put to maker function to save execution time if not needed
/*
@see https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
/**
* Removes diacritic marks (accents) from characters in a string.
*
* Note: [๐] This function is idempotent.
*
* @param input The string containing diacritics to be normalized.
* @returns The string with diacritics removed or normalized.
* @public exported from `@promptbook/utils`
*/
function removeDiacritics(input) {
/*eslint no-control-regex: "off"*/
return input.replace(/[^\u0000-\u007E]/g, (a) => {
return DIACRITIC_VARIANTS_LETTERS[a] || a;
});
}
/**
* TODO: [ะ] Variant for cyrillic (and in general non-latin) letters
*/
/**
* Converts a given text to kebab-case format.
*
* @param text The text to be converted.
* @returns The kebab-case formatted string.
* @example 'hello-world'
* @example 'i-love-promptbook'
* @public exported from `@promptbook/utils`
*/
function normalizeToKebabCase(text) {
text = removeDiacritics(text);
let charType;
let lastCharType = 'OTHER';
let normalizedName = '';
for (const char of text) {
let normalizedChar;
if (/^[a-z]$/.test(char)) {
charType = 'LOWERCASE';
normalizedChar = char;
}
else if (/^[A-Z]$/.test(char)) {
charType = 'UPPERCASE';
normalizedChar = char.toLowerCase();
}
else if (/^[0-9]$/.test(char)) {
charType = 'NUMBER';
normalizedChar = char;
}
else {
charType = 'OTHER';
normalizedChar = '-';
}
if (charType !== lastCharType &&
!(lastCharType === 'UPPERCASE' && charType === 'LOWERCASE') &&
!(lastCharType === 'NUMBER') &&
!(charType === 'NUMBER')) {
normalizedName += '-';
}
normalizedName += normalizedChar;
lastCharType = charType;
}
normalizedName = normalizedName.split(/-+/g).join('-');
normalizedName = normalizedName.split(/-?\/-?/g).join('/');
normalizedName = normalizedName.replace(/^-/, '');
normalizedName = normalizedName.replace(/-$/, '');
return normalizedName;
}
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Converts a title string into a normalized name.
*
* @param value The title string to be converted to a name.
* @returns A normalized name derived from the input title.
* @example 'Hello World!' -> 'hello-world'
* @public exported from `@promptbook/utils`
*/
function titleToName(value) {
if (isValidUrl(value)) {
value = value.replace(/^https?:\/\//, '');
value = value.replace(/\.html$/, '');
}
else if (isValidFilePath(value)) {
value = basename(value);
// Note: Keeping extension in the name
}
value = value.split('/').join('-');
value = removeEmojis(value);
value = normalizeToKebabCase(value);
// TODO: [๐ง ] Maybe warn or add some padding to short name which are not good identifiers
return value;
}
/**
* Creates a Mermaid graph based on the promptbook
*
* Note: The result is not wrapped in a Markdown code block
*
* @public exported from `@promptbook/utils`
*/
function renderPromptbookMermaid(pipelineJson, options) {
const { linkTask = () => null } = options || {};
const MERMAID_PREFIX = 'pipeline_';
const MERMAID_KNOWLEDGE_NAME = MERMAID_PREFIX + 'knowledge';
const MERMAID_RESERVED_NAME = MERMAID_PREFIX + 'reserved';
const MERMAID_INPUT_NAME = MERMAID_PREFIX + 'input';
const MERMAID_OUTPUT_NAME = MERMAID_PREFIX + 'output';
const parameterNameToTaskName = (parameterName) => {
if (parameterName === 'knowledge') {
return MERMAID_KNOWLEDGE_NAME;
}
else if (RESERVED_PARAMETER_NAMES.includes(parameterName)) {
return MERMAID_RESERVED_NAME;
}
const parameter = pipelineJson.parameters.find((parameter) => parameter.name === parameterName);
if (!parameter) {
throw new UnexpectedError(`Could not find {${parameterName}}`);
// <- TODO: This causes problems when {knowledge} and other reserved parameters are used
}
if (parameter.isInput) {
return MERMAID_INPUT_NAME;
}
const task = pipelineJson.tasks.find((task) => task.resultingParameterName === parameterName);
if (!task) {
throw new Error(`Could not find task for {${parameterName}}`);
}
return MERMAID_PREFIX + (task.name || normalizeTo_camelCase('task-' + titleToName(task.title)));
};
const inputAndIntermediateParametersMermaid = pipelineJson.tasks
.flatMap(({ title, dependentParameterNames, resultingParameterName }) => [
`${parameterNameToTaskName(resultingParameterName)}("${title}")`,
...dependentParameterNames.map((dependentParameterName) => `${parameterNameToTaskName(dependentParameterName)}--"{${dependentParameterName}}"-->${parameterNameToTaskName(resultingParameterName)}`),
])
.join('\n');
const outputParametersMermaid = pipelineJson.parameters
.filter(({ isOutput }) => isOutput)
.map(({ name }) => `${parameterNameToTaskName(name)}--"{${name}}"-->${MERMAID_OUTPUT_NAME}`)
.join('\n');
const linksMermaid = pipelineJson.tasks
.map((task) => {
const link = linkTask(task);
if (link === null) {
return '';
}
const { href, title } = link;
const taskName = parameterNameToTaskName(task.resultingParameterName);
return `click ${taskName} href "${href}" "${title}";`;
})
.filter((line) => line !== '')
.join('\n');
const interactionPointsMermaid = Object.entries({
[MERMAID_INPUT_NAME]: 'Input',
[MERMAID_OUTPUT_NAME]: 'Output',
[MERMAID_RESERVED_NAME]: 'Other',
[MERMAID_KNOWLEDGE_NAME]: 'Knowledge',
})
.filter(([MERMAID_NAME]) => (inputAndIntermediateParametersMermaid + outputParametersMermaid).includes(MERMAID_NAME))
.map(([MERMAID_NAME, title]) => `${MERMAID_NAME}((${title})):::${MERMAID_NAME}`)
.join('\n');
const promptbookMermaid = spaceTrim$2((block) => `
%% ๐ฎ Tip: Open this on GitHub or in the VSCode website to see the Mermaid graph visually
flowchart LR
subgraph "${pipelineJson.title}"
%% Basic configuration
direction TB
%% Interaction points from pipeline to outside
${block(interactionPointsMermaid)}
%% Input and intermediate parameters
${block(inputAndIntermediateParametersMermaid)}
%% Output parameters
${block(outputParametersMermaid)}
%% Links
${block(linksMermaid)}
%% Styles
classDef ${MERMAID_INPUT_NAME} color: grey;
classDef ${MERMAID_OUTPUT_NAME} color: grey;
classDef ${MERMAID_RESERVED_NAME} color: grey;
classDef ${MERMAID_KNOWLEDGE_NAME} color: grey;
end;
`);
return promptbookMermaid;
}
/**
* TODO: [๐ง ] FOREACH in mermaid graph
* TODO: [๐ง ] Knowledge in mermaid graph
* TODO: [๐ง ] Personas in mermaid graph
* TODO: Maybe use some Mermaid package instead of string templating
* TODO: [๐] When more than 2 functionalities, split into separate functions
*/
/**
* This error indicates problems parsing the format value
*
* For example, when the format value is not a valid JSON or CSV
* This is not thrown directly but in extended classes
*
* @public exported from `@promptbook/core`
*/
class AbstractFormatError extends Error {
// Note: To allow instanceof do not put here error `name`
// public readonly name = 'AbstractFormatError';
constructor(message) {
super(message);
Object.setPrototypeOf(this, AbstractFormatError.prototype);
}
}
/**
* This error indicates problem with parsing of CSV
*
* @public exported from `@promptbook/core`
*/
class CsvFormatError extends AbstractFormatError {
constructor(message) {
super(message);
this.name = 'CsvFormatError';
Object.setPrototypeOf(this, CsvFormatError.prototype);
}
}
/**
* AuthenticationError is thrown from login function which is dependency of remote server
*
* @public exported from `@promptbook/core`
*/
class AuthenticationError extends Error {
constructor(message) {
super(message);
this.name = 'AuthenticationError';
Object.setPrototypeOf(this, AuthenticationError.prototype);
}
}
/**
* This error indicates that the pipeline collection cannot be properly loaded
*
* @public exported from `@promptbook/core`
*/
class CollectionError extends Error {
constructor(message) {
super(message);
this.name = 'CollectionError';
Object.setPrototypeOf(this, CollectionError.prototype);
}
}
/**
* This error type indicates that you try to use a feature that is not available in the current environment
*
* @public exported from `@promptbook/core`
*/
class EnvironmentMismatchError extends Error {
constructor(message) {
super(message);
this.name = 'EnvironmentMismatchError';
Object.setPrototypeOf(this, EnvironmentMismatchError.prototype);
}
}
/**
* This error occurs when some expectation is not met in the execution of the pipeline
*
* @public exported from `@promptbook/core`
* Note: Do not throw this error, its reserved for `checkExpectations` and `createPipelineExecutor` and public ONLY to be serializable through remote server
* Note: Always thrown in `checkExpectations` and catched in `createPipelineExecutor` and rethrown as `PipelineExecutionError`
* Note: This is a kindof subtype of PipelineExecutionError
*/
class ExpectError extends Error {
constructor(message) {
super(message);
this.name = 'ExpectError';
Object.setPrototypeOf(this, ExpectError.prototype);
}
}
/**
* This error indicates that the promptbook can not retrieve knowledge from external sources
*
* @public exported from `@promptbook/core`
*/
class KnowledgeScrapeError extends Error {
constructor(message) {
super(message);
this.name = 'KnowledgeScrapeError';
Object.setPrototypeOf(this, KnowledgeScrapeError.prototype);
}
}
/**
* This error type indicates that some limit was reached
*
* @public exported from `@promptbook/core`
*/
class LimitReachedError extends Error {
constructor(message) {
super(message);
this.name = 'LimitReachedError';
Object.setPrototypeOf(this, LimitReachedError.prototype);
}
}
/**
* This error type indicates that some tools are missing for pipeline execution or preparation
*
* @public exported from `@promptbook/core`
*/
class MissingToolsError extends Error {
constructor(message) {
super(spaceTrim$2((block) => `
${block(message)}
Note: You have probably forgot to provide some tools for pipeline execution or preparation
`));
this.name = 'MissingToolsError';
Object.setPrototypeOf(this, MissingToolsError.prototype);
}
}
/**
* This error indicates that promptbook not found in the collection
*
* @public exported from `@promptbook/core`
*/
class NotFoundError extends Error {
constructor(message) {
super(message);
this.name = 'NotFoundError';
Object.setPrototypeOf(this, NotFoundError.prototype);
}
}
/**
* This error type indicates that some part of the code is not implemented yet
*
* @public exported from `@promptbook/core`
*/
class NotYetImplementedError extends Error {
constructor(message) {
super(spaceTrim$2((block) => `
${block(message)}
Note: This feature is not implemented yet but it will be soon.
If you want speed up the implementation or just read more, look here:
https://github.com/webgptorg/promptbook
Or contact us on pavol@ptbk.io
`));
this.name = 'NotYetImplementedError';
Object.setPrototypeOf(this, NotYetImplementedError.prototype);
}
}
/**
* This error indicates that the promptbook in a markdown format cannot be parsed into a valid promptbook object
*
* @public exported from `@promptbook/core`
*/
class ParseError extends Error {
constructor(message) {
super(message);
this.name = 'ParseError';
Object.setPrototypeOf(this, ParseError.prototype);
}
}
/**
* TODO: Maybe split `ParseError` and `ApplyError`
*/
/**
* Generates random token
*
* Note: This function is cryptographically secure (it uses crypto.randomBytes internally)
*
* @private internal helper function
* @returns secure random token
*/
function $randomToken(randomness) {
return randomBytes(randomness).toString('hex');
}
/**
* TODO: Maybe use nanoid instead https://github.com/ai/nanoid
*/
/**
* This error indicates errors during the execution of the pipeline
*
* @public exported from `@promptbook/core`
*/
class PipelineExecutionError extends Error {
constructor(message) {
// Added id parameter
super(message);
this.name = 'PipelineExecutionError';
// TODO: [๐] DRY - Maybe $randomId
this.id = `error-${$randomToken(8 /* <- TODO: To global config + Use Base58 to avoid similar char conflicts */)}`;
Object.setPrototypeOf(this, PipelineExecutionError.prototype);
}
}
/**
* TODO: [๐ง ][๐] Add id to all errors
*/
/**
* This error indicates that the promptbook object has valid syntax (=can be parsed) but contains logical errors (like circular dependencies)
*
* @public exported from `@promptbook/core`
*/
class PipelineLogicError extends Error {
constructor(message) {
super(message);
this.name = 'PipelineLogicError';
Object.setPrototypeOf(this, PipelineLogicError.prototype);
}
}
/**
* This error indicates errors in referencing promptbooks between each other
*
* @public exported from `@promptbook/core`
*/
class PipelineUrlError extends Error {
constructor(message) {
super(message);
this.name = 'PipelineUrlError';
Object.setPrototypeOf(this, PipelineUrlError.prototype);
}
}
/**
* Error thrown when a fetch request fails
*
* @public exported from `@promptbook/core`
*/
class PromptbookFetchError extends Error {
constructor(message) {
super(message);
this.name = 'PromptbookFetchError';
Object.setPrototypeOf(this, PromptbookFetchError.prototype);
}
}
/**
* Index of all custom errors
*
* @public exported from `@promptbook/core`
*/
const PROMPTBOOK_ERRORS = {
AbstractFormatError,
CsvFormatError,
CollectionError,
EnvironmentMismatchError,
ExpectError,
KnowledgeScrapeError,
LimitReachedError,
MissingToolsError,
NotFoundError,
NotYetImplementedError,
ParseError,
PipelineExecutionError,
PipelineLogicError,
PipelineUrlError,
AuthenticationError,
PromptbookFetchError,
UnexpectedError,
WrappedError,
// TODO: [๐ช]> VersionMismatchError,
};
/**
* Index of all javascript errors
*
* @private for internal usage
*/
const COMMON_JAVASCRIPT_ERRORS = {
Error,
EvalError,
RangeError,
ReferenceError,
SyntaxError,
TypeError,
URIError,
AggregateError,
/*
Note: Not widely supported
> InternalError,
> ModuleError,
> HeapError,
> WebAssemblyCompileError,
> WebAssemblyRuntimeError,
*/
};
/**
* Index of all errors
*
* @private for internal usage
*/
const ALL_ERRORS = {
...PROMPTBOOK_ERRORS,
...COMMON_JAVASCRIPT_ERRORS,
};
/**
* Note: [๐] Ignore a discrepancy between file name and entity name
*/
/**
* Deserializes the error object
*
* @public exported from `@promptbook/utils`
*/
function deserializeError(error) {
const { name, stack, id } = error; // Added id
let { message } = error;
let ErrorClass = ALL_ERRORS[error.name];
if (ErrorClass === undefined) {
ErrorClass = Error;
message = `${name}: ${message}`;
}
if (stack !== undefined && stack !== '') {
message = spaceTrim$1((block) => `
${block(message)}
Original stack trace:
${block(stack || '')}
`);
}
const deserializedError = new ErrorClass(message);
deserializedError.id = id; // Assign id to the error object
return deserializedError;
}
/**
* Serializes an error into a [๐] JSON-serializable object
*
* @public exported from `@promptbook/utils`
*/
function serializeError(error) {
const { name, message, stack } = error;
const { id } = error;
if (!Object.keys(ALL_ERRORS).includes(name)) {
console.error(spaceTrim$1((block) => `
Cannot serialize error with name "${name}"
Authors of Promptbook probably forgot to add this error into the list of errors:
https://github.com/webgptorg/promptbook/blob/main/src/errors/0-index.ts
${block(stack || message)}
`));
}
return {
name: name,
message,
stack,
id, // Include id in the serialized object
};
}
/**
* Async version of Array.forEach
*
* @param array - Array to iterate over
* @param options - Options for the function
* @param callbackfunction - Function to call for each item
* @public exported from `@promptbook/utils`
* @deprecated [๐ช] Use queues instead
*/
async function forEachAsync(array, options, callbackfunction) {
const { maxParallelCount = Infinity } = options;
let index = 0;
let runningTasks = [];
const tasks = [];
for (const item of array) {
const currentIndex = index++;
const task = callbackfunction(item, currentIndex, array);
tasks.push(task);
runningTasks.push(task);
/* not await */ Promise.resolve(task).then(() => {
runningTasks = runningTasks.filter((t) => t !== task);
});
if (maxParallelCount < runningTasks.length) {
await Promise.race(runningTasks);
}
}
await Promise.all(tasks);
}
/**
* Function to check if a string is valid CSV
*
* @param value The string to check
* @returns `true` if the string is a valid CSV string, false otherwise
*
* @public exported from `@promptbook/utils`
*/
function isValidCsvString(value) {
try {
// A simple check for CSV